Merge pull request 'v4: Personal calendar' (#168) from v4 into master
All checks were successful
divent / lint (push) Successful in 1m48s
divent / docker (push) Successful in 38s
divent / pypi (push) Successful in 1m15s

Reviewed-on: #168
This commit is contained in:
Michel Roux 2023-11-07 00:40:57 +00:00
commit 38bf85d255
11 changed files with 150 additions and 114 deletions

View File

@ -1,4 +1,4 @@
FROM python:3.11.5 as build
FROM python:3.11.6 as build
WORKDIR /app
COPY . .

View File

@ -5,7 +5,8 @@ from functools import wraps
from os import getenv, path
from typing import Dict, Optional
from disnake import Client, Guild
from disnake import Asset, Client, Guild
from disnake.guild_scheduled_event import GuildScheduledEvent
from dotenv import load_dotenv
from ics import Calendar, ContentLine, Event
from ics.alarm import DisplayAlarm
@ -116,17 +117,11 @@ 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(),
)
@ -179,12 +174,10 @@ async def callback():
@app.route("/guilds")
@login_required
async def guilds():
guild = get_guild_by_id(request.args.get("guild"))
guild = request.args.get("guild")
if guild:
return redirect(
url_for(".subscribe", guild_id=guild.vanity_url_code or guild.id)
)
return redirect(url_for(".subscribe", entity_id=guild))
try:
discord = make_session(token=session.get("oauth2_token"))
@ -200,69 +193,120 @@ async def guilds():
common_guilds.append(bot_guild)
return await render_template(
"guilds.html.j2", user=user, common_guilds=common_guilds
"guilds.html.j2",
user=user,
avatar=Asset._from_avatar(None, user["id"], user["avatar"]),
common_guilds=common_guilds,
)
@app.route("/subscribe/<guild_id>")
@app.route("/subscribe/<entity_id>")
@login_required
async def subscribe(guild_id: str):
guild = get_guild_by_id(guild_id)
if guild is None:
return redirect(url_for(".login"))
async def subscribe(entity_id: str):
guild = get_guild_by_id(entity_id)
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 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"))
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", guild=guild)
@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
return await render_template(
"subscribe.html.j2",
avatar=guild.icon,
entity_id=guild.vanity_url_code or guild.id,
)
alarm = DisplayAlarm()
alarm.trigger = timedelta(hours=-1)
event.alarms.append(alarm)
user = await client.get_or_fetch_user(int(entity_id))
calendar.events.append(event)
if user and str(user.id) == entity_id:
return await render_template(
"subscribe.html.j2", avatar=user.avatar, entity_id=user.id
)
return calendar.serialize()
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()
user = await client.get_or_fetch_user(int(entity_id))
if user:
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=client.user.display_name))
calendar.extra.append(
ContentLine(name="X-WR-CALNAME", value=client.user.display_name)
)
for guild in client.guilds:
if await guild.get_or_fetch_member(int(entity_id)):
for scheduled_event in guild.scheduled_events:
if user.id in [
member.id
for member in await scheduled_event.fetch_users().flatten()
]:
event = make_event(scheduled_event)
calendar.events.append(event)
return calendar.serialize()
return redirect(url_for(".login"))
def __main__():

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,32 +4,30 @@
<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="{{ cdn_avatar_url(user.id, user.avatar) }}"
alt="{{ _('User Avatar') }}"
<img src="{{ avatar.url }}"
alt="{{ _("User Avatar") }}"
width="80"
height="80"/>
height="80" />
</div>
<h1>
<a href="{{ url_for(".index") }}">{{ client.user.display_name }}</a>
</h1>
<h3>{{ _('Choose a server:') }}</h3>
<select name="guild" class="black_input">
<option>
&nbsp;
</option>
<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>
{% 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>
@ -48,8 +46,5 @@
</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,25 +3,22 @@
<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="{{ guild.icon.url }}"
alt="{{ _('Guild Logo') }}"
width="80"
height="80"/>
<img src="{{ avatar.url }}" alt="{{ _("Avatar") }}" 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 }}/{{ guild.vanity_url_code|default(guild.id, True) }}.ics">
href="https://calendar.google.com/calendar/u/0/r?cid=webcal://{{ request.host }}/{{ entity_id }}.ics">
<i class="fa fa-google"></i>
{{ _("Subscribe to") }} Google
</a>
@ -29,14 +26,14 @@
<li>
<a class="button"
target="_blank"
href="https://outlook.live.com/owa?path=/calendar/action/compose&rru=addsubscription&url=webcal://{{ request.host }}/{{ guild.vanity_url_code|default(guild.id, True) }}.ics">
href="https://outlook.live.com/owa?path=/calendar/action/compose&rru=addsubscription&url=webcal://{{ request.host }}/{{ entity_id }}.ics">
<i class="fa fa-windows"></i>
{{ _("Subscribe to") }} Outlook
</a>
</li>
<li>
{# djlint:off #}
<a class="button" target="_blank" href="webcal://{{ request.host }}/{{ guild.vanity_url_code|default(guild.id, True) }}.ics">
<a class="button" target="_blank" href="webcal://{{ request.host }}/{{ entity_id }}.ics">
<i class="fa fa-apple"></i>
{{ _("Subscribe to") }} Apple
</a>
@ -47,7 +44,7 @@
<div>
<h3>{{ _("Use the direct link:") }}</h3>
{# djlint:off #}
<input type="text" readonly class="black_input" value="webcal://{{ request.host }}/{{ guild.vanity_url_code|default(guild.id, True) }}.ics"/>
<input type="text" readonly class="black_input" value="webcal://{{ request.host }}/{{ entity_id }}.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",
"Choose a server:": "Choisi un serveur :",
"For all your servers": "Pour tous tes serveurs",
"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",

4
poetry.lock generated
View File

@ -1649,5 +1649,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.8.1"
content-hash = "e4463ad986a3f8fafe36af53b741fe71dd1a78d65efb3e4240ac422688fca367"
python-versions = ">=3.8.1,<3.12"
content-hash = "30113574429e0ed0539ebe2850264eb819fb2a7af92310402c9eccbca33487ed"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "divent"
version = "3.4.4"
version = "4.0.0"
description = "The discord scheduled event calendar generator"
authors = ["Xéfir Destiny <xefir@crystalyx.net>"]
license = "WTFPL"
@ -12,7 +12,7 @@ repository = "https://git.crystalyx.net/Xefir/Divent"
divent = 'divent.bot:__main__'
[tool.poetry.dependencies]
python = "^3.8.1"
python = ">=3.8.1,<3.12"
disnake = "^2.9.1"
ics = "0.8.0.dev0"
python-dotenv = "^1.0.0"