Compare commits

..

No commits in common. "master" and "2.0.2" have entirely different histories.

19 changed files with 1112 additions and 1456 deletions

34
.drone.yml Normal file
View File

@ -0,0 +1,34 @@
kind: pipeline
name: default
type: docker
steps:
- name: lint
image: python:3.8-slim
commands:
- pip install poetry
- poetry install
- poetry run flake8 .
- poetry run mypy .
- poetry run djlint .
- name: docker
image: plugins/docker
settings:
repo: xefir/divent
auto_tag: true
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: publish
image: python:3.8-slim
commands:
- pip install poetry
- poetry publish --build
environment:
POETRY_PYPI_TOKEN_PYPI:
from_secret: pypi_token
when:
event: tag

2
.flake8 Normal file
View File

@ -0,0 +1,2 @@
[flake8]
max-line-length = 100

View File

@ -1,56 +0,0 @@
name: divent
on: [push]
jobs:
lint:
runs-on: ubuntu-latest
container: python:3.12.3
steps:
- run: apt-get update
- run: apt-get install -y git nodejs
- uses: actions/checkout@v4
- uses: Gr1N/setup-poetry@v9
- run: poetry install
- run: poetry run flake8 .
- run: poetry run mypy .
- run: poetry run djlint .
docker:
runs-on: ubuntu-latest
container: docker
needs: [lint]
steps:
- run: apk add git nodejs
- uses: actions/checkout@v4
- uses: docker/metadata-action@v5
id: meta
with:
images: xefir/divent
tags: |
type=schedule
type=ref,event=tag
type=ref,event=pr
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: ${{ gitea.ref_name == 'master' || gitea.ref_type == 'tag' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
pypi:
runs-on: ubuntu-latest
container: python:3.12.3
needs: [lint]
if: gitea.ref_type == 'tag'
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.POETRY_PYPI_TOKEN_PYPI }}
steps:
- run: apt-get update
- run: apt-get install -y git nodejs
- uses: actions/checkout@v4
- uses: Gr1N/setup-poetry@v9
- run: poetry publish --build

View File

