19 Commits
Author SHA1 Message Date
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 103 additions and 390 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
+92 -356
View File
@@ -5,33 +5,27 @@ 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()+'astrolobott.py','r') as file:
with open(script_path()+'astrolobot.py','r') as file:
current_script=file.read() current_script=file.read()
urlretrieve( urlretrieve(
'https://gitea.sugoidogo.com/SugoiDogo/astrolobot/releases/download/latest/astrolobot.py', 'https://github.com/sugoidogo/astrolobot/releases/latest/download/astrolobot.py',
script_path()+'astrolobot.py' script_path()+'astrolobot.py'
) )
with open(script_path()+'astrolobot.py','r') as file: with open(script_path()+'astrolobott.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'): print('checking 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')
if not path.exists(script_path()+'ephe/seas_18.se1'): if not path.exists(script_path()+'ephe/seas_18.se1'):
@@ -39,16 +33,8 @@ def dependency_setup():
makedirs(script_path()+'ephe') makedirs(script_path()+'ephe')
urlretrieve('https://github.com/aloistr/swisseph/raw/refs/heads/master/ephe/seas_18.se1',script_path()+'ephe/seas_18.se1') urlretrieve('https://github.com/aloistr/swisseph/raw/refs/heads/master/ephe/seas_18.se1',script_path()+'ephe/seas_18.se1')
import swisseph as swe
swe.set_ephe_path(script_path()+'ephe/')
swe.set_sid_mode(swe.SIDM_LAHIRI)
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,24 +45,20 @@ 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): zodiac_signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
zodiac = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"]
return zodiac[int(angle//30)]
def is_retrograde(speed): def get_planet_info(date):
return speed<0
@cache
def get_positions_raw(date=today()):
import swisseph as swe import swisseph as swe
# make sure location is set in THIS thread
swe.set_ephe_path(script_path()+'ephe/')
swe.set_sid_mode(swe.SIDM_LAHIRI)
# Get current UTC time
julian_day = swe.julday(date.year, date.month, date.day) julian_day = swe.julday(date.year, date.month, date.day)
# List of planets (Swiss Ephemeris IDs)
planets = { planets = {
"The Sun": swe.SUN, "The Sun": swe.SUN,
"The Moon": swe.MOON, "The Moon": swe.MOON,
@@ -91,303 +73,93 @@ def get_positions_raw(date=today()):
"Chiron": swe.CHIRON, "Chiron": swe.CHIRON,
"North Node":swe.TRUE_NODE "North Node":swe.TRUE_NODE
} }
positions={}
for planet_name, planet_id in planets.items(): retrograde_info = []
position, _ = swe.calc_ut(julian_day, planet_id, swe.FLG_SPEED) positions_info = {}
positions[planet_name]={
'angle':position[0],
'speed':position[3],
}
if planet_name=='North Node':
positions['South Node']={
'angle':(position[0]+180)%360,
'speed':position[3],
}
return positions for name, planet_id in planets.items():
# Get planet position (longitude) and speed
pos, flags = swe.calc_ut(julian_day, planet_id, swe.FLG_SPEED)
@cache # Check if retrograde (speed is negative)
def get_positions(date=today()): is_retrograde = pos[3] < 0
positions={} if is_retrograde:
if name=='North Node':
for name, position_raw in get_positions_raw(date).items(): pass
positions[name]={
'zodiac':get_zodiac(position_raw['angle']),
'retrograde':is_retrograde(position_raw['speed']),
}
return positions
def get_list_formatted(in_list,isare=False):
match len(in_list):
case 0:
output='Nothing'
if isare:
output+=' is '
case 1:
output=in_list[0]
if isare:
output+=' is '
case 2:
output=' and '.join(in_list)
if isare:
output+=' are '
case _:
output=', '.join(in_list[0:-1])+', and '+in_list[-1]
if isare:
output+=' are '
return output
@cache
def get_positions_formatted(date=today()):
retrograde_planets=[]
position_strings=[]
for name, position in get_positions(date).items():
if(position['retrograde']):
retrograde_planets.append(name)
position_strings.append(name+' is in '+position['zodiac'])
positions_formatted=get_list_formatted(retrograde_planets,True)+'in Retrograde\n'
positions_formatted+=', '.join(position_strings[0:2])+'\n'
positions_formatted+=', '.join(position_strings[2:5])+'\n'
positions_formatted+=', '.join(position_strings[5:8])+'\n'
positions_formatted+=', '.join(position_strings[8:11])+'\n'
positions_formatted+=', '.join(position_strings[11:13])+'\n'
return positions_formatted
seconds_in_1_day=86400
@cache
def get_transits(date=today(), maxdays=28):
positions_now=get_positions(date)
transits={}
days=1
while len(transits)<12 and days<=maxdays:
future_timestamp=date.timestamp()+(days*seconds_in_1_day)
future_date=datetime.fromtimestamp(future_timestamp,timezone.utc)
positions_future=get_positions(future_date)
for name, position_future in positions_future.items():
position_now=positions_now[name]
if name not in transits and position_now!=position_future:
position_future['date']=future_date
if position_now['zodiac']==position_future['zodiac']:
position_future['zodiac']=None
if position_now['retrograde']==position_future['retrograde']:
position_future['retrograde']=None
transits[name]=position_future
days+=1
return transits
date_format='%b %d'
@cache
def get_transits_formatted(date=today(), maxdays=28):
transits_formatted=''
for name,transit in get_transits(date, maxdays).items():
if transit['zodiac']!=None:
transits_formatted+=name+' is entering '+transit['zodiac']+' on '+transit['date'].strftime(date_format)+'\n'
if transit['retrograde']==True:
transits_formatted+=name+' is entering Retrograde on '+transit['date'].strftime(date_format)+'\n'
if transit['retrograde']==False:
transits_formatted+=name+' is exiting Retrograde on '+transit['date'].strftime(date_format)+'\n'
return transits_formatted
@cache
def get_aspects(date=today(), minor=False):
positions=get_positions_raw(date).copy()
major_aspects={
'conjuction':{'angle':0,'orb':10.0},
'oposition':{'angle':180,'orb':10.0},
'trine':{'angle':120,'orb':10.0},
'square':{'angle':90,'orb':10.0},
'sextile':{'angle':60,'orb':5.0},
}
minor_aspects={
'semi-sextile':{'angle':30,'orb':1.5},
'inconjunct':{'angle':150,'orb':3},
'semi-square':{'angle':45,'orb':3},
'trioctile':{'angle':135,'orb':3},
'quintile':{'angle':72,'orb':1},
'biquintile':{'angle':144,'orb':1},
}
aspects={}
if minor:
selected_aspects=minor_aspects
else: else:
selected_aspects=major_aspects retrograde_info.append(name)
for aname, aposition in positions.copy().items(): # Get zodiac sign (0-360° -> Aries to Pisces)
del positions[aname] sign_num = int(pos[0] // 30) # 30° per sign
if aname.endswith('Node'): positions_info[name]=sign_num
continue
planet_aspects={}
for bname, bposition in positions.items():
angle_diff=abs(aposition['angle']-bposition['angle'])
for aspect_name,aspect in selected_aspects.items():
aspect_max=(aspect['angle']+aspect['orb'])%360
aspect_min=(aspect['angle']-aspect['orb'])%360
if aspect_min<angle_diff<aspect_max:
try:
planet_aspects[aspect_name].append(bname)
except KeyError:
planet_aspects[aspect_name]=[bname]
if len(planet_aspects)!=0:
aspects[aname]=planet_aspects
return aspects if name=='North Node':
# rotate sign 180 dagrees
south_pos=pos[0]+180
if(south_pos>360):
south_pos-=360
# calculate South Node sign
sign_num = int(south_pos // 30)
positions_info['South Node']=sign_num
@cache return {
def get_aspects_formatted(date=today(),minor=False): 'positions': positions_info,
aspects_formatted='' 'retrograde': retrograde_info,
for planet,aspects in get_aspects(date,minor).items():
aspects_formatted+=planet+' is in '
aspects_list=[]
for aspect_name,aspect_planets in aspects.items():
aspects_list.append(aspect_name+' with '+get_list_formatted(aspect_planets))
aspects_formatted+=get_list_formatted(aspects_list)+'\n'
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): def get_planet_info_formatted(date=datetime.utcnow()):
ensure_pre_path(planet_a) planets=get_planet_info(date)
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): match len(planets['retrograde']):
pre_transits[planet_a]={} case 0:
for aspect,planets in aspects.items(): info=''
pre_aspect(planet_a,aspect,planets,date_future,direction) case 1:
info=planets['retrograde'][0]+' is in Retrograde\n'
case 2:
info=' and '.join(planets['retrograde'])+' are in Retrograde\n'
case _:
info=', '.join(planets['retrograde'][0:-1])+', and '+planets['retrograde'][-1]+' are in Retrograde\n'
while days<maxdays: positions_info=[]
for planet,position in planets['positions'].items():
positions_info.append(planet+' is in '+zodiac_signs[position])
info+=', '.join(positions_info[0:2])+'\n'
info+=', '.join(positions_info[2:5])+'\n'
info+=', '.join(positions_info[5:8])+'\n'
info+=', '.join(positions_info[8:11])+'\n'
info+=', '.join(positions_info[11:13])+'\n'
return info
def get_transits():
seconds_in_1_day=86400
now=datetime.utcnow()
positions_today=get_planet_info(now)
position_updates={}
days=1
date_format='%b %d'
while len(position_updates)<12 and days<29:
future_timestamp=now.timestamp()+(days*seconds_in_1_day)
future_date=datetime.fromtimestamp(future_timestamp,timezone.utc)
positions_future=get_planet_info(future_date)
for planet in positions_future['retrograde']:
if planet not in positions_today['retrograde'] and planet not in position_updates.keys():
position_updates[planet]='entering Retrograde on '+future_date.strftime(date_format)
for planet in positions_today['retrograde']:
if planet not in positions_future['retrograde'] and planet not in position_updates.keys():
position_updates[planet]='exiting Retrograde on '+future_date.strftime(date_format)
for planet in positions_future['positions']:
if positions_today['positions'][planet]!=positions_future['positions'][planet] and planet not in position_updates.keys():
position_updates[planet]='entering '+zodiac_signs[positions_future['positions'][planet]]+' on '+future_date.strftime(date_format)
days+=1 days+=1
timestamp_future=timestamp_now+(seconds_in_1_day*days) return position_updates
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 def get_transits_formatted():
while days<maxdays: transits=get_transits()
days+=1 info=''
timestamp_future=timestamp_now+(seconds_in_1_day*days) for planet,transit in transits.items():
date_future=datetime.fromtimestamp(timestamp_future,timezone.utc) info+=planet+' is '+transit+'\n'
aspects_future=get_aspects(date_future) return info
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
@@ -397,11 +169,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 +238,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,29 +258,12 @@ 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_planet_info_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']:
for line in get_transits_formatted().splitlines(False): for line in get_transits_formatted().splitlines(False):
await client.channel.chat.send_message(line,data['message_id']) await client.channel.chat.send_message(line,data['message_id'])
return
if data['message']['text']==config['major_aspects']:
for line in get_aspects_formatted().splitlines(False):
await client.channel.chat.send_message(line,data['message_id'])
return
if data['message']['text']==config['minor_aspects']:
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'])
return
client.run(*tokens.values()) client.run(*tokens.values())
@@ -524,9 +275,6 @@ def script_main():
dependency_setup() dependency_setup()
main() main()
def obs_update(*args):
Thread(target=update,daemon=True).start()
def obs_load_settings(): def obs_load_settings():
import obspython import obspython
global obs_settings global obs_settings
@@ -561,14 +309,10 @@ def script_properties():
import obspython import obspython
properties=obspython.obs_properties_create() properties=obspython.obs_properties_create()
obspython.obs_properties_add_button(properties,'login','Login to Twitch',login) obspython.obs_properties_add_button(properties,'login','Login to Twitch',login)
obspython.obs_properties_add_button(properties,'update','Check for Updates',obs_update) obspython.obs_properties_add_button(properties,'update','Check for Updates',update)
obspython.obs_properties_add_text(properties,'commands','commands',obspython.OBS_TEXT_INFO) obspython.obs_properties_add_text(properties,'commands','commands',obspython.OBS_TEXT_INFO)
obspython.obs_properties_add_text(properties,'positions','positions',obspython.OBS_TEXT_DEFAULT) obspython.obs_properties_add_text(properties,'positions','positions',obspython.OBS_TEXT_DEFAULT)
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,'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):
@@ -576,10 +320,6 @@ def script_defaults(settings):
obspython.obs_data_set_default_string(settings,'commands','you can change the commands below, they save automatically') obspython.obs_data_set_default_string(settings,'commands','you can change the commands below, they save automatically')
obspython.obs_data_set_default_string(settings,'positions','!positions') obspython.obs_data_set_default_string(settings,'positions','!positions')
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,'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 +350,6 @@ 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_planet_info_formatted())
print(get_transits_formatted()) print(get_transits_formatted())
print(get_aspects_formatted())
print(get_aspects_formatted(minor=True))
print(get_aspect_transits_formatted())
print(get_aspect_transits_formatted(minor=True))
main() main()
+1 -1
View File
@@ -1,2 +1,2 @@
pyswisseph pyswisseph
twitch.py == 3.* twitch.py