FInal draft for no oauth
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Michel Roux 2022-09-07 17:36:18 +00:00
parent 98c4f7d599
commit f4ca67bb24
9 changed files with 102 additions and 251 deletions

View File

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

View File

@ -2,14 +2,13 @@ import json
import logging
from datetime import datetime, timedelta
from os import environ, path
from typing import List, Optional
from typing import Optional
from disnake import Client, Guild
from dotenv import load_dotenv
from ics import Calendar, Event # type: ignore
from ics.alarm.display import DisplayAlarm # type: ignore
from quart import Quart, redirect, render_template, request, session, url_for
from requests_oauthlib import OAuth2Session # type: ignore
from quart import Quart, redirect, render_template, request, url_for
import sentry_sdk
from sentry_sdk.integrations.quart import QuartIntegration
@ -17,18 +16,8 @@ load_dotenv()
QUART_DEBUG = environ.get("QUART_DEBUG", False)
DISCORD_TOKEN = environ.get("DISCORD_TOKEN")
OAUTH2_CLIENT_ID = environ.get("OAUTH2_CLIENT_ID")
OAUTH2_CLIENT_SECRET = environ.get("OAUTH2_CLIENT_SECRET")
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 not DISCORD_TOKEN:
raise Exception("Missing DISCORD_TOKEN")
if QUART_DEBUG:
logging.basicConfig(level=logging.DEBUG)
@ -45,41 +34,16 @@ class Discord(Client):
client = Discord()
app = Quart(__name__)
app.config["SECRET_KEY"] = OAUTH2_CLIENT_SECRET
def get_guild_by_id(guild_id: str) -> Optional[Guild]:
for guild in client.guilds:
if str(guild.id) == guild_id or guild.vanity_url_code == guild_id:
return guild
if guild_id:
for guild in client.guilds:
if str(guild.id) == guild_id or guild.vanity_url_code == guild_id:
return guild
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 = {}
@ -118,80 +82,27 @@ def days_before_failure():
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
def context_processor():
return dict(
_=i18n,
avatar_cdn=avatar_cdn,
days_before_failure=days_before_failure(),
bot=client.user,
)
return dict(_=i18n, client=client, days_before_failure=days_before_failure())
@app.route("/")
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")
@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")
async def ical(guild_id: str):
guild = get_guild_by_id(guild_id)
if guild is None:
return redirect(url_for(".login"))
return redirect(url_for(".index"))
calendar = Calendar()

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<ul id="details">
<ul>
<li>
<a href="https://discord.com/users/133305654512320513" target="_blank">
<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" %}
{% block content %}
<div id="box">
<div id="avatars">
<img src="{{ bot.display_avatar }}"
alt="{{ _('Bot Logo') }}"
width="80"
height="80"/>
<form action="" method="get">
<div id="box">
<div id="avatars">
<img src="{{ client.user.display_avatar }}" alt="{{ _('Bot Logo') }}" width="80" height="80" />
</div>
<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>
<h1>{{ bot.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 />
{% include 'footer.html.j2' %}
</div>
<div id="buttons">
<a href="{{ url_for('login') }}">{{ _("Let's go!") }}</a>
</div>
<div id="buttons">
<a href="{{ url_for('index') }}">{{ _("Let's go!") }}</a>
</div>
</form>
{% endblock content %}

66
poetry.lock generated
View File

@ -440,19 +440,6 @@ category = "dev"
optional = false
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]]
name = "pathspec"
version = "0.9.0"
@ -558,42 +545,9 @@ category = "dev"
optional = false
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]]
name = "sentry-sdk"
version = "1.9.7"
version = "1.9.8"
description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = false
@ -759,7 +713,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "b7dd5fc5983b3b1c86b333d3bbf2d7808ef12b92b17571a9316a35879d2643dd"
content-hash = "af26c03edbb68dda37f3710e72f9a693234a94bc3c5d0dee16c3134a51a56dbd"
[metadata.files]
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.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 = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{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.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 = [
{file = "sentry-sdk-1.9.7.tar.gz", hash = "sha256:af0987fc074ada4a166bdc7e9d99d1da7811c6107e4b3416c7052ea1adb77dfc"},
{file = "sentry_sdk-1.9.7-py2.py3-none-any.whl", hash = "sha256:d391204a2a59c54b764cd351c44c67eed17b43d51f4dbe3eba48b83b70c93db9"},
{file = "sentry-sdk-1.9.8.tar.gz", hash = "sha256:ef4b4d57631662ff81e15c22bf007f7ce07c47321648c8e601fbd22950ed1a79"},
{file = "sentry_sdk-1.9.8-py2.py3-none-any.whl", hash = "sha256:7378c08d81da78a4860da7b4900ac51fef38be841d01bc5b7cb249ac51e070c6"},
]
six = [
{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"
python-dotenv = "0.21.0"
quart = "0.18.0"
requests = "2.28.1"
requests-oauthlib = "1.3.1"
sentry-sdk = "1.9.7"
sentry-sdk = "1.9.8"
[tool.poetry.dev-dependencies]
flake8 = "5.0.4"