Merge pull request 'FInal draft for no oauth' (#43) from simple into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #43
This commit is contained in:
Michel Roux 2022-09-07 17:40:19 +00:00
commit 4125c20cd8
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]:
if guild_id:
for guild in client.guilds: for guild in client.guilds:
if str(guild.id) == guild_id or guild.vanity_url_code == guild_id: if str(guild.id) == guild_id or guild.vanity_url_code == guild_id:
return guild 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

@ -25,5 +25,8 @@
{% 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,13 +1,11 @@
{% extends "base.html.j2" %} {% extends "base.html.j2" %}
{% block content %} {% block content %}
<form action="" method="get">
<div id="box"> <div id="box">
<div id="avatars"> <div id="avatars">
<img src="{{ bot.display_avatar }}" <img src="{{ client.user.display_avatar }}" alt="{{ _('Bot Logo') }}" width="80" height="80" />
alt="{{ _('Bot Logo') }}"
width="80"
height="80"/>
</div> </div>
<h1>{{ bot.display_name }}</h1> <h1>{{ client.user.display_name }}</h1>
<h2>{{ _('The discord scheduled event calendar generator') }}</h2> <h2>{{ _('The discord scheduled event calendar generator') }}</h2>
<hr /> <hr />
<h3>{{ _('This will allow you to:') }}</h3> <h3>{{ _('This will allow you to:') }}</h3>
@ -22,9 +20,18 @@
</li> </li>
</ul> </ul>
<hr /> <hr />
{% include 'footer.html.j2' %} <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>
<div id="buttons"> <div id="buttons">
<a href="{{ url_for('login') }}">{{ _("Let's go!") }}</a> <a href="{{ url_for('index') }}">{{ _("Let's go!") }}</a>
</div> </div>
</form>
{% 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"