FInal draft for no oauth #43

Merged
Xefir merged 2 commits from simple into master 2022-09-07 17:40:20 +00:00
9 changed files with 104 additions and 254 deletions

View File

@ -1,3 +1 @@
DISCORD_TOKEN= DISCORD_TOKEN=
OAUTH2_CLIENT_ID=
OAUTH2_CLIENT_SECRET=

View File

@ -2,14 +2,13 @@ import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from os import environ, path from os import environ, path
from typing import List, Optional from typing import Optional
from disnake import Client, Guild from disnake import Client, Guild
from dotenv import load_dotenv from dotenv import load_dotenv
from ics import Calendar, Event # type: ignore from ics import Calendar, Event # type: ignore
from ics.alarm.display import DisplayAlarm # type: ignore from ics.alarm.display import DisplayAlarm # type: ignore
from quart import Quart, redirect, render_template, request, session, url_for from quart import Quart, redirect, render_template, request, url_for
from requests_oauthlib import OAuth2Session # type: ignore
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.quart import QuartIntegration from sentry_sdk.integrations.quart import QuartIntegration
@ -17,18 +16,8 @@ load_dotenv()
QUART_DEBUG = environ.get("QUART_DEBUG", False) QUART_DEBUG = environ.get("QUART_DEBUG", False)
DISCORD_TOKEN = environ.get("DISCORD_TOKEN") DISCORD_TOKEN = environ.get("DISCORD_TOKEN")
OAUTH2_CLIENT_ID = environ.get("OAUTH2_CLIENT_ID") if not DISCORD_TOKEN:
OAUTH2_CLIENT_SECRET = environ.get("OAUTH2_CLIENT_SECRET") raise Exception("Missing DISCORD_TOKEN")
OAUTH2_REDIRECT_URI = environ.get("OAUTH2_REDIRECT_URI", "callback")
if not DISCORD_TOKEN or not OAUTH2_CLIENT_ID or not OAUTH2_CLIENT_SECRET:
raise Exception(
"Missing some env variables "
"(could be DISCORD_TOKEN, OAUTH2_CLIENT_ID or OAUTH2_CLIENT_SECRET)"
)
API_BASE_URL = environ.get("API_BASE_URL", "https://discordapp.com/api")
AUTHORIZATION_BASE_URL = API_BASE_URL + "/oauth2/authorize"
TOKEN_URL = API_BASE_URL + "/oauth2/token"
if QUART_DEBUG: if QUART_DEBUG:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -45,41 +34,16 @@ class Discord(Client):
client = Discord() client = Discord()
app = Quart(__name__) app = Quart(__name__)
app.config["SECRET_KEY"] = OAUTH2_CLIENT_SECRET
def get_guild_by_id(guild_id: str) -> Optional[Guild]: def get_guild_by_id(guild_id: str) -> Optional[Guild]:
for guild in client.guilds: if guild_id:
if str(guild.id) == guild_id or guild.vanity_url_code == guild_id: for guild in client.guilds:
return guild if str(guild.id) == guild_id or guild.vanity_url_code == guild_id:
return guild
return None return None
def token_updater(token: str) -> None:
session["oauth2_token"] = token
def make_oauth_session(
host_url: str,
token: Optional[str] = None,
state: Optional[str] = None,
scope: Optional[List[str]] = None,
) -> OAuth2Session:
return OAuth2Session(
client_id=OAUTH2_CLIENT_ID,
token=token,
state=state,
scope=scope,
redirect_uri=host_url + OAUTH2_REDIRECT_URI,
auto_refresh_kwargs={
"client_id": OAUTH2_CLIENT_ID,
"client_secret": OAUTH2_CLIENT_SECRET,
},
auto_refresh_url=TOKEN_URL,
token_updater=token_updater,
)
CATALOG_CACHE = {} CATALOG_CACHE = {}
@ -118,80 +82,27 @@ def days_before_failure():
return nextDelta.days return nextDelta.days
def avatar_cdn(type: str, id: str, hash: str):
ext = "gif" if hash.startswith("a_") else "png"
return f"https://cdn.discordapp.com/{type}/{id}/{hash}.{ext}"
@app.context_processor @app.context_processor
def context_processor(): def context_processor():
return dict( return dict(_=i18n, client=client, days_before_failure=days_before_failure())
_=i18n,
avatar_cdn=avatar_cdn,
days_before_failure=days_before_failure(),
bot=client.user,
)
@app.route("/") @app.route("/")
async def index(): async def index():
guild_id = request.args.get("guild")
guild = get_guild_by_id(guild_id)
if guild:
return False
return await render_template("index.html.j2") return await render_template("index.html.j2")
@app.route("/login")
async def login():
token = session.get("oauth2_token")
if token is not None:
return redirect(url_for(".guilds"))
discord = make_oauth_session(
host_url=request.host_url, scope=["identify", "guilds"]
)
authorization_url, state = discord.authorization_url(AUTHORIZATION_BASE_URL)
session["oauth2_state"] = state
return redirect(authorization_url)
@app.route("/callback")
async def callback():
request_values = await request.values
if request_values.get("error"):
return request_values["error"]
discord = make_oauth_session(
host_url=request.host_url, state=session.get("oauth2_state")
)
token = discord.fetch_token(
TOKEN_URL,
client_secret=OAUTH2_CLIENT_SECRET,
authorization_response=request.url,
)
token_updater(token)
return redirect(url_for(".guilds"))
@app.route("/guilds")
async def guilds():
token = session.get("oauth2_token")
if token is None:
return redirect(url_for(".login"))
discord = make_oauth_session(host_url=request.host_url, token=token)
user = discord.get(API_BASE_URL + "/users/@me")
guilds = discord.get(API_BASE_URL + "/users/@me/guilds")
if user.status_code != 200 or guilds.status_code != 200:
return redirect(url_for(".login"))
return await render_template(
"guilds.html.j2", guilds=guilds.json(), user=user.json()
)
@app.route("/<guild_id>.ics") @app.route("/<guild_id>.ics")
async def ical(guild_id: str): async def ical(guild_id: str):
guild = get_guild_by_id(guild_id) guild = get_guild_by_id(guild_id)
if guild is None: if guild is None:
return redirect(url_for(".login")) return redirect(url_for(".index"))
calendar = Calendar() calendar = Calendar()

