11import asyncio
22import smtplib
33import traceback
4+ from aiohttp import ClientSession
45from contextlib import asynccontextmanager
56from datetime import date , datetime
67from datetime import time as dt_time
1112
1213import asyncpg
1314from jinja2 import Environment , PackageLoader
14- from sanic import response
15+ from sanic import response , Sanic
16+ from sanic .request import Request
17+ from sanic .response import HTTPResponse
1518
1619
1720class Oauth2 :
18- def __init__ (self , client_id , client_secret , scope = None , redirect_uri = None , session = None ):
21+ """An Oauth2 class to handle logging in with Discord"""
22+
23+ def __init__ (self , client_id : str , client_secret : str , scope : str , redirect_uri : str , session : ClientSession ):
1924 self .client_id = client_id
2025 self .client_secret = client_secret
2126 self .scope = scope
2227 self .redirect_uri = redirect_uri
23- self .discord_login_url = 'https://discord.com/api/oauth2/authorize?client_id={}&redirect_uri={}&response_type=code&scope={}' .format (client_id , redirect_uri , scope )
28+ self .discord_login_url = 'https://discord.com/api/oauth2/authorize' + \
29+ f'?client_id={ client_id } &redirect_uri={ redirect_uri } &response_type=code&scope={ scope } '
2430 self .discord_token_url = 'https://discord.com/api/oauth2/token'
2531 self .discord_api_url = 'https://discord.com/api/v6'
2632 self .session = session
2733
28- async def get_access_token (self , code ):
34+ async def get_access_token (self , code : str ):
35+ """Get an access token to make a request to the Discord API
36+
37+ Parameters
38+ ----------
39+ code: str
40+ The code that gets sent to the request arguments when the user is redirected
41+ back to the redirect URI after authorization.
42+ """
2943 payload = {
3044 'client_id' : self .client_id ,
3145 'client_secret' : self .client_secret ,
@@ -43,11 +57,12 @@ async def get_access_token(self, code):
4357 resp = await z .json ()
4458 return resp .get ('access_token' )
4559
46- async def get_user_json (self , access_token ):
60+ async def get_user_json (self , access_token : str ):
61+ """Get user information with our new access token"""
4762 url = self .discord_api_url + '/users/@me'
4863
4964 headers = {
50- 'Authorization' : 'Bearer {}' . format ( access_token )
65+ 'Authorization' : f 'Bearer { access_token } '
5166 }
5267
5368 async with self .session .get (url , headers = headers ) as z :
@@ -56,7 +71,7 @@ async def get_user_json(self, access_token):
5671
5772
5873@asynccontextmanager
59- async def open_db_connection (app , ** options ):
74+ async def open_db_connection (app : Sanic , ** options ):
6075 user = options .pop ('user' , app .config .DB_USERNAME )
6176 password = options .pop ('password' , app .config .DB_PASSWORD )
6277 database = options .pop ('database' , app .config .DB_NAME )
@@ -68,7 +83,7 @@ async def open_db_connection(app, **options):
6883 # finally:
6984 await conn .close ()
7085
71- async def render_template (template , request , ** context ):
86+ async def render_template (template : str , request : Request , ** context ) -> HTTPResponse :
7287 """
7388 Function to return jinja variables to the html
7489 """
@@ -88,14 +103,13 @@ async def render_template(template, request, **context):
88103 request .ctx .session ['messages' ] = [] # Clear messages after every request
89104 return response .html (html_content )
90105
91- def add_message (request , category , message , redirect_to = None ):
92- """Add a flash message to appear at the top of a page and redirect to an endpoint if provided """
106+ def add_message (request : Request , category : str , message : str , redirect_to : str ):
107+ """Add a flash message to appear at the top of a page and redirect to an endpoint"""
93108 request .ctx .session ['messages' ].append ([category , message ])
94- if redirect_to :
95- return response .redirect (redirect_to )
109+ return response .redirect (redirect_to )
96110
97111
98- def disable_xss (content ) :
112+ def disable_xss (content : str ) -> str :
99113 """Prevent cross-site scripting"""
100114 return content .replace ('&' , '&' ).replace ('<' , '<' ).replace ('>' , '>' )
101115
@@ -125,8 +139,6 @@ def daterange(start_date: date, end_date: date) -> list:
125139 day_count = (end_date - start_date ).days + 1
126140 return [start_date + timedelta (days = i ) for i in range (day_count )]
127141
128-
129-
130142def thisweek (today : date ) -> list :
131143 """Gets a list of dates for this week"""
132144 if 0 <= today .weekday () <= 4 :
@@ -138,7 +150,6 @@ def thisweek(today: date) -> list:
138150
139151 return daterange (monday , friday )
140152
141-
142153async def get_school_week (requested_date : date , first_day : date , week = True ):
143154 no_school = [
144155 date (2020 , 9 , 28 ), # Yom Kippur
@@ -206,7 +217,6 @@ async def get_school_week(requested_date: date, first_day: date, week=True):
206217 'date' : d ,
207218 'day' : prev_day + 1 })
208219
209-
210220 this_week = thisweek (requested_date )
211221
212222 if week :
@@ -217,7 +227,9 @@ async def get_school_week(requested_date: date, first_day: date, week=True):
217227 except IndexError :
218228 week_fmt .append (f"{ day .strftime ('%a %m/%d' )} <br>NO SCHOOL" )
219229 else :
220- week_fmt .append (f"{ day .strftime ('%a %m/%d' )} <br>{ day_info ['cohort' ].title ()} { day_map [day_info ['day' ] % 2 ]} day" )
230+ week_fmt .append (
231+ f"{ day .strftime ('%a %m/%d' )} <br>{ day_info ['cohort' ].title ()} { day_map [day_info ['day' ] % 2 ]} day"
232+ )
221233 return week_fmt
222234
223235 try :
@@ -228,7 +240,6 @@ async def get_school_week(requested_date: date, first_day: date, week=True):
228240 day_info ['day' ] = day_map [day_info ['day' ] % 2 ]
229241 return day_info
230242
231-
232243async def handle_daily_emails (app ):
233244 """Send out an email at a specified time every weekday"""
234245 today = date .today ()
@@ -249,7 +260,6 @@ async def handle_daily_emails(app):
249260 # Could be here if the func was called after the time on Friday
250261 return app .add_task (handle_daily_emails )
251262
252-
253263 today_info = await get_school_week (today , date (2020 , 9 , 8 ), week = False )
254264 if today_info is None :
255265 # No school
@@ -266,7 +276,7 @@ async def handle_daily_emails(app):
266276 msg ['From' ] = app .config .CUSTOM_EMAIL
267277 msg ['To' ] = email
268278 body = MIMEText (
269- f"Today, { today .strftime ('%m/%d/%Y' )} , is a { today_info ['cohort' ].title ()} { today_info ['day' ]} day. <br><br>"
279+ f"Today, { today .strftime ('%m/%d/%Y' )} , is a { today_info ['cohort' ].title ()} { today_info ['day' ]} day.<br><br>"
270280 f"Click <a href=\" http{ 's' if not app .config .DEV else '' } ://{ app .config .DOMAIN } "
271281 f"/schoolweek/unsubscribe/{ msg ['To' ]} \" >here</a> to unsubscribe." , 'html' )
272282
0 commit comments