FInal draft for no oauth #43
121
divent/bot.py
121
divent/bot.py
@ -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()
|
||||
|
||||
|
@ -8,34 +8,47 @@
|
||||
url('../fonts/open-sans-v29-latin-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
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;
|
||||
#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;
|
||||
background-color: #18191c;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#box {
|
||||
@ -52,18 +65,25 @@ body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#details {
|
||||
footer ul {
|
||||
font-size: 12px;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#details a {
|
||||
color: #a3a6aa;
|
||||
text-decoration: none;
|
||||
footer ul li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#details i {
|
||||
footer ul li:last-child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
footer ul li a {
|
||||
color: #a3a6aa;
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -122,21 +140,17 @@ hr {
|
||||
padding: 0 16px;
|
||||
border-radius: 3px;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
@ -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
66
poetry.lock
generated
@ -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"},
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user