View File

@ -8,34 +8,47 @@
url('../fonts/open-sans-v29-latin-regular.woff') format('woff'); url('../fonts/open-sans-v29-latin-regular.woff') format('woff');
} }
html, body {
height: 100%;
}
a {
text-decoration: none;
}
body { body {
background-color: #2f3136; background-color: #2f3136;
margin: 0; margin: 0;
font-family: Whitney, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: Whitney, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #b9bbbe; color: #b9bbbe;
display: flex;
flex-direction: column;
justify-content: space-between;
} }
#avatars { #logo {
display: flex; position: relative;
align-items: center; left: 24px;
justify-content: space-around; top: 24px;
height: 36px;
width: 130px;
}
#avatars, footer, h1, h2 {
text-align: center;
} }
#avatars img { #avatars img {
border-radius: 50%; border-radius: 50%;
} }
#dots {
font-size: xx-large;
opacity: 0.2;
}
#content { #content {
min-width: 280px; min-width: 280px;
max-width: 400px; max-width: 400px;
background-color: #18191c; background-color: #18191c;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
align-self: center;
} }
#box { #box {
@ -52,18 +65,25 @@ body {
font-size: 14px; font-size: 14px;
} }
#details { footer ul {
font-size: 12px; font-size: 12px;
list-style-type: none; list-style-type: none;
padding: 0; padding: 0;
} }
#details a { footer ul li {
color: #a3a6aa; display: inline-block;
text-decoration: none;
} }
#details i { footer ul li:last-child {
display: block;
}
footer ul li a {
color: #a3a6aa;
}
footer ul li i {
margin: 8px; margin: 8px;
} }
@ -89,13 +109,11 @@ body {
h1 { h1 {
font-size: 20px; font-size: 20px;
text-align: center;
color: white; color: white;
} }
h2 { h2 {
font-size: 16px; font-size: 16px;
text-align: center;
} }
h3 { h3 {
@ -122,21 +140,17 @@ hr {
padding: 0 16px; padding: 0 16px;
border-radius: 3px; border-radius: 3px;
color: white; color: white;
text-decoration: none;
display: inline-block; display: inline-block;
} }
@media only screen and (min-width: 600px) { @media only screen and (max-width : 320px) {
#content { #logo {
position: absolute; text-align: center;
top: 50%; top: inherit;
left: 50%; left: inherit;
transform: translate(-50%, -50%);
} }
#logo { footer ul li {
position: relative; display: block;
left: 24px;
top: 24px;
} }
} }