@ -1,10 +1,10 @@
FROM python:3.12.3 as build
FROM python:3.10.8-slim as build
WORKDIR /app
COPY . .
RUN pip install poetry && poetry build
FROM python:3.12.3
FROM python:3.10.8-slim
COPY --from=build /app/dist /tmp/dist
RUN pip install /tmp/dist/*.whl && rm -rf /tmp/dist

View File

@ -1,6 +1,7 @@
# Divent
> The discord scheduled event calendar generator
[![Build Status](https://ci.crystalyx.net/api/badges/Xefir/Divent/status.svg)](https://ci.crystalyx.net/Xefir/Divent)
[![Docker Hub](https://img.shields.io/docker/pulls/xefir/divent)](https://hub.docker.com/r/xefir/divent)
Simple website that guides you to invite a bot to read and format scheduled events to a subscribable calendar.

View File

View File

@ -1,3 +0,0 @@
from divent.bot import run
run()

View File

@ -2,25 +2,23 @@ import json
import logging
from datetime import datetime, timedelta
from functools import wraps
from os import getenv, path
from typing import Dict, Optional, Union
from os import environ, path
from typing import Dict, Optional
from disnake import Asset, Client, Guild, Intents, Member, User
from disnake.guild_scheduled_event import GuildScheduledEvent
from disnake import Client, Guild
from dotenv import load_dotenv
from ics import Calendar, ContentLine, Event
from ics.alarm import DisplayAlarm
from oauthlib.oauth2 import OAuth2Error
from quart import Quart, redirect, render_template, request, session, url_for
from requests_oauthlib import OAuth2Session # type: ignore
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware # type: ignore
load_dotenv()
DISCORD_TOKEN = getenv("DISCORD_TOKEN")
OAUTH2_CLIENT_ID = getenv("OAUTH2_CLIENT_ID")
OAUTH2_CLIENT_SECRET = getenv("OAUTH2_CLIENT_SECRET")
DISCORD_TOKEN = environ.get("DISCORD_TOKEN")
OAUTH2_CLIENT_ID = environ.get("OAUTH2_CLIENT_ID")
OAUTH2_CLIENT_SECRET = environ.get("OAUTH2_CLIENT_SECRET")
if not DISCORD_TOKEN:
raise Exception("Missing DISCORD_TOKEN")
if not OAUTH2_CLIENT_ID:
@ -28,58 +26,23 @@ if not OAUTH2_CLIENT_ID:
if not OAUTH2_CLIENT_SECRET:
raise Exception("Missing OAUTH2_CLIENT_SECRET")
QUART_DEBUG = getenv("QUART_DEBUG", False)
QUART_DEBUG = environ.get("QUART_DEBUG", False)
if QUART_DEBUG:
logging.basicConfig(level=logging.DEBUG)
API_BASE_URL = getenv("API_BASE_URL", "https://discordapp.com/api")
API_BASE_URL = environ.get("API_BASE_URL", "https://discordapp.com/api")
AUTHORIZATION_BASE_URL = f"{API_BASE_URL}/oauth2/authorize"
TOKEN_URL = f"{API_BASE_URL}/oauth2/token"
CATALOG_CACHE = {}
EVENTS_CACHE = {}
class Discord(Client):
async def on_ready(self):
print(f"Logged on as {self.user}!", flush=True)
for guild in self.guilds:
for scheduled_event in guild.scheduled_events:
EVENTS_CACHE[scheduled_event.id] = [
member.id
for member in await scheduled_event.fetch_users().flatten()
]
print("Events synchronised!", flush=True)
async def on_guild_scheduled_event_subscribe(
self, event: GuildScheduledEvent, user: Union[Member, User]
):
EVENTS_CACHE[event.id].append(user.id)
async def on_guild_scheduled_event_unsubscribe(
self, event: GuildScheduledEvent, user: Union[Member, User]
):
EVENTS_CACHE[event.id].remove(user.id)
async def on_guild_scheduled_event_create(self, event: GuildScheduledEvent):
EVENTS_CACHE[event.id] = [
member.id for member in await event.fetch_users().flatten()
]
async def on_guild_scheduled_event_delete(self, event: GuildScheduledEvent):
EVENTS_CACHE.pop(event.id)
intents = Intents.default()
intents.guild_scheduled_events = True
intents.members = True
client = Discord(intents=intents)
client = Discord()
app = Quart(__name__)
app.config["SECRET_KEY"] = OAUTH2_CLIENT_SECRET
app.config["EXPLAIN_TEMPLATE_LOADING"] = QUART_DEBUG
app.asgi_app = ProxyHeadersMiddleware(app.asgi_app, "*") # type: ignore
@ -91,6 +54,9 @@ def get_guild_by_id(guild_id: str) -> Optional[Guild]:
return None
CATALOG_CACHE = {}
@app.errorhandler(500)
async def errorhandler(error: Exception):
print(f"\33[31m{error}\33[m", flush=True)
@ -148,11 +114,17 @@ def days_before_failure() -> int:
return nextDelta.days
def cdn_avatar_url(user_id: int, hash: str) -> str:
ext = "gif" if hash.startswith("a_") else "png"
return f"https://cdn.discordapp.com/avatars/{user_id}/{hash}.{ext}"
@app.context_processor
def context_processor():
return dict(
_=i18n,
client=client,
cdn_avatar_url=cdn_avatar_url,
days_before_failure=days_before_failure(),
)
@ -188,34 +160,29 @@ async def callback():
if request_values.get("error"):
return errorhandler(request_values.get("error"))
try:
discord = make_session(state=session.get("oauth2_state"))
token = discord.fetch_token(
TOKEN_URL,
client_secret=OAUTH2_CLIENT_SECRET,
authorization_response=request.url,
)
token_updater(token)
except OAuth2Error as e:
return errorhandler(e)
discord = make_session(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(session.pop("redirect_url", url_for(".guilds")))
@app.route("/guilds")
@login_required
async def guilds():
guild = request.args.get("guild")
guild = get_guild_by_id(request.args.get("guild"))
if guild:
return redirect(url_for(".subscribe", entity_id=guild))
return redirect(
url_for(".subscribe", guild_id=guild.vanity_url_code or guild.id)
)
try:
discord = make_session(token=session.get("oauth2_token"))
user = discord.get(f"{API_BASE_URL}/users/@me").json()
user_guilds = discord.get(f"{API_BASE_URL}/users/@me/guilds").json()
except OAuth2Error:
return redirect(url_for(".login"))
discord = make_session(token=session.get("oauth2_token"))
user = discord.get(f"{API_BASE_URL}/users/@me").json()
user_guilds = discord.get(f"{API_BASE_URL}/users/@me/guilds").json()
common_guilds = []
for bot_guild in client.guilds:
@ -224,124 +191,68 @@ async def guilds():
common_guilds.append(bot_guild)
return await render_template(
"guilds.html.j2",
user=user,
avatar=Asset._from_avatar(None, user["id"], user["avatar"]),
common_guilds=common_guilds,
"guilds.html.j2", user=user, common_guilds=common_guilds
)
@app.route("/subscribe/<entity_id>")
@app.route("/subscribe/<guild_id>")
@login_required
async def subscribe(entity_id: str):
guild = get_guild_by_id(entity_id)
if guild:
try:
discord = make_session(token=session.get("oauth2_token"))
user_guilds = discord.get(f"{API_BASE_URL}/users/@me/guilds").json()
except OAuth2Error:
return redirect(url_for(".login"))
if not any(str(guild.id) == user_guild["id"] for user_guild in user_guilds):
return redirect(url_for(".login"))
return await render_template(
"subscribe.html.j2",
avatar=guild.icon,
entity_id=guild.vanity_url_code or guild.id,
)
try:
user = await client.get_or_fetch_user(int(entity_id))
except ValueError:
async def subscribe(guild_id: str):
guild = get_guild_by_id(guild_id)
if guild is None:
return redirect(url_for(".login"))
if user and str(user.id) == entity_id:
return await render_template(
"subscribe.html.j2", avatar=user.avatar, entity_id=user.id
)
discord = make_session(token=session.get("oauth2_token"))
user_guilds = discord.get(f"{API_BASE_URL}/users/@me/guilds").json()
return redirect(url_for(".login"))
def make_event(scheduled_event: GuildScheduledEvent) -> Event:
event = Event()
event.summary = scheduled_event.name
event.begin = scheduled_event.scheduled_start_time
event.end = scheduled_event.scheduled_end_time
event.duration = timedelta(hours=2)
event.uid = str(scheduled_event.id)
event.description = scheduled_event.description
event.url = scheduled_event.url
event.location = (
scheduled_event.entity_metadata.location
if scheduled_event.entity_metadata
else None
)
alarm = DisplayAlarm()
alarm.trigger = timedelta(hours=-1)
event.alarms.append(alarm)
return event
@app.route("/<entity_id>.ics")
async def ical(entity_id: str):
guild = get_guild_by_id(entity_id)
if guild:
calendar = Calendar()
calendar.extra.append(ContentLine(name="REFRESH-INTERVAL", value="PT1H"))
calendar.extra.append(ContentLine(name="X-PUBLISHED-TTL", value="PT1H"))
calendar.extra.append(ContentLine(name="NAME", value=guild.name))
calendar.extra.append(ContentLine(name="X-WR-CALNAME", value=guild.name))
if guild.description:
calendar.extra.append(
ContentLine(name="DESCRIPTION", value=guild.description)
)
calendar.extra.append(
ContentLine(name="X-WR-CALDESC", value=guild.description)
)
for scheduled_event in guild.scheduled_events:
event = make_event(scheduled_event)
calendar.events.append(event)
return calendar.serialize()
try:
user = await client.get_or_fetch_user(int(entity_id))
except ValueError:
if not any(str(guild.id) == user_guild["id"] for user_guild in user_guilds):
return redirect(url_for(".login"))
if user:
calendar = Calendar()
return await render_template("subscribe.html.j2", guild=guild)
calendar.extra.append(ContentLine(name="REFRESH-INTERVAL", value="PT1H"))
calendar.extra.append(ContentLine(name="X-PUBLISHED-TTL", value="PT1H"))
calendar.extra.append(ContentLine(name="NAME", value=client.user.display_name))
calendar.extra.append(
ContentLine(name="X-WR-CALNAME", value=client.user.display_name)
@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"))
calendar = Calendar()
calendar.extra.append(ContentLine(name="REFRESH-INTERVAL", value="PT1H"))
calendar.extra.append(ContentLine(name="X-PUBLISHED-TTL", value="PT1H"))
calendar.extra.append(ContentLine(name="NAME", value=guild.name))
calendar.extra.append(ContentLine(name="X-WR-CALNAME", value=guild.name))
if guild.description:
calendar.extra.append(ContentLine(name="DESCRIPTION", value=guild.description))
calendar.extra.append(ContentLine(name="X-WR-CALDESC", value=guild.description))
for scheduled_event in guild.scheduled_events:
event = Event()
event.summary = scheduled_event.name
event.begin = scheduled_event.scheduled_start_time
event.end = scheduled_event.scheduled_end_time
event.duration = timedelta(hours=2)
event.uid = str(scheduled_event.id)
event.description = scheduled_event.description
event.url = f"https://discord.com/events/{guild_id}/{scheduled_event.id}"
event.location = (
scheduled_event.entity_metadata.location
if scheduled_event.entity_metadata
else None
)
for guild in client.guilds:
for scheduled_event in guild.scheduled_events:
if user.id in EVENTS_CACHE[scheduled_event.id]:
event = make_event(scheduled_event)
calendar.events.append(event)
alarm = DisplayAlarm()
alarm.trigger = timedelta(hours=-1)
event.alarms.append(alarm)
return calendar.serialize()
calendar.events.append(event)
return redirect(url_for(".login"))
return calendar.serialize()
def run():
client.loop.create_task(client.start(DISCORD_TOKEN))
app.run("0.0.0.0", loop=client.loop)
quart_task = client.loop.create_task(app.run_task("0.0.0.0"))
quart_task.add_done_callback(lambda f: client.loop.stop())
client.run(DISCORD_TOKEN)

View File

View File

@ -6,11 +6,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<title>{{ client.user.display_name }} - {{ _("The discord scheduled event calendar generator") }}</title>
<title>{{ client.user.display_name }} - {{ _('The discord scheduled event calendar generator') }}</title>
<link rel="stylesheet"
href="{{ url_for('static', filename='css/font-awesome.min.css') }}" />
href="{{ url_for('static', filename='css/font-awesome.min.css') }}"/>
<link rel="stylesheet"
href="{{ url_for('static', filename='css/global.css') }}" />
href="{{ url_for('static', filename='css/global.css') }}"/>
</head>
<body>
<div id="content">

View File

@ -3,17 +3,17 @@
<div id="box">
<div id="avatars">
<img src="{{ url_for('static', filename='img/deadlink.png') }}"
alt="{{ _("Link is dead") }}"
alt="{{ _('Link is dead') }}"
height="179"
width="173" />
width="173"/>
</div>
<hr />
<span>{{ error }}</span>
</div>
<div id="buttons">
<a href="{{ url_for("index") }}">
<a href="{{ url_for('index') }}">
<i class="fa fa-arrow-left"></i>
{{ _("Back to the beginning") }}
{{ _('Back to the beginning') }}
</a>
</div>
{% endblock content %}

View File

@ -2,7 +2,7 @@
<li>
<a href="https://discord.com/users/133305654512320513" target="_blank">
<i class="fa fa-user-plus"></i>
{{ _("Add author on Discord") }}
{{ _('Add author on Discord') }}
</a>
</li>
<li>
@ -16,17 +16,17 @@
<li>
<a href="https://git.crystalyx.net/Xefir/Divent" target="_blank">
<i class="fa fa-code-fork"></i>
{{ _("View the source code") }}
{{ _('View the source code') }}
</a>
</li>
<li>
<a href="https://hub.docker.com/r/xefir/divent" target="_blank">
<i class="fa fa-cubes"></i>
{{ _("Host it yourself") }}
{{ _('Host it yourself') }}
</a>
</li>
<li>
<i class="fa fa-heartbeat"></i>
{{ _("Next castastrophic life failure in about %days% days") | replace('%days%', days_before_failure) }}
{{ _('Next castastrophic life failure in about %days% days') | replace('%days%', days_before_failure) }}
</li>
</ul>

View File

@ -4,30 +4,32 @@
<div id="box">
<div id="avatars">
<img src="{{ client.user.display_avatar }}"
alt="{{ _("Bot Logo") }}"
alt="{{ _('Bot Logo') }}"
width="80"
height="80" />
height="80"/>
<span id="dots">…</span>
<img src="{{ avatar.url }}"
alt="{{ _("User Avatar") }}"
<img src="{{ cdn_avatar_url(user.id, user.avatar) }}"
alt="{{ _('User Avatar') }}"
width="80"
height="80" />
height="80"/>
</div>
<h1>
<a href="{{ url_for(".index") }}">{{ client.user.display_name }}</a>
</h1>
<h2>{{ _("The discord scheduled event calendar generator") }}</h2>
<hr>
<a class="button" href="{{ url_for(".guilds", guild=user.id) }}">{{ _("For all your servers") }}</a>
<div class="hr-sect">{{ _("OR") }}</div>
<select name="guild" class="black_input" onchange="this.form.submit()">
<option>&nbsp;</option>
<h3>{{ _('Choose a server:') }}</h3>
<select name="guild" class="black_input">
<option>
&nbsp;
</option>
{% for guild in common_guilds %}
<option value="{{ guild.vanity_url_code|default(guild.id, True) }}">{{ guild.name }}</option>
<option value="{{ guild.vanity_url_code|default(guild.id, True) }}">
{{ guild.name }}
</option>
{% endfor %}
</select>
<div class="hr-sect">{{ _("OR") }}</div>
<a class="button"
target="_blank"
href="https://discord.com/api/oauth2/authorize?client_id={{ client.user.id }}&permissions=8589934592&scope=bot">
{{ _("Add the bot on your server") }}
</a>
@ -46,5 +48,8 @@
</li>
</ul>
</div>
<div id="buttons">
<input type="submit" class="button" value="{{ _("Let's go!") }}"/>
</div>
</form>
{% endblock content %}

View File

@ -4,24 +4,24 @@
<div id="box">
<div id="avatars">
<img src="{{ client.user.display_avatar }}"
alt="{{ _("Bot Logo") }}"
alt="{{ _('Bot Logo') }}"
width="80"
height="80" />
height="80"/>
</div>
<h1>
<a href="{{ url_for(".index") }}">{{ client.user.display_name }}</a>
</h1>
<h2>{{ _("The discord scheduled event calendar generator") }}</h2>
<h2>{{ _('The discord scheduled event calendar generator') }}</h2>
<hr />
<h3>{{ _("This will allow you to:") }}</h3>
<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, Outlook, Apple or any ICS complient software") }}
{{ _('Subscribe to a calendar on Google, 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") }}
{{ _('Throwing you to a new isekai world') }}
</li>
</ul>
</div>

View File

@ -3,22 +3,25 @@
<div id="box">
<div id="avatars">
<img src="{{ client.user.display_avatar }}"
alt="{{ _("Bot Logo") }}"
alt="{{ _('Bot Logo') }}"
width="80"
height="80" />
height="80"/>
<span id="dots">…</span>
<img src="{{ avatar.url }}" alt="{{ _("Avatar") }}" width="80" height="80" />
<img src="{{ guild.icon.url }}"
alt="{{ _('Guild Logo') }}"
width="80"
height="80"/>
</div>
<h1>
<a href="{{ url_for(".index") }}">{{ client.user.display_name }}</a>
</h1>
<h2>{{ _("The discord scheduled event calendar generator") }}</h2>
<h2>{{ _('The discord scheduled event calendar generator') }}</h2>
<hr />
<ul id="providers">
<li>
<a class="button"
target="_blank"
href="https://calendar.google.com/calendar/u/0/r?cid=webcal://{{ request.host }}/{{ entity_id }}.ics">
href="https://calendar.google.com/calendar/u/0/r?cid={{ request.host_url }}{{ guild.vanity_url_code|default(guild.id, True) }}.ics">
<i class="fa fa-google"></i>
{{ _("Subscribe to") }} Google
</a>
@ -26,14 +29,14 @@
<li>
<a class="button"
target="_blank"
href="https://outlook.live.com/owa?path=/calendar/action/compose&rru=addsubscription&url=webcal://{{ request.host }}/{{ entity_id }}.ics">
href="https://outlook.live.com/owa?path=/calendar/action/compose&rru=addsubscription&url={{ request.host_url }}{{ guild.vanity_url_code|default(guild.id, True) }}.ics">
<i class="fa fa-windows"></i>
{{ _("Subscribe to") }} Outlook
</a>
</li>
<li>
{# djlint:off #}
<a class="button" target="_blank" href="webcal://{{ request.host }}/{{ entity_id }}.ics">
<a class="button" target="_blank" href="webcal://{{ request.host }}/{{ guild.vanity_url_code|default(guild.id, True) }}.ics">
<i class="fa fa-apple"></i>
{{ _("Subscribe to") }} Apple
</a>
@ -44,7 +47,7 @@
<div>
<h3>{{ _("Use the direct link:") }}</h3>
{# djlint:off #}
<input type="text" readonly class="black_input" value="webcal://{{ request.host }}/{{ entity_id }}.ics"/>
<input type="text" readonly class="black_input" value="webcal://{{ request.host }}/{{ guild.vanity_url_code|default(guild.id, True) }}.ics"/>
{# djlint:on #}
</div>
</div>

View File

@ -5,7 +5,7 @@
"This will allow you to:": "Ceci te permettra de :",
"Subscribe to a calendar on Google, Outlook, Apple or any ICS complient software": "T'abonner à un calendrier sur Google, Outlook, Apple ou tout autre logiciel compatible",
"Throwing you to a new isekai world": "T'envoyer dans un monde fantaisiste armée d'une poêle à frire",
"For all your servers": "Pour tous tes serveurs",
"Choose a server:": "Choisi un serveur :",
"You must have": "Tu dois avoir la permission",
"Manage Server": "Gérer le serveur",
"permission on this server to perform this action": "sur ce serveur pour effectuer cette action",

2095
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +1,32 @@
[tool.poetry]
name = "divent"
version = "4.1.3"
version = "2.0.2"
description = "The discord scheduled event calendar generator"
authors = ["Xéfir Destiny <xefir@crystalyx.net>"]
authors = ["Xéfir Destiny"]
license = "WTFPL"
readme = "README.md"
homepage = "https://divent.crystalyx.net/"
repository = "https://git.crystalyx.net/Xefir/Divent"
[tool.poetry.scripts]
divent = 'divent.bot:run'
divent = 'divent.bot:__main__'
[tool.poetry.dependencies]
python = ">=3.8.1,<4.0.0"
disnake = "^2.9.1"
python = "^3.8"
disnake = "2.6.0"
ics = "0.8.0.dev0"
python-dotenv = "^1.0.1"
quart = "^0.19.4"
requests-oauthlib = "^2.0.0"
uvicorn = "^0.29.0"
python-dotenv = "0.21.0"
quart = "0.18.3"
requests-oauthlib = "1.3.1"
uvicorn = "0.18.3"
[tool.poetry.dev-dependencies]
black = "^24.2.0"
djlint = "^1.34.1"
flake8 = "^7.0.0"
flake8-alphabetize = "^0.0.21"
flake8-black = "^0.3.6"
flake8-pyproject = "^1.2.3"
mypy = "^1.8.0"
types-oauthlib = "^3.2.0"
[tool.flake8]
max-line-length = 88
black = "22.10.0"
djlint = "1.19.1"
flake8 = "5.0.4"
flake8-alphabetize = "0.0.17"
flake8-black = "0.3.3"
mypy = "0.982"
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@ -1,3 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"ignorePaths": [".drone.yml"]
}