38 Commits
Author SHA1 Message Date
sugoidogo d278df09f7 don't fail for update check 2025-12-14 22:16:59 -08:00
sugoidogo 8e31da1636 automated release 2025-12-14 22:16:23 -08:00
sugoidogo e4280f7721 update release url 2025-12-14 22:13:23 -08:00
sugoidogo c2638a03ad always call login if refreshing tokens fails 2025-12-12 23:43:38 -08:00
sugoidogo a793c96532 always open browser if login is called and device_code is availible, and print url to output in case webbrowser.open fails 2025-12-12 23:43:03 -08:00
sugoidogo 1ce6dc2de1 use specific version of twitch.py 2025-12-12 23:37:33 -08:00
sugoidogo e93f0e6473 add aspect transits 2025-07-29 10:35:33 -07:00
sugoidogo 4fbdfef3ff fix maxdays comparison 2025-07-29 08:10:16 -07:00
sugoidogo 38e031a9ba prevent deleting cached results 2025-07-29 08:03:47 -07:00
sugoidogo 38b93884c2 restore missing variable 2025-07-29 07:58:17 -07:00
sugoidogo 862279c39c add default values to non-obs settings 2025-07-29 07:57:29 -07:00
sugoidogo e9fdb3a04d maximize cache usage 2025-07-29 07:57:01 -07:00
sugoidogo 363a985b16 cache all date parameter functions 2025-07-29 07:32:10 -07:00
sugoidogo 5529bf1402 add aspects command 2025-07-27 22:14:09 -07:00
sugoidogo 173fa33b8f add aspect functions and refactor functions for reusable code 2025-07-24 21:12:53 -07:00
sugoidogo bbe85db026 reduce startup delay by skipping pip install if pip folder exists 2025-07-24 09:49:55 -07:00
sugoidogo 3437bb50a6 update in seperate thread 2025-07-20 23:18:24 -07:00
sugoidogo 4cf720cc5d fix typo 2025-07-20 23:15:26 -07:00
sugoidogo 0e16e196b4 fix update function args 2025-07-20 23:14:19 -07:00
sugoidogo 70616babbd add update button to obs 2025-07-20 23:12:04 -07:00
sugoidogo 3cd0ab79a8 add command name to settings 2025-07-20 22:51:06 -07:00
sugoidogo 3182d66e63 fix early login request 2025-07-20 15:57:49 -07:00
sugoidogo e60ff257e6 gracefully fail refreshing tokens 2025-07-20 15:55:39 -07:00
sugoidogo ef93c95f9d refresh tokens and log token activity 2025-07-20 02:59:32 -07:00
sugoidogo fb72c54ab7 use universal strftime format 2025-07-19 01:57:37 -07:00
sugoidogo a3915595e3 switch to twitch.py and other improvements 2025-07-19 01:31:50 -07:00
sugoidogo 7cabd2e1b3 fix overridden event 2025-07-18 17:10:36 -07:00
sugoidogo 011deb41e0 fix dependency check 2025-07-18 01:39:05 -07:00
sugoidogo 0e67657698 better dependency check 2025-07-18 01:35:27 -07:00
sugoidogo bb05fc0b44 fix loading with no tokens 2025-07-18 01:19:02 -07:00
sugoidogo ee68221691 fix library usage for OBS cold start 2025-07-18 01:03:56 -07:00
sugoidogo fcc0d8649b add !transits command 2025-07-18 00:25:48 -07:00
sugoidogo 9695f0bc10 updated output format 2025-07-16 21:18:54 -07:00
sugoidogo 742ae622d2 install dependencies to astrolobot folder 2025-07-16 20:21:55 -07:00
sugoidogo 973ef9d338 more readable output format 2025-07-16 11:36:28 -07:00
sugoidogo f8861b130d add simple test without OBS 2025-07-16 11:36:09 -07:00
sugoidogo 967d5aa16a download swiss ephemeris files as needed 2025-07-16 11:35:34 -07:00
sugoidogo 08f2924c2a initial commit 2025-07-16 03:49:20 -07:00
3 changed files with 236 additions and 43 deletions
+23
View File
@@ -0,0 +1,23 @@
name: Release
on:
push:
tags:
- "*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- run: git log $(git describe --tags --abbrev=0 HEAD~1)..HEAD --oneline > changelog.txt
- uses: softprops/action-gh-release@v2
with:
make_latest: true
body_path: changelog.txt
files: astrolobot.py
+201 -31
View File
@@ -5,14 +5,16 @@ from urllib.parse import quote_plus
from urllib.request import urlretrieve, urlopen, Request, HTTPError from urllib.request import urlretrieve, urlopen, Request, HTTPError
from threading import Thread from threading import Thread
from os import makedirs,path from os import makedirs,path
from functools import cache
import json,sys,webbrowser,importlib,tomllib import json,sys,webbrowser,importlib,tomllib
def update(): def update():
print('checking for updates...') print('checking for updates...')
try:
with open(script_path()+'astrolobot.py','r') as file: with open(script_path()+'astrolobot.py','r') as file:
current_script=file.read() current_script=file.read()
urlretrieve( urlretrieve(
'https://github.com/sugoidogo/astrolobot/releases/latest/download/astrolobot.py', 'https://gitea.sugoidogo.com/SugoiDogo/astrolobot/releases/download/latest/astrolobot.py',
script_path()+'astrolobot.py' script_path()+'astrolobot.py'
) )
with open(script_path()+'astrolobot.py','r') as file: with open(script_path()+'astrolobot.py','r') as file:
@@ -21,12 +23,14 @@ def update():
print('up to date') print('up to date')
else: else:
print('update downloaded, restart script to apply') print('update downloaded, restart script to apply')
except:
print('update check failed, ignoring')
def dependency_setup(): def dependency_setup():
if not path.exists(script_path()+'pip'): if not path.exists(script_path()+'pip'):
print('installing dependencies') print('installing dependencies')
from pip._internal.cli.main import main as pip from pip._internal.cli.main import main as pip
pip(['install','-qq','pyswisseph','twitch.py','--target',script_path()+'pip']) pip(['install','-qq','-r',script_path()+'/requirements.txt','--target',script_path()+'pip'])
sys.path.append(script_path()+'pip') sys.path.append(script_path()+'pip')
@@ -41,6 +45,10 @@ def dependency_setup():
print('dependencies ready') print('dependencies ready')
def today():
now=datetime.utcnow()
return datetime(now.year,now.month,now.day)
# Astrology functions # Astrology functions
def load_settings(): def load_settings():
@@ -51,6 +59,10 @@ def load_settings():
return { return {
'positions':'!positions', 'positions':'!positions',
'transits':'!transits', 'transits':'!transits',
'major_aspects':'!aspects major',
'minor_aspects':'!aspects minor',
'major_aspect_transits':'!aspects major transits',
'minor_aspect_transits':'!aspects minor transits',
} }
def get_zodiac(angle): def get_zodiac(angle):
@@ -61,7 +73,8 @@ def get_zodiac(angle):
def is_retrograde(speed): def is_retrograde(speed):
return speed<0 return speed<0
def get_positions_raw(date): @cache
def get_positions_raw(date=today()):
import swisseph as swe import swisseph as swe
julian_day = swe.julday(date.year, date.month, date.day) julian_day = swe.julday(date.year, date.month, date.day)
planets = { planets = {
@@ -94,7 +107,8 @@ def get_positions_raw(date):
return positions return positions
def get_positions(date): @cache
def get_positions(date=today()):
positions={} positions={}
for name, position_raw in get_positions_raw(date).items(): for name, position_raw in get_positions_raw(date).items():
@@ -105,7 +119,7 @@ def get_positions(date):
return positions return positions
def get_list_formatted(in_list,isare=True): def get_list_formatted(in_list,isare=False):
match len(in_list): match len(in_list):
case 0: case 0:
output='Nothing' output='Nothing'
@@ -125,7 +139,8 @@ def get_list_formatted(in_list,isare=True):
output+=' are ' output+=' are '
return output return output
def get_positions_formatted(date): @cache
def get_positions_formatted(date=today()):
retrograde_planets=[] retrograde_planets=[]
position_strings=[] position_strings=[]
@@ -134,7 +149,7 @@ def get_positions_formatted(date):
retrograde_planets.append(name) retrograde_planets.append(name)
position_strings.append(name+' is in '+position['zodiac']) position_strings.append(name+' is in '+position['zodiac'])
positions_formatted=get_list_formatted(retrograde_planets)+'in Retrograde\n' positions_formatted=get_list_formatted(retrograde_planets,True)+'in Retrograde\n'
positions_formatted+=', '.join(position_strings[0:2])+'\n' positions_formatted+=', '.join(position_strings[0:2])+'\n'
positions_formatted+=', '.join(position_strings[2:5])+'\n' positions_formatted+=', '.join(position_strings[2:5])+'\n'
@@ -144,15 +159,16 @@ def get_positions_formatted(date):
return positions_formatted return positions_formatted
def get_transits(): seconds_in_1_day=86400
seconds_in_1_day=86400
now=datetime.utcnow() @cache
positions_now=get_positions(now) def get_transits(date=today(), maxdays=28):
positions_now=get_positions(date)
transits={} transits={}
days=1 days=1
while len(transits)<12 and days<29: while len(transits)<12 and days<=maxdays:
future_timestamp=now.timestamp()+(days*seconds_in_1_day) future_timestamp=date.timestamp()+(days*seconds_in_1_day)
future_date=datetime.fromtimestamp(future_timestamp,timezone.utc) future_date=datetime.fromtimestamp(future_timestamp,timezone.utc)
positions_future=get_positions(future_date) positions_future=get_positions(future_date)
for name, position_future in positions_future.items(): for name, position_future in positions_future.items():
@@ -168,11 +184,13 @@ def get_transits():
return transits return transits
def get_transits_formatted(): date_format='%b %d'
date_format='%b %d'
@cache
def get_transits_formatted(date=today(), maxdays=28):
transits_formatted='' transits_formatted=''
for name,transit in get_transits().items(): for name,transit in get_transits(date, maxdays).items():
if transit['zodiac']!=None: if transit['zodiac']!=None:
transits_formatted+=name+' is entering '+transit['zodiac']+' on '+transit['date'].strftime(date_format)+'\n' transits_formatted+=name+' is entering '+transit['zodiac']+' on '+transit['date'].strftime(date_format)+'\n'
if transit['retrograde']==True: if transit['retrograde']==True:
@@ -182,8 +200,9 @@ def get_transits_formatted():
return transits_formatted return transits_formatted
def get_aspects(date, minor=False): @cache
positions=get_positions_raw(date) def get_aspects(date=today(), minor=False):
positions=get_positions_raw(date).copy()
major_aspects={ major_aspects={
'conjuction':{'angle':0,'orb':10.0}, 'conjuction':{'angle':0,'orb':10.0},
'oposition':{'angle':180,'orb':10.0}, 'oposition':{'angle':180,'orb':10.0},
@@ -225,18 +244,151 @@ def get_aspects(date, minor=False):
return aspects return aspects
def get_aspects_formatted(date,minor=False): @cache
def get_aspects_formatted(date=today(),minor=False):
aspects_formatted='' aspects_formatted=''
for planet,aspects in get_aspects(date,minor).items(): for planet,aspects in get_aspects(date,minor).items():
aspects_formatted+=planet+' is in ' aspects_formatted+=planet+' is in '
aspects_list=[] aspects_list=[]
for aspect_name,aspect_planets in aspects.items(): for aspect_name,aspect_planets in aspects.items():
aspects_list.append(aspect_name+' with '+get_list_formatted(aspect_planets,False)) aspects_list.append(aspect_name+' with '+get_list_formatted(aspect_planets))
aspects_formatted+=get_list_formatted(aspects_list,False)+'\n' aspects_formatted+=get_list_formatted(aspects_list)+'\n'
return aspects_formatted return aspects_formatted
@cache
def get_aspect_transits(date=today(),minor=False,maxdays=28):
date_now=today()
timestamp_now=date_now.timestamp()
aspects_now=get_aspects(date_now,minor)
pre_transits={}
days=0
def aspects_contains(aspects,planet_a,aspect=None,planet_b=None):
if planet_a not in aspects:
return False
elif aspect and aspect not in aspects[planet_a]:
return False
elif aspect and planet_b and planet_b not in aspects[planet_a][aspect]:
return False
return True
def ensure_pre_path(planet_a,aspect=None):
if planet_a not in pre_transits:
pre_transits[planet_a]={}
if aspect and aspect not in pre_transits[planet_a]:
pre_transits[planet_a][aspect]={}
def pre_planet_b(planet_a,aspect,planet_b,date_future,direction):
ensure_pre_path(planet_a,aspect)
pre_transits[planet_a][aspect][planet_b]={
'date':date_future,
'direction':direction
}
def pre_aspect(planet_a,aspect,planets,date_future,direction):
ensure_pre_path(planet_a)
pre_transits[planet_a][aspect]={}
for planet_b in planets:
pre_planet_b(planet_a,aspect,planet_b,date_future,direction)
def pre_planet_a(planet_a,aspects,date_future,direction):
pre_transits[planet_a]={}
for aspect,planets in aspects.items():
pre_aspect(planet_a,aspect,planets,date_future,direction)
while days<maxdays:
days+=1
timestamp_future=timestamp_now+(seconds_in_1_day*days)
date_future=datetime.fromtimestamp(timestamp_future,timezone.utc)
aspects_future=get_aspects(date_future)
for planet_a,aspects in aspects_now.items():
try:
pre_transits[planet_a]
except KeyError:
if not aspects_contains(aspects_future,planet_a):
pre_planet_a(planet_a,aspects,date_future,'exiting')
continue
for aspect, planets in aspects.items():
try:
pre_transits[planet_a][aspect]
except KeyError:
if not aspects_contains(aspects_future,planet_a,aspect):
pre_aspect(planet_a,aspect,planets,date_future,'exiting')
continue
for planet_b in planets:
try:
pre_transits[planet_a][aspect][planet_b]
except KeyError:
if not aspects_contains(aspects_future,planet_a,aspect,planet_b):
pre_planet_b(planet_a,aspect,planet_b,date_future,'exiting')
days=0
while days<maxdays:
days+=1
timestamp_future=timestamp_now+(seconds_in_1_day*days)
date_future=datetime.fromtimestamp(timestamp_future,timezone.utc)
aspects_future=get_aspects(date_future)
for planet_a,aspects in aspects_future.items():
try:
pre_transits[planet_a]
except KeyError:
if not aspects_contains(aspects_now,planet_a):
pre_planet_a(planet_a,aspects,date_future,'entering')
continue
for aspect, planets in aspects.items():
try:
pre_transits[planet_a][aspect]
except KeyError:
if not aspects_contains(aspects_now,planet_a,aspect):
pre_aspect(planet_a,aspect,planets,date_future,'entering')
continue
for planet_b in planets:
try:
pre_transits[planet_a][aspect][planet_b]
except KeyError:
if not aspects_contains(aspects_now,planet_a,aspect,planet_b):
pre_planet_b(planet_a,aspect,planet_b,date_future,'entering')
post_transits={}
for planet_a,aspects in pre_transits.items():
post_transits[planet_a]={}
for aspect,planets in aspects.items():
for planet_b,transit in planets.items():
direction=transit['direction']
date=transit['date']
if direction not in post_transits[planet_a]:
post_transits[planet_a][direction]={}
if date not in post_transits[planet_a][direction]:
post_transits[planet_a][direction][date]={}
if aspect not in post_transits[planet_a][direction][date]:
post_transits[planet_a][direction][date][aspect]=[]
post_transits[planet_a][direction][date][aspect].append(planet_b)
return post_transits
@cache
def get_aspect_transits_formatted(date=today(),minor=False,maxdays=28):
aspect_transits=get_aspect_transits(date,minor,maxdays)
aspect_transits_formatted=''
for planet_a,directions in aspect_transits.items():
aspect_transits_formatted+=planet_a+' is '
direction_list=[]
for direction,dates in directions.items():
date_list=[]
for date,aspects in dates.items():
aspect_list=[]
for aspect,planets in aspects.items():
aspect_list.append(aspect+' with '+get_list_formatted(planets))
date_list.append(get_list_formatted(aspect_list)+' on '+date.strftime(date_format))
direction_list.append(direction+' '+get_list_formatted(date_list))
aspect_transits_formatted+=get_list_formatted(direction_list)+'\n'
return aspect_transits_formatted
# Twitch functions # Twitch functions
device_code=None device_code=None
@@ -245,7 +397,11 @@ def login(*args):
global device_code global device_code
if device_code==None: if device_code==None:
print('astrolobot is still starting, please wait',sys.stderr) print('astrolobot is still starting, please wait',sys.stderr)
webbrowser.open('https://www.twitch.tv/activate?public=true&device-code='+device_code) else:
login_url='https://www.twitch.tv/activate?public=true&device-code='+device_code
print("if your browser doesn't automatically open, go to the following url:")
print(login_url)
webbrowser.open(login_url)
def save_tokens(access_token,refresh_token): def save_tokens(access_token,refresh_token):
with open(script_path()+'tokens.json','w') as file: with open(script_path()+'tokens.json','w') as file:
@@ -314,7 +470,7 @@ def main():
async def on_code(code: str): async def on_code(code: str):
global device_code, obs_settings global device_code, obs_settings
device_code=code device_code=code
if obs_settings == None: if len(tokens)==0:
login() login()
@client.event @client.event
@@ -334,7 +490,7 @@ def main():
async def on_chat_message(data: eventsub.chat.MessageEvent): async def on_chat_message(data: eventsub.chat.MessageEvent):
config=load_settings() config=load_settings()
if data['message']['text']==config['positions']: if data['message']['text']==config['positions']:
for line in get_positions_formatted(datetime.now()).splitlines(False): for line in get_positions_formatted().splitlines(False):
await client.channel.chat.send_message(line,data['message_id']) await client.channel.chat.send_message(line,data['message_id'])
return return
if data['message']['text']==config['transits']: if data['message']['text']==config['transits']:
@@ -342,11 +498,19 @@ def main():
await client.channel.chat.send_message(line,data['message_id']) await client.channel.chat.send_message(line,data['message_id'])
return return
if data['message']['text']==config['major_aspects']: if data['message']['text']==config['major_aspects']:
for line in get_aspects_formatted(datetime.now()).splitlines(False): for line in get_aspects_formatted().splitlines(False):
await client.channel.chat.send_message(line,data['message_id']) await client.channel.chat.send_message(line,data['message_id'])
return return
if data['message']['text']==config['minor_aspects']: if data['message']['text']==config['minor_aspects']:
for line in get_aspects_formatted(datetime.now(),True).splitlines(False): for line in get_aspects_formatted(minor=True).splitlines(False):
await client.channel.chat.send_message(line,data['message_id'])
return
if data['message']['text']==config['major_aspect_transits']:
for line in get_aspect_transits_formatted().splitlines(False):
await client.channel.chat.send_message(line,data['message_id'])
return
if data['message']['text']==config['minor_aspect_transits']:
for line in get_aspect_transits_formatted(minor=True).splitlines(False):
await client.channel.chat.send_message(line,data['message_id']) await client.channel.chat.send_message(line,data['message_id'])
return return
@@ -403,6 +567,8 @@ def script_properties():
obspython.obs_properties_add_text(properties,'transits','transits',obspython.OBS_TEXT_DEFAULT) obspython.obs_properties_add_text(properties,'transits','transits',obspython.OBS_TEXT_DEFAULT)
obspython.obs_properties_add_text(properties,'major_aspects','major aspects',obspython.OBS_TEXT_DEFAULT) obspython.obs_properties_add_text(properties,'major_aspects','major aspects',obspython.OBS_TEXT_DEFAULT)
obspython.obs_properties_add_text(properties,'minor_aspects','minor aspects',obspython.OBS_TEXT_DEFAULT) obspython.obs_properties_add_text(properties,'minor_aspects','minor aspects',obspython.OBS_TEXT_DEFAULT)
obspython.obs_properties_add_text(properties,'major_aspect_transits','major aspect transits',obspython.OBS_TEXT_DEFAULT)
obspython.obs_properties_add_text(properties,'minor_aspect_transits','minor aspect transits',obspython.OBS_TEXT_DEFAULT)
return properties return properties
def script_defaults(settings): def script_defaults(settings):
@@ -412,6 +578,8 @@ def script_defaults(settings):
obspython.obs_data_set_default_string(settings,'transits','!transits') obspython.obs_data_set_default_string(settings,'transits','!transits')
obspython.obs_data_set_default_string(settings,'major_aspects','!aspects major') obspython.obs_data_set_default_string(settings,'major_aspects','!aspects major')
obspython.obs_data_set_default_string(settings,'minor_aspects','!aspects minor') obspython.obs_data_set_default_string(settings,'minor_aspects','!aspects minor')
obspython.obs_data_set_default_string(settings,'major_aspect_transits','!aspects major transits')
obspython.obs_data_set_default_string(settings,'minor_aspect_transits','!aspects minor transits')
def script_load(settings): def script_load(settings):
global obs_settings, save_tokens, load_tokens, load_settings global obs_settings, save_tokens, load_tokens, load_settings
@@ -442,8 +610,10 @@ if __name__ == '__main__':
def script_path(): def script_path():
return path.dirname(__file__)+'/' return path.dirname(__file__)+'/'
dependency_setup() dependency_setup()
#print(get_positions_formatted(datetime(2025,7,21))) print(get_positions_formatted())
#print(get_transits_formatted()) print(get_transits_formatted())
#main() print(get_aspects_formatted())
print(get_aspects_formatted(datetime(2025,7,21))) print(get_aspects_formatted(minor=True))
print(get_aspects_formatted(datetime(2025,7,21),True)) print(get_aspect_transits_formatted())
print(get_aspect_transits_formatted(minor=True))
main()
+1 -1
View File
@@ -1,2 +1,2 @@
pyswisseph pyswisseph
twitch.py twitch.py == 3.*