25 Commits
Author SHA1 Message Date
sugoidogo 75bf46edc2 add aspects command 2025-07-27 22:14:09 -07:00
josephsmendoza 073bad2fe9 add aspect functions and refactor functions for reusable code 2025-07-24 21:12:53 -07:00
josephsmendoza 1e3e651ccb reduce startup delay by skipping pip install if pip folder exists 2025-07-24 09:49:55 -07:00
sugoidogo e4376a62f2 update in seperate thread 2025-07-20 23:18:24 -07:00
sugoidogo d1892a241f fix typo 2025-07-20 23:15:26 -07:00
sugoidogo eb045fa94a fix update function args 2025-07-20 23:14:19 -07:00
sugoidogo f586906a08 add update button to obs 2025-07-20 23:12:04 -07:00
sugoidogo 9278d32549 add command name to settings 2025-07-20 22:51:06 -07:00
josephsmendoza 2784872d12 fix early login request 2025-07-20 15:57:49 -07:00
josephsmendoza 9bfcc1a8a3 gracefully fail refreshing tokens 2025-07-20 15:55:39 -07:00
sugoidogo b730599d7e refresh tokens and log token activity 2025-07-20 02:59:32 -07:00
sugoidogo 32115c5a02 use universal strftime format 2025-07-19 01:57:37 -07:00
sugoidogo 43e3f632cc switch to twitch.py and other improvements 2025-07-19 01:31:50 -07:00
josephsmendoza 4102e8e672 fix overridden event 2025-07-18 17:10:36 -07:00
sugoidogo f0ca3fd16c fix dependency check 2025-07-18 01:39:05 -07:00
sugoidogo df27549942 better dependency check 2025-07-18 01:35:27 -07:00
sugoidogo cdb521b8ad fix loading with no tokens 2025-07-18 01:19:02 -07:00
sugoidogo 6907dacf89 fix library usage for OBS cold start 2025-07-18 01:03:56 -07:00
sugoidogo 06cc06476a add !transits command 2025-07-18 00:25:48 -07:00
sugoidogo 05dfb7215b updated output format 2025-07-16 21:18:54 -07:00
sugoidogo 11a713edbb install dependencies to astrolobot folder 2025-07-16 20:21:55 -07:00
josephsmendoza eba9505d1a more readable output format 2025-07-16 11:36:28 -07:00
josephsmendoza 241394fe2d add simple test without OBS 2025-07-16 11:36:09 -07:00
josephsmendoza f8347eeed8 download swiss ephemeris files as needed 2025-07-16 11:35:34 -07:00
josephsmendoza ee9fb1a969 initial commit 2025-07-16 03:49:20 -07:00
3 changed files with 43 additions and 236 deletions
-23
View File
@@ -1,23 +0,0 @@
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
+42 -212
View File
@@ -5,32 +5,28 @@ 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: updated_script=file.read()
updated_script=file.read() if current_script==updated_script:
if current_script==updated_script: 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','-r',script_path()+'/requirements.txt','--target',script_path()+'pip']) pip(['install','-qq','pyswisseph','twitch.py','--target',script_path()+'pip'])
sys.path.append(script_path()+'pip') sys.path.append(script_path()+'pip')
@@ -45,10 +41,6 @@ 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():
@@ -59,10 +51,6 @@ 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):
@@ -73,8 +61,7 @@ def get_zodiac(angle):
def is_retrograde(speed): def is_retrograde(speed):
return speed<0 return speed<0
@cache def get_positions_raw(date):
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 = {
@@ -107,8 +94,7 @@ def get_positions_raw(date=today()):
return positions return positions
@cache def get_positions(date):
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():
@@ -119,7 +105,7 @@ def get_positions(date=today()):
return positions return positions
def get_list_formatted(in_list,isare=False): def get_list_formatted(in_list,isare=True):
match len(in_list): match len(in_list):
case 0: case 0:
output='Nothing' output='Nothing'
@@ -139,8 +125,7 @@ def get_list_formatted(in_list,isare=False):
output+=' are ' output+=' are '
return output return output
@cache def get_positions_formatted(date):
def get_positions_formatted(date=today()):
retrograde_planets=[] retrograde_planets=[]
position_strings=[] position_strings=[]
@@ -149,7 +134,7 @@ def get_positions_formatted(date=today()):
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,True)+'in Retrograde\n' positions_formatted=get_list_formatted(retrograde_planets)+'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'
@@ -159,16 +144,15 @@ def get_positions_formatted(date=today()):
return positions_formatted return positions_formatted
seconds_in_1_day=86400 def get_transits():
seconds_in_1_day=86400
@cache now=datetime.utcnow()
def get_transits(date=today(), maxdays=28): positions_now=get_positions(now)
positions_now=get_positions(date)
transits={} transits={}
days=1 days=1
while len(transits)<12 and days<=maxdays: while len(transits)<12 and days<29:
future_timestamp=date.timestamp()+(days*seconds_in_1_day) future_timestamp=now.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():
@@ -184,13 +168,11 @@ def get_transits(date=today(), maxdays=28):
return transits return transits
date_format='%b %d' def get_transits_formatted():
date_format='%b %d'
@cache
def get_transits_formatted(date=today(), maxdays=28):
transits_formatted='' transits_formatted=''
for name,transit in get_transits(date, maxdays).items(): for name,transit in get_transits().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:
@@ -200,9 +182,8 @@ def get_transits_formatted(date=today(), maxdays=28):
return transits_formatted return transits_formatted
@cache def get_aspects(date, minor=False):
def get_aspects(date=today(), minor=False): positions=get_positions_raw(date)
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},
@@ -244,151 +225,18 @@ def get_aspects(date=today(), minor=False):
return aspects return aspects
@cache def get_aspects_formatted(date,minor=False):
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)) aspects_list.append(aspect_name+' with '+get_list_formatted(aspect_planets,False))
aspects_formatted+=get_list_formatted(aspects_list)+'\n' aspects_formatted+=get_list_formatted(aspects_list,False)+'\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
@@ -397,11 +245,7 @@ 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)
else: webbrowser.open('https://www.twitch.tv/activate?public=true&device-code='+device_code)
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:
@@ -470,7 +314,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 len(tokens)==0: if obs_settings == None:
login() login()
@client.event @client.event
@@ -490,7 +334,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().splitlines(False): for line in get_positions_formatted(datetime.now()).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']:
@@ -498,19 +342,11 @@ 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().splitlines(False): for line in get_aspects_formatted(datetime.now()).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(minor=True).splitlines(False): for line in get_aspects_formatted(datetime.now(),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
@@ -567,8 +403,6 @@ 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):
@@ -578,8 +412,6 @@ 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
@@ -610,10 +442,8 @@ 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()) #print(get_positions_formatted(datetime(2025,7,21)))
print(get_transits_formatted()) #print(get_transits_formatted())
print(get_aspects_formatted()) #main()
print(get_aspects_formatted(minor=True)) print(get_aspects_formatted(datetime(2025,7,21)))
print(get_aspect_transits_formatted()) print(get_aspects_formatted(datetime(2025,7,21),True))
print(get_aspect_transits_formatted(minor=True))
main()
+1 -1
View File
@@ -1,2 +1,2 @@
pyswisseph pyswisseph
twitch.py == 3.* twitch.py