View File

@ -17,13 +17,16 @@
</head> </head>
<body> <body>
<img src="{{ url_for('static', filename='img/Discord-Logo+Wordmark-White.svg') }}" <img src="{{ url_for('static', filename='img/Discord-Logo+Wordmark-White.svg') }}"
id="logo" id="logo"
height="36" height="36"
width="130" width="130"
alt="{{ _('Discord Logo') }}"/> alt="{{ _('Discord Logo') }}"/>
<div id="content"> <div id="content">
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
</div> </div>
<footer>
{% include 'footer.html.j2' %}
</footer>
</body> </body>
</html> </html>

View File

@ -1,4 +1,4 @@
<ul id="details"> <ul>
<li> <li>
<a href="https://discord.com/users/133305654512320513" target="_blank"> <a href="https://discord.com/users/133305654512320513" target="_blank">
<i class="fa fa-user-plus"></i> <i class="fa fa-user-plus"></i>

View File

@ -1,23 +0,0 @@
{% extends "base.html.j2" %}
{% block content %}
<div id="box">
<div id="avatars">
<img src="{{ bot.display_avatar }}"
alt="{{ _('Bot Logo') }}"
width="80"
height="80"/>
<span id="dots">…</span>
<img src="{{ avatar_cdn('avatars', user.id, user.avatar) }}"
alt="{{ _('User Logo') }}"
width="80"
height="80"/>
</div>
</div>
<select name="guild">
{% for guild in guilds %}
<option value="{{ guild.id }}">
{{ guild.name }}
</option>
{% endfor %}
</select>
{% endblock content %}

View File

@ -1,30 +1,37 @@
{% extends "base.html.j2" %} {% extends "base.html.j2" %}
{% block content %} {% block content %}
<div id="box"> <form action="" method="get">
<div id="avatars"> <div id="box">
<img src="{{ bot.display_avatar }}" <div id="avatars">
alt="{{ _('Bot Logo') }}" <img src="{{ client.user.display_avatar }}" alt="{{ _('Bot Logo') }}" width="80" height="80" />
width="80" </div>
height="80"/> <h1>{{ client.user.display_name }}</h1>
<h2>{{ _('The discord scheduled event calendar generator') }}</h2>
<hr />
<h3>{{ _('This will allow you to:') }}</h3>
<ul id="scopes">
<li>
<i class="fa fa-custom-circle fa-check"></i>
{{ _('Subscribe to a calendar on Google, Yahoo, Outlook, Apple or any ICS complient software') }}
</li>
<li>
<i class="fa fa-custom-circle fa-times"></i>
{{ _('Throwing you to a new isekai world') }}
</li>
</ul>
<hr />
<h3>{{ _('Choose a server:') }}</h3>
<select name="guild">
<option>Choose a server:</option>
{% for guild in client.guilds %}
<option value="{{ guild.id }}">
{{ guild.name }}
</option>
{% endfor %}
</select>
</div> </div>
<h1>{{ bot.display_name }}</h1> <div id="buttons">
<h2>{{ _('The discord scheduled event calendar generator') }}</h2> <a href="{{ url_for('index') }}">{{ _("Let's go!") }}</a>
<hr /> </div>
<h3>{{ _('This will allow you to:') }}</h3> </form>
<ul id="scopes">
<li>
<i class="fa fa-custom-circle fa-check"></i>
{{ _('Subscribe to a calendar on Google, Yahoo, Outlook, Apple or any ICS complient software') }}
</li>
<li>
<i class="fa fa-custom-circle fa-times"></i>
{{ _('Throwing you to a new isekai world') }}
</li>
</ul>
<hr />
{% include 'footer.html.j2' %}
</div>
<div id="buttons">
<a href="{{ url_for('login') }}">{{ _("Let's go!") }}</a>
</div>
{% endblock content %} {% endblock content %}

66
poetry.lock generated
View File

@ -440,19 +440,6 @@ category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "oauthlib"
version = "3.2.0"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.9.0" version = "0.9.0"
@ -558,42 +545,9 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[[package]]
name = "requests"
version = "2.28.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<3"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-oauthlib"
version = "1.3.1"
description = "OAuthlib authentication support for Requests."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
oauthlib = ">=3.0.0"
requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]] [[package]]
name = "sentry-sdk" name = "sentry-sdk"
version = "1.9.7" version = "1.9.8"
description = "Python client for Sentry (https://sentry.io)" description = "Python client for Sentry (https://sentry.io)"
category = "main" category = "main"
optional = false optional = false
@ -759,7 +713,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "b7dd5fc5983b3b1c86b333d3bbf2d7808ef12b92b17571a9316a35879d2643dd" content-hash = "af26c03edbb68dda37f3710e72f9a693234a94bc3c5d0dee16c3134a51a56dbd"
[metadata.files] [metadata.files]
aiofiles = [ aiofiles = [
@ -1177,10 +1131,6 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
] ]
oauthlib = [
{file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"},
{file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"},
]
pathspec = [ pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
@ -1324,17 +1274,9 @@ regex = [
{file = "regex-2022.8.17-cp39-cp39-win_amd64.whl", hash = "sha256:ccb986e80674c929f198464bce55e995178dea26833421e2479ff04a6956afac"}, {file = "regex-2022.8.17-cp39-cp39-win_amd64.whl", hash = "sha256:ccb986e80674c929f198464bce55e995178dea26833421e2479ff04a6956afac"},
{file = "regex-2022.8.17.tar.gz", hash = "sha256:5c77eab46f3a2b2cd8bbe06467df783543bf7396df431eb4a144cc4b89e9fb3c"}, {file = "regex-2022.8.17.tar.gz", hash = "sha256:5c77eab46f3a2b2cd8bbe06467df783543bf7396df431eb4a144cc4b89e9fb3c"},
] ]
requests = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
]
requests-oauthlib = [
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
]
sentry-sdk = [ sentry-sdk = [
{file = "sentry-sdk-1.9.7.tar.gz", hash = "sha256:af0987fc074ada4a166bdc7e9d99d1da7811c6107e4b3416c7052ea1adb77dfc"}, {file = "sentry-sdk-1.9.8.tar.gz", hash = "sha256:ef4b4d57631662ff81e15c22bf007f7ce07c47321648c8e601fbd22950ed1a79"},
{file = "sentry_sdk-1.9.7-py2.py3-none-any.whl", hash = "sha256:d391204a2a59c54b764cd351c44c67eed17b43d51f4dbe3eba48b83b70c93db9"}, {file = "sentry_sdk-1.9.8-py2.py3-none-any.whl", hash = "sha256:7378c08d81da78a4860da7b4900ac51fef38be841d01bc5b7cb249ac51e070c6"},
] ]
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},

View File

@ -14,9 +14,7 @@ disnake = "2.5.2"
ics = "0.7.2" ics = "0.7.2"
python-dotenv = "0.21.0" python-dotenv = "0.21.0"
quart = "0.18.0" quart = "0.18.0"
requests = "2.28.1" sentry-sdk = "1.9.8"
requests-oauthlib = "1.3.1"
sentry-sdk = "1.9.7"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "5.0.4" flake8 = "5.0.4"