-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMain.py
207 lines (146 loc) · 5.29 KB
/
Main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
from Notifications import Notifications
from TwitchAPI import TwitchAPI
from Streamer import Streamer
from Validate import validate
from Config import Config
from Logger import Log
import traceback
import asyncio
import time
# >>> LOAD PLUGINS <<<
from Plugins.Pushover import Pushover
from Plugins.Discord import Discord
Config.enabled_modules = [Pushover, Discord]
streamer_dict = {}
refresh_rate = 1
initialized = asyncio.Event()
terminate = asyncio.Event()
# Runs Once to Initialize Modules and Builds Streamer Dict.
# Pre-Condition: Modules Have Been Included and Global Variables Have Been Declared
# Post-Condition: All Modules Have Been Initialized and the Streamer Dictionary has Been Populated
async def init():
global streamer_dict
global refresh_rate
# Load and Validate The Config File
await Config.load()
validation_warnings = validate()
validation_warnings += Log.validate()
# >>> VALIDATE PLUGINS <<<
for module in Config.enabled_modules:
if hasattr(module, "validate"):
ret = await to_async(module.validate)()
if type(ret) == list: validation_warnings += ret
# Fetch Refresh Rate
refresh_rate = float(Config.config_file["Twitch Settings"]["Refresh Rate"])
# Initialize Modules
Log.init(Config.config_file)
TwitchAPI.init(Config.config_file)
Notifications.init(Config.config_file)
# Display Any Warnings that Arose During the Config Validation Process
# We Had to Wait for Logs to Initialize Before Showing These
for warning in validation_warnings:
Log.logger.warning(warning)
# Initialize the Dictionary of Streamers
streamer_dict = await Streamer.init_all(Config.config_file)
# >>> INITIALIZE PLUGINS <<<
for module in Config.enabled_modules:
if hasattr(module, "init"):
await to_async(module.init)(streamer_dict)
# >>> SET PLUGIN ALERT CALLBACK <<<
Notifications.alert_callbacks = [Log.alert]
for module in Config.enabled_modules:
if hasattr(module, "alert"):
Notifications.alert_callbacks.append(to_async(module.alert))
# Start the Notification Handler
# Any Alerts that Arose During Initialization Will Now Be Sent
Notifications.Handler.streamer_dict = streamer_dict
Notifications.Handler.ready.set()
# Set 'Initialized' Event
initialized.set()
Log.logger.info("Initialized Successfully. Awaiting Activity...")
# Loop to Pull Data and Send Alerts
# Pre-condition: init() has Executed Successfully
# Post-Condition: Loop has Terminated Due to an Error/Interrupt
async def poll():
global streamer_dict
# Continuously Update Streamer Data
while True:
# Start a Timer
start = time.time()
# Checks Errors that Arose While Sending Notifications
Notifications.Handler.check_tasks()
# Get New Info on Streamers
await Streamer.refresh_all(streamer_dict)
# Try to Maintain a Constant Refresh Rate
time_remaining = (start + 1.0 / refresh_rate) - time.time()
await asyncio.sleep( (time_remaining if time_remaining > 0 else 0) )
# Primary Error Handler
# Pre-Condition: An Error Has Been Caught in main()
# Post-Condition: Errors Have Been Recorded by Log Module or Recursion Limit Was Hit
def error_handler(loop, exception):
# Call Recursive Function
fatal = error_helper(loop, exception, 0)
# Perform Shutdown Operations
if fatal or not initialized.is_set():
terminate.set()
loop.run_until_complete(shutdown())
# Recursive Helper for Error Handler
# Returns True for Fatal Errors
def error_helper(loop, exception, recursion_count):
# Terminate the Program if More than 5 Errors Occur
if recursion_count >= 5:
print("\nToo Many Errors Occurred While Handling Error!")
print("Displaying Errors (Earliest First):\n")
print("-"*50)
traceback.print_exception(type(exception), exception, exception.__traceback__)
print("-"*50)
return True
# Try to Handle the Original Exception
try:
return loop.run_until_complete(Log.fail(exception))
# If Any Errors Occur During the Handling of that Exception, Handle Those As Well
except BaseException as err:
return error_helper(loop, err, recursion_count + 1)
# Shut Down the Program
# Pre-Condition: A Fatal Error Has Been Handled in error_handler()
# Post-Condition: All Modules Have Been Shut Down and a Closing Log Message Has Been Sent
async def shutdown():
# Kill All Alert Tasks
await Notifications.Handler.stop()
# Kill ClientSession Objects
if hasattr(TwitchAPI, 'requests'):
await TwitchAPI.requests.close()
if hasattr(Notifications, 'requests'):
await Notifications.requests.close()
# >>> KILL PLUGINS <<<
for module in Config.enabled_modules:
if hasattr(module, 'terminate'):
try: await to_async(module.terminate)()
except: pass
# Send Closing Message
Log.sessionEnded()
# Returns an Asychronous Version of 'function'
def to_async(function):
# Already async
if asyncio.iscoroutinefunction(function):
return function
# Not async
async def async_func(*args, **kwargs):
return function(*args, **kwargs)
return async_func
# Set Up Event Loop
# Post-Condition: The terminate Event Has Been Set in error_handler()
def main():
loop = asyncio.get_event_loop()
while not terminate.is_set():
try:
if not initialized.is_set():
Notifications.Handler.start(loop)
loop.run_until_complete( init() )
loop.run_until_complete( poll() )
except BaseException as err:
error_handler(loop, err)
loop.close()
# Start Event Loop
if __name__ == "__main__":
main()