mirror of
https://github.com/nexy7574/LCC-bot.git
synced 2024-09-19 10:03:40 +01:00
Make embeds pretty & reformat
This commit is contained in:
parent
4ca6b7f1d9
commit
e26a91d437
22 changed files with 432 additions and 639 deletions
|
@ -3,18 +3,19 @@ import sqlite3
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import config
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
import config
|
|
||||||
from utils import (
|
from utils import (
|
||||||
Assignments,
|
Assignments,
|
||||||
Tutors,
|
|
||||||
simple_embed_paginator,
|
|
||||||
get_or_none,
|
|
||||||
Student,
|
|
||||||
hyperlink,
|
|
||||||
console,
|
|
||||||
SelectAssigneesView,
|
SelectAssigneesView,
|
||||||
|
Student,
|
||||||
|
Tutors,
|
||||||
|
console,
|
||||||
|
get_or_none,
|
||||||
|
hyperlink,
|
||||||
|
simple_embed_paginator,
|
||||||
)
|
)
|
||||||
|
|
||||||
BOOL_EMOJI = {True: "\N{white heavy check mark}", False: "\N{cross mark}"}
|
BOOL_EMOJI = {True: "\N{white heavy check mark}", False: "\N{cross mark}"}
|
||||||
|
|
169
cogs/events.py
169
cogs/events.py
|
@ -1,3 +1,5 @@
|
||||||
|
import asyncio
|
||||||
|
import glob
|
||||||
import hashlib
|
import hashlib
|
||||||
import inspect
|
import inspect
|
||||||
import io
|
import io
|
||||||
|
@ -5,21 +7,22 @@ import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import asyncio
|
|
||||||
import textwrap
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
import glob
|
|
||||||
import warnings
|
import warnings
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple, Dict, Any
|
from typing import Any, Dict, Optional, Tuple
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import httpx
|
import httpx
|
||||||
from discord.ext import commands, pages, tasks
|
from bs4 import BeautifulSoup
|
||||||
from utils import Student, get_or_none, console
|
|
||||||
from config import guilds
|
from config import guilds
|
||||||
|
from discord.ext import commands, pages, tasks
|
||||||
|
|
||||||
|
from utils import Student, console, get_or_none
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from config import dev
|
from config import dev
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -29,8 +32,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
OAUTH_REDIRECT_URI = None
|
OAUTH_REDIRECT_URI = None
|
||||||
try:
|
try:
|
||||||
from config import GITHUB_USERNAME
|
from config import GITHUB_PASSWORD, GITHUB_USERNAME
|
||||||
from config import GITHUB_PASSWORD
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
GITHUB_USERNAME = None
|
GITHUB_USERNAME = None
|
||||||
GITHUB_PASSWORD = None
|
GITHUB_PASSWORD = None
|
||||||
|
@ -143,16 +145,12 @@ class Events(commands.Cog):
|
||||||
_re = re.match(
|
_re = re.match(
|
||||||
r"https://github\.com/(?P<repo>[a-zA-Z0-9-]+/[\w.-]+)/blob/(?P<path>[^#>]+)(\?[^#>]+)?"
|
r"https://github\.com/(?P<repo>[a-zA-Z0-9-]+/[\w.-]+)/blob/(?P<path>[^#>]+)(\?[^#>]+)?"
|
||||||
r"(#L(?P<start_line>\d+)(([-~:]|(\.\.))L(?P<end_line>\d+))?)",
|
r"(#L(?P<start_line>\d+)(([-~:]|(\.\.))L(?P<end_line>\d+))?)",
|
||||||
message.content
|
message.content,
|
||||||
)
|
)
|
||||||
if _re:
|
if _re:
|
||||||
branch, path = _re.group("path").split("/", 1)
|
branch, path = _re.group("path").split("/", 1)
|
||||||
_p = Path(path).suffix
|
_p = Path(path).suffix
|
||||||
url = RAW_URL.format(
|
url = RAW_URL.format(repo=_re.group("repo"), branch=branch, path=path)
|
||||||
repo=_re.group("repo"),
|
|
||||||
branch=branch,
|
|
||||||
path=path
|
|
||||||
)
|
|
||||||
if all((GITHUB_PASSWORD, GITHUB_USERNAME)):
|
if all((GITHUB_PASSWORD, GITHUB_USERNAME)):
|
||||||
auth = (GITHUB_USERNAME, GITHUB_PASSWORD)
|
auth = (GITHUB_USERNAME, GITHUB_PASSWORD)
|
||||||
else:
|
else:
|
||||||
|
@ -180,7 +178,7 @@ class Events(commands.Cog):
|
||||||
RAW_URL = "https://github.com/{repo}/archive/refs/heads/{branch}.zip"
|
RAW_URL = "https://github.com/{repo}/archive/refs/heads/{branch}.zip"
|
||||||
_full_re = re.finditer(
|
_full_re = re.finditer(
|
||||||
r"https://github\.com/(?P<repo>[a-zA-Z0-9-]+/[\w.-]+)(/tree/(?P<branch>[^#>]+))?\.(git|zip)",
|
r"https://github\.com/(?P<repo>[a-zA-Z0-9-]+/[\w.-]+)(/tree/(?P<branch>[^#>]+))?\.(git|zip)",
|
||||||
message.content
|
message.content,
|
||||||
)
|
)
|
||||||
for _match in _full_re:
|
for _match in _full_re:
|
||||||
repo = _match.group("repo")
|
repo = _match.group("repo")
|
||||||
|
@ -206,11 +204,7 @@ class Events(commands.Cog):
|
||||||
await message.edit(suppress=True)
|
await message.edit(suppress=True)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_voice_state_update(
|
async def on_voice_state_update(self, member: discord.Member, *_):
|
||||||
self,
|
|
||||||
member: discord.Member,
|
|
||||||
*_
|
|
||||||
):
|
|
||||||
me_voice = member.guild.me.voice
|
me_voice = member.guild.me.voice
|
||||||
if me_voice is None or me_voice.channel is None or member.guild.voice_client is None:
|
if me_voice is None or me_voice.channel is None or member.guild.voice_client is None:
|
||||||
return
|
return
|
||||||
|
@ -240,16 +234,14 @@ class Events(commands.Cog):
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
await message.channel.trigger_typing()
|
await message.channel.trigger_typing()
|
||||||
await message.reply(
|
await message.reply(
|
||||||
"I'd play the song but discord's voice servers are shit.",
|
"I'd play the song but discord's voice servers are shit.", file=discord.File(_file)
|
||||||
file=discord.File(_file)
|
|
||||||
)
|
)
|
||||||
region = message.author.voice.channel.rtc_region
|
region = message.author.voice.channel.rtc_region
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
console.log(
|
console.log(
|
||||||
"Timed out connecting to voice channel: {0.name} in {0.guild.name} "
|
"Timed out connecting to voice channel: {0.name} in {0.guild.name} "
|
||||||
"(region {1})".format(
|
"(region {1})".format(
|
||||||
message.author.voice.channel,
|
message.author.voice.channel, region.name if region else "auto (unknown)"
|
||||||
region.name if region else "auto (unknown)"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -268,29 +260,23 @@ class Events(commands.Cog):
|
||||||
)
|
)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
console.log(f"Error playing audio: {err}")
|
console.log(f"Error playing audio: {err}")
|
||||||
self.bot.loop.create_task(
|
self.bot.loop.create_task(message.add_reaction("\N{speaker with cancellation stroke}"))
|
||||||
message.add_reaction("\N{speaker with cancellation stroke}")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.bot.loop.create_task(
|
self.bot.loop.create_task(
|
||||||
message.remove_reaction("\N{speaker with three sound waves}", self.bot.user)
|
message.remove_reaction("\N{speaker with three sound waves}", self.bot.user)
|
||||||
)
|
)
|
||||||
self.bot.loop.create_task(
|
self.bot.loop.create_task(message.add_reaction("\N{speaker}"))
|
||||||
message.add_reaction("\N{speaker}")
|
|
||||||
)
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
src = discord.FFmpegPCMAudio(str(_file.resolve()), stderr=subprocess.DEVNULL)
|
src = discord.FFmpegPCMAudio(str(_file.resolve()), stderr=subprocess.DEVNULL)
|
||||||
src = discord.PCMVolumeTransformer(src, volume=0.5)
|
src = discord.PCMVolumeTransformer(src, volume=0.5)
|
||||||
voice.play(
|
voice.play(src, after=after)
|
||||||
src,
|
|
||||||
after=after
|
|
||||||
)
|
|
||||||
if message.channel.permissions_for(message.guild.me).add_reactions:
|
if message.channel.permissions_for(message.guild.me).add_reactions:
|
||||||
await message.add_reaction("\N{speaker with three sound waves}")
|
await message.add_reaction("\N{speaker with three sound waves}")
|
||||||
else:
|
else:
|
||||||
await message.channel.trigger_typing()
|
await message.channel.trigger_typing()
|
||||||
await message.reply(file=discord.File(_file))
|
await message.reply(file=discord.File(_file))
|
||||||
|
|
||||||
return internal
|
return internal
|
||||||
|
|
||||||
async def send_what():
|
async def send_what():
|
||||||
|
@ -305,29 +291,18 @@ class Events(commands.Cog):
|
||||||
await message.reply("You really are deaf, aren't you.")
|
await message.reply("You really are deaf, aren't you.")
|
||||||
elif not msg.content:
|
elif not msg.content:
|
||||||
await message.reply(
|
await message.reply(
|
||||||
"Maybe *I* need to get my hearing checked, I have no idea what {} said.".format(
|
"Maybe *I* need to get my hearing checked, I have no idea what {} said.".format(msg.author.mention)
|
||||||
msg.author.mention
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = "{0.author.mention} said '{0.content}', you deaf sod.".format(
|
text = "{0.author.mention} said '{0.content}', you deaf sod.".format(msg)
|
||||||
msg
|
_content = textwrap.shorten(text, width=2000, placeholder="[...]")
|
||||||
)
|
|
||||||
_content = textwrap.shorten(
|
|
||||||
text, width=2000, placeholder="[...]"
|
|
||||||
)
|
|
||||||
await message.reply(_content, allowed_mentions=discord.AllowedMentions.none())
|
await message.reply(_content, allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
|
||||||
def get_sloc_count():
|
def get_sloc_count():
|
||||||
root = Path.cwd()
|
root = Path.cwd()
|
||||||
root_files = list(root.glob("**/*.py"))
|
root_files = list(root.glob("**/*.py"))
|
||||||
root_files.append(root / "main.py")
|
root_files.append(root / "main.py")
|
||||||
root_files = list(
|
root_files = list(filter(lambda f: "venv" not in f.parents and "venv" not in f.parts, root_files))
|
||||||
filter(
|
|
||||||
lambda f: "venv" not in f.parents and "venv" not in f.parts,
|
|
||||||
root_files
|
|
||||||
)
|
|
||||||
)
|
|
||||||
lines = 0
|
lines = 0
|
||||||
|
|
||||||
for file in root_files:
|
for file in root_files:
|
||||||
|
@ -361,10 +336,10 @@ class Events(commands.Cog):
|
||||||
"content_type": a.content_type,
|
"content_type": a.content_type,
|
||||||
}
|
}
|
||||||
for a in message.attachments
|
for a in message.attachments
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
if message.author.discriminator != "0":
|
if message.author.discriminator != "0":
|
||||||
payload["author"] += '#%s' % message.author.discriminator
|
payload["author"] += "#%s" % message.author.discriminator
|
||||||
if message.author != self.bot.user and (payload["content"] or payload["attachments"]):
|
if message.author != self.bot.user and (payload["content"] or payload["attachments"]):
|
||||||
await self.bot.bridge_queue.put(payload)
|
await self.bot.bridge_queue.put(payload)
|
||||||
|
|
||||||
|
@ -388,78 +363,48 @@ class Events(commands.Cog):
|
||||||
},
|
},
|
||||||
r"it just works": {
|
r"it just works": {
|
||||||
"func": play_voice(assets / "it-just-works.ogg"),
|
"func": play_voice(assets / "it-just-works.ogg"),
|
||||||
"meta": {
|
"meta": {"check": (assets / "it-just-works.ogg").exists},
|
||||||
"check": (assets / "it-just-works.ogg").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"count to (3|three)": {
|
"count to (3|three)": {
|
||||||
"func": play_voice(assets / "count-to-three.ogg"),
|
"func": play_voice(assets / "count-to-three.ogg"),
|
||||||
"meta": {
|
"meta": {"check": (assets / "count-to-three.ogg").exists},
|
||||||
"check": (assets / "count-to-three.ogg").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"^linux$": {
|
r"^linux$": {
|
||||||
"content": lambda: (assets / "copypasta.txt").read_text(),
|
"content": lambda: (assets / "copypasta.txt").read_text(),
|
||||||
"meta": {
|
"meta": {"needs_mention": True, "check": (assets / "copypasta.txt").exists},
|
||||||
"needs_mention": True,
|
|
||||||
"check": (assets / "copypasta.txt").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"carat": {
|
r"carat": {
|
||||||
"file": discord.File(assets / "carat.jpg"),
|
"file": discord.File(assets / "carat.jpg"),
|
||||||
"delete_after": None,
|
"delete_after": None,
|
||||||
"meta": {
|
"meta": {"check": (assets / "carat.jpg").exists},
|
||||||
"check": (assets / "carat.jpg").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"(lupupa|fuck(ed)? the hell out\W*)": {
|
r"(lupupa|fuck(ed)? the hell out\W*)": {
|
||||||
"file": discord.File(assets / "lupupa.jpg"),
|
"file": discord.File(assets / "lupupa.jpg"),
|
||||||
"meta": {
|
"meta": {"check": (assets / "lupupa.jpg").exists},
|
||||||
"check": (assets / "lupupa.jpg").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"[s5]+(m)+[e3]+[g9]+": {
|
r"[s5]+(m)+[e3]+[g9]+": {
|
||||||
# "func": send_smeg,
|
# "func": send_smeg,
|
||||||
"file": lambda: discord.File(random.choice(list((assets / "smeg").iterdir()))),
|
"file": lambda: discord.File(random.choice(list((assets / "smeg").iterdir()))),
|
||||||
"delete_after": 30,
|
"delete_after": 30,
|
||||||
"meta": {
|
"meta": {"sub": {r"pattern": r"([-_.\s\u200b])+", r"with": ""}, "check": (assets / "smeg").exists},
|
||||||
"sub": {
|
|
||||||
r"pattern": r"([-_.\s\u200b])+",
|
|
||||||
r"with": ''
|
|
||||||
},
|
|
||||||
"check": (assets / "smeg").exists
|
|
||||||
}
|
|
||||||
},
|
|
||||||
r"(what|huh)(\?|!)*$": {
|
|
||||||
"func": send_what,
|
|
||||||
"meta": {
|
|
||||||
"check": lambda: message.reference is not None
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
r"(what|huh)(\?|!)*$": {"func": send_what, "meta": {"check": lambda: message.reference is not None}},
|
||||||
("year", "linux", "desktop"): {
|
("year", "linux", "desktop"): {
|
||||||
"content": lambda: "%s will be the year of the GNU+Linux desktop." % datetime.now().year,
|
"content": lambda: "%s will be the year of the GNU+Linux desktop." % datetime.now().year,
|
||||||
"delete_after": None
|
"delete_after": None,
|
||||||
},
|
},
|
||||||
r"mine(ing|d)? (diamonds|away)": {
|
r"mine(ing|d)? (diamonds|away)": {
|
||||||
"func": play_voice(assets / "mine-diamonds.ogg"),
|
"func": play_voice(assets / "mine-diamonds.ogg"),
|
||||||
"meta": {
|
"meta": {"check": (assets / "mine-diamonds.ogg").exists},
|
||||||
"check": (assets / "mine-diamonds.ogg").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"v[ei]r[mg]in(\sme(d|m[a]?)ia\W*)?(\W\w*\W*)?$": {
|
r"v[ei]r[mg]in(\sme(d|m[a]?)ia\W*)?(\W\w*\W*)?$": {
|
||||||
"content": "Get virgin'd",
|
"content": "Get virgin'd",
|
||||||
"file": lambda: discord.File(
|
"file": lambda: discord.File(random.choice(list(Path(assets / "virgin").iterdir()))),
|
||||||
random.choice(list(Path(assets / 'virgin').iterdir()))
|
"meta": {"check": (assets / "virgin").exists},
|
||||||
),
|
|
||||||
"meta": {
|
|
||||||
"check": (assets / 'virgin').exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"richard|(dick\W*$)": {
|
r"richard|(dick\W*$)": {
|
||||||
"file": discord.File(assets / "visio.png"),
|
"file": discord.File(assets / "visio.png"),
|
||||||
"meta": {
|
"meta": {"check": (assets / "visio.png").exists},
|
||||||
"check": (assets / "visio.png").exists
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
r"thank(\syou|s)(,)? jimmy": {
|
r"thank(\syou|s)(,)? jimmy": {
|
||||||
"content": "You're welcome, %s!" % message.author.mention,
|
"content": "You're welcome, %s!" % message.author.mention,
|
||||||
|
@ -467,18 +412,12 @@ class Events(commands.Cog):
|
||||||
r"(ok )?jimmy (we|i) g[eo]t it": {
|
r"(ok )?jimmy (we|i) g[eo]t it": {
|
||||||
"content": "No need to be so rude! Cunt.",
|
"content": "No need to be so rude! Cunt.",
|
||||||
},
|
},
|
||||||
r"c(mon|ome on) jimmy": {
|
r"c(mon|ome on) jimmy": {"content": "IM TRYING"},
|
||||||
"content": "IM TRYING"
|
r"(bor(r)?is|johnson)": {"file": discord.File(assets / "boris.jpeg")},
|
||||||
},
|
|
||||||
r"(bor(r)?is|johnson)": {
|
|
||||||
"file": discord.File(assets / "boris.jpeg")
|
|
||||||
},
|
|
||||||
r"\W?(s(ource\w)?)?l(ines\s)?o(f\s)?c(ode)?(\W)?$": {
|
r"\W?(s(ource\w)?)?l(ines\s)?o(f\s)?c(ode)?(\W)?$": {
|
||||||
"content": lambda: "I have {:,} lines of source code across {:,} files!".format(*get_sloc_count())
|
"content": lambda: "I have {:,} lines of source code across {:,} files!".format(*get_sloc_count())
|
||||||
},
|
},
|
||||||
r"t(ry\s)?i(t\s)?a(nd\s)?see.*": {
|
r"t(ry\s)?i(t\s)?a(nd\s)?see.*": {"content": "https://tryitands.ee"},
|
||||||
"content": "https://tryitands.ee"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
# Stop responding to any bots
|
# Stop responding to any bots
|
||||||
if message.author.bot is True:
|
if message.author.bot is True:
|
||||||
|
@ -515,11 +454,7 @@ class Events(commands.Cog):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if meta.get("sub") is not None and isinstance(meta["sub"], dict):
|
if meta.get("sub") is not None and isinstance(meta["sub"], dict):
|
||||||
content = re.sub(
|
content = re.sub(meta["sub"]["pattern"], meta["sub"]["with"], message.content)
|
||||||
meta["sub"]["pattern"],
|
|
||||||
meta["sub"]["with"],
|
|
||||||
message.content
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
content = message.content
|
content = message.content
|
||||||
|
|
||||||
|
@ -566,7 +501,7 @@ class Events(commands.Cog):
|
||||||
r"mpreg|lupupa|\U0001fac3": "\U0001fac3", # mpreg
|
r"mpreg|lupupa|\U0001fac3": "\U0001fac3", # mpreg
|
||||||
r"(trans(gender)?($|\W+)|%s)" % T_EMOJI: T_EMOJI, # trans
|
r"(trans(gender)?($|\W+)|%s)" % T_EMOJI: T_EMOJI, # trans
|
||||||
r"gay|%s" % G_EMOJI: G_EMOJI,
|
r"gay|%s" % G_EMOJI: G_EMOJI,
|
||||||
r"(femboy|trans(gender)?($|\W+))": C_EMOJI
|
r"(femboy|trans(gender)?($|\W+))": C_EMOJI,
|
||||||
}
|
}
|
||||||
if message.channel.permissions_for(message.guild.me).add_reactions:
|
if message.channel.permissions_for(message.guild.me).add_reactions:
|
||||||
is_naus = random.randint(1, 100) == 32
|
is_naus = random.randint(1, 100) == 32
|
||||||
|
@ -588,9 +523,7 @@ class Events(commands.Cog):
|
||||||
if channel is None or not channel.can_send(discord.Embed()):
|
if channel is None or not channel.can_send(discord.Embed()):
|
||||||
warnings.warn("Cannot send to spam channel, disabling feed fetcher")
|
warnings.warn("Cannot send to spam channel, disabling feed fetcher")
|
||||||
return
|
return
|
||||||
headers = {
|
headers = {"User-Agent": f"python-httpx/{httpx.__version__} (Like Akregator/5.22.3); syndication"}
|
||||||
"User-Agent": f"python-httpx/{httpx.__version__} (Like Akregator/5.22.3); syndication"
|
|
||||||
}
|
|
||||||
|
|
||||||
file = Path.home() / ".cache" / "lcc-bot" / "discord.atom"
|
file = Path.home() / ".cache" / "lcc-bot" / "discord.atom"
|
||||||
if not file.exists():
|
if not file.exists():
|
||||||
|
@ -675,22 +608,18 @@ class Events(commands.Cog):
|
||||||
content = f"[open on discordstatus.com (too large to display)]({entry.link['href']})"
|
content = f"[open on discordstatus.com (too large to display)]({entry.link['href']})"
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=title,
|
title=title, description=content, color=colour, url=entry.link["href"], timestamp=updated
|
||||||
description=content,
|
|
||||||
color=colour,
|
|
||||||
url=entry.link["href"],
|
|
||||||
timestamp=updated
|
|
||||||
)
|
)
|
||||||
embed.set_author(
|
embed.set_author(
|
||||||
name="Discord Status",
|
name="Discord Status",
|
||||||
url="https://discordstatus.com/",
|
url="https://discordstatus.com/",
|
||||||
icon_url="https://raw.githubusercontent.com/EEKIM10/LCC-bot/"
|
icon_url="https://raw.githubusercontent.com/EEKIM10/LCC-bot/"
|
||||||
"fe0cb6dd932f9fc2cb0a26433aff8e4cce19279a/assets/discord.png"
|
"fe0cb6dd932f9fc2cb0a26433aff8e4cce19279a/assets/discord.png",
|
||||||
)
|
)
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text="Published: {} | Updated: {}".format(
|
text="Published: {} | Updated: {}".format(
|
||||||
datetime.fromisoformat(entry.find("published").text).strftime("%Y-%m-%d %H:%M:%S"),
|
datetime.fromisoformat(entry.find("published").text).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
updated.strftime("%Y-%m-%d %H:%M:%S")
|
updated.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
# You have been warned - this file is very EXTREME!
|
# You have been warned - this file is very EXTREME!
|
||||||
import discord
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import io
|
import io
|
||||||
import numpy
|
|
||||||
import blend_modes
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from discord.ext import commands
|
|
||||||
|
import blend_modes
|
||||||
|
import discord
|
||||||
|
import numpy
|
||||||
import PIL.Image
|
import PIL.Image
|
||||||
|
from discord.ext import commands
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def _overlay_images(
|
def _overlay_images(
|
||||||
background: PIL.Image.Image,
|
background: PIL.Image.Image, foreground: PIL.Image.Image, mode=blend_modes.overlay, opacity: float = 1.0
|
||||||
foreground: PIL.Image.Image,
|
|
||||||
mode=blend_modes.overlay,
|
|
||||||
opacity: float = 1.0
|
|
||||||
) -> PIL.Image.Image:
|
) -> PIL.Image.Image:
|
||||||
background = background.convert("RGBA")
|
background = background.convert("RGBA")
|
||||||
foreground = foreground.convert("RGBA")
|
foreground = foreground.convert("RGBA")
|
||||||
|
|
20
cogs/info.py
20
cogs/info.py
|
@ -1,8 +1,10 @@
|
||||||
import discord
|
import discord
|
||||||
import httpx
|
import httpx
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from utils import get_or_none
|
from utils import get_or_none
|
||||||
from utils.db import AccessTokens
|
from utils.db import AccessTokens
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from config import OAUTH_ID, OAUTH_REDIRECT_URI
|
from config import OAUTH_ID, OAUTH_REDIRECT_URI
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -51,7 +53,7 @@ class InfoCog(commands.Cog):
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="You must link your account first!",
|
title="You must link your account first!",
|
||||||
description="Don't worry, [I only store your IP information. And the access token.](%s)" % url,
|
description="Don't worry, [I only store your IP information. And the access token.](%s)" % url,
|
||||||
url=url
|
url=url,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
user_data = await self.get_user_info(user.access_token)
|
user_data = await self.get_user_info(user.access_token)
|
||||||
|
@ -61,8 +63,20 @@ class InfoCog(commands.Cog):
|
||||||
title="Your info",
|
title="Your info",
|
||||||
)
|
)
|
||||||
if user_data:
|
if user_data:
|
||||||
for field in ("bot", "system", "mfa_enabled", "banner", "accent_color", "mfa_enabled", "locale",
|
for field in (
|
||||||
"verified", "email", "flags", "premium_type", "public_flags"):
|
"bot",
|
||||||
|
"system",
|
||||||
|
"mfa_enabled",
|
||||||
|
"banner",
|
||||||
|
"accent_color",
|
||||||
|
"mfa_enabled",
|
||||||
|
"locale",
|
||||||
|
"verified",
|
||||||
|
"email",
|
||||||
|
"flags",
|
||||||
|
"premium_type",
|
||||||
|
"public_flags",
|
||||||
|
):
|
||||||
user_data.setdefault(field, None)
|
user_data.setdefault(field, None)
|
||||||
lines = [
|
lines = [
|
||||||
"ID: {0[id]}",
|
"ID: {0[id]}",
|
||||||
|
|
27
cogs/mod.py
27
cogs/mod.py
|
@ -1,13 +1,15 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Sized
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from utils import Student, get_or_none, BannedStudentID, owner_or_admin, JimmyBans
|
|
||||||
from typing import Sized
|
from utils import BannedStudentID, JimmyBans, Student, get_or_none, owner_or_admin
|
||||||
|
|
||||||
|
|
||||||
class LimitedList(list):
|
class LimitedList(list):
|
||||||
"""FIFO Limited list"""
|
"""FIFO Limited list"""
|
||||||
|
|
||||||
def __init__(self, iterable: Sized = None, size: int = 5000):
|
def __init__(self, iterable: Sized = None, size: int = 5000):
|
||||||
if iterable:
|
if iterable:
|
||||||
assert len(iterable) <= size, "Initial iterable too big."
|
assert len(iterable) <= size, "Initial iterable too big."
|
||||||
|
@ -131,7 +133,7 @@ class Mod(commands.Cog):
|
||||||
message: discord.Message
|
message: discord.Message
|
||||||
if message.author == user:
|
if message.author == user:
|
||||||
query_ = query.lower() if query else None
|
query_ = query.lower() if query else None
|
||||||
content_ = str(message.clean_content or '').lower()
|
content_ = str(message.clean_content or "").lower()
|
||||||
if query_ is not None and (query_ in content_ or content_ in query_):
|
if query_ is not None and (query_ in content_ or content_ in query_):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -144,22 +146,15 @@ class Mod(commands.Cog):
|
||||||
colour=message.author.colour,
|
colour=message.author.colour,
|
||||||
timestamp=message.created_at,
|
timestamp=message.created_at,
|
||||||
fields=[
|
fields=[
|
||||||
discord.EmbedField(
|
discord.EmbedField("Attachment count", str(len(message.attachments)), False),
|
||||||
"Attachment count",
|
discord.EmbedField("Location", str(message.channel.mention)),
|
||||||
str(len(message.attachments)),
|
|
||||||
False
|
|
||||||
),
|
|
||||||
discord.EmbedField(
|
|
||||||
"Location",
|
|
||||||
str(message.channel.mention)
|
|
||||||
),
|
|
||||||
discord.EmbedField(
|
discord.EmbedField(
|
||||||
"Times",
|
"Times",
|
||||||
f"Created: {discord.utils.format_dt(message.created_at, 'R')} | Edited: "
|
f"Created: {discord.utils.format_dt(message.created_at, 'R')} | Edited: "
|
||||||
f"{'None' if message.edited_at is None else discord.utils.format_dt(message.edited_at, 'R')}"
|
f"{'None' if message.edited_at is None else discord.utils.format_dt(message.edited_at, 'R')}",
|
||||||
)
|
),
|
||||||
]
|
],
|
||||||
).set_author(name=message.author.display_name, icon_url=message.author.display_avatar.url)
|
).set_author(name=message.author.display_name, icon_url=message.author.display_avatar.url),
|
||||||
]
|
]
|
||||||
await ctx.reply(embeds=embeds)
|
await ctx.reply(embeds=embeds)
|
||||||
|
|
||||||
|
|
279
cogs/other.py
279
cogs/other.py
|
@ -1,14 +1,13 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import glob
|
import glob
|
||||||
import hashlib
|
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -16,24 +15,22 @@ import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import dns.resolver
|
|
||||||
import httpx
|
|
||||||
from dns import asyncresolver
|
|
||||||
import aiofiles
|
|
||||||
import pyttsx3
|
|
||||||
from time import time, time_ns, sleep
|
|
||||||
from typing import Literal
|
|
||||||
from typing import Tuple, Optional, Dict
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from time import sleep, time, time_ns
|
||||||
|
from typing import Dict, Literal, Optional, Tuple
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from PIL import Image
|
|
||||||
import pytesseract
|
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
|
import dns.resolver
|
||||||
|
import httpx
|
||||||
import psutil
|
import psutil
|
||||||
|
import pytesseract
|
||||||
|
import pyttsx3
|
||||||
from discord.ext import commands, pages
|
from discord.ext import commands, pages
|
||||||
|
from dns import asyncresolver
|
||||||
|
from PIL import Image
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.common.exceptions import WebDriverException
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
@ -41,7 +38,8 @@ from selenium.webdriver.chrome.options import Options as ChromeOptions
|
||||||
from selenium.webdriver.chrome.service import Service as ChromeService
|
from selenium.webdriver.chrome.service import Service as ChromeService
|
||||||
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
||||||
from selenium.webdriver.firefox.service import Service as FirefoxService
|
from selenium.webdriver.firefox.service import Service as FirefoxService
|
||||||
from utils import console, Timer
|
|
||||||
|
from utils import Timer, console
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from config import proxy
|
from config import proxy
|
||||||
|
@ -138,13 +136,12 @@ class OtherCog(commands.Cog):
|
||||||
"noprogress": True,
|
"noprogress": True,
|
||||||
"logger": NullLogger(),
|
"logger": NullLogger(),
|
||||||
"paths": {"home": tempdir, "temp": tempdir},
|
"paths": {"home": tempdir, "temp": tempdir},
|
||||||
"cookiefile": Path(__file__).parent.parent / "jimmy-cookies.txt"
|
"cookiefile": Path(__file__).parent.parent / "jimmy-cookies.txt",
|
||||||
}
|
}
|
||||||
) as downloader:
|
) as downloader:
|
||||||
try:
|
try:
|
||||||
info = await self.bot.loop.run_in_executor(
|
info = await self.bot.loop.run_in_executor(
|
||||||
None,
|
None, partial(downloader.extract_info, url, download=False)
|
||||||
partial(downloader.extract_info, url, download=False)
|
|
||||||
)
|
)
|
||||||
except yt_dlp.utils.DownloadError:
|
except yt_dlp.utils.DownloadError:
|
||||||
return {}
|
return {}
|
||||||
|
@ -157,8 +154,8 @@ class OtherCog(commands.Cog):
|
||||||
"acodec": fmt.get("acodec", "?"),
|
"acodec": fmt.get("acodec", "?"),
|
||||||
"vcodec": fmt.get("vcodec", "?"),
|
"vcodec": fmt.get("vcodec", "?"),
|
||||||
"resolution": fmt.get("resolution", "?x?"),
|
"resolution": fmt.get("resolution", "?x?"),
|
||||||
"filesize": fmt.get("filesize", float('inf')),
|
"filesize": fmt.get("filesize", float("inf")),
|
||||||
"format": fmt.get("format", '?'),
|
"format": fmt.get("format", "?"),
|
||||||
}
|
}
|
||||||
for fmt in info["formats"]
|
for fmt in info["formats"]
|
||||||
}
|
}
|
||||||
|
@ -820,10 +817,11 @@ class OtherCog(commands.Cog):
|
||||||
f"* URL: <{friendly_url}>\n"
|
f"* URL: <{friendly_url}>\n"
|
||||||
f"* Load time: {fetch_time:.2f}ms\n"
|
f"* Load time: {fetch_time:.2f}ms\n"
|
||||||
f"* Screenshot render time: {screenshot_time:.2f}ms\n"
|
f"* Screenshot render time: {screenshot_time:.2f}ms\n"
|
||||||
f"* Total time: {(fetch_time + screenshot_time):.2f}ms\n" +
|
f"* Total time: {(fetch_time + screenshot_time):.2f}ms\n"
|
||||||
(
|
+ (
|
||||||
'* Probability of being scat or something else horrifying: 100%'
|
"* Probability of being scat or something else horrifying: 100%"
|
||||||
if ctx.user.id == 1019233057519177778 else ''
|
if ctx.user.id == 1019233057519177778
|
||||||
|
else ""
|
||||||
),
|
),
|
||||||
file=screenshot,
|
file=screenshot,
|
||||||
)
|
)
|
||||||
|
@ -882,20 +880,13 @@ class OtherCog(commands.Cog):
|
||||||
async def yt_dl_2(
|
async def yt_dl_2(
|
||||||
self,
|
self,
|
||||||
ctx: discord.ApplicationContext,
|
ctx: discord.ApplicationContext,
|
||||||
url: discord.Option(
|
url: discord.Option(description="The URL to download.", type=str),
|
||||||
description="The URL to download.",
|
|
||||||
type=str
|
|
||||||
),
|
|
||||||
_format: discord.Option(
|
_format: discord.Option(
|
||||||
name="format",
|
name="format", description="The format to download.", type=str, autocomplete=format_autocomplete, default=""
|
||||||
description="The format to download.",
|
|
||||||
type=str,
|
|
||||||
autocomplete=format_autocomplete,
|
|
||||||
default=""
|
|
||||||
) = "",
|
) = "",
|
||||||
extract_audio: bool = False,
|
extract_audio: bool = False,
|
||||||
cookies_txt: discord.Attachment = None,
|
cookies_txt: discord.Attachment = None,
|
||||||
disable_filesize_buffer: bool = False
|
disable_filesize_buffer: bool = False,
|
||||||
):
|
):
|
||||||
"""Downloads a video using youtube-dl"""
|
"""Downloads a video using youtube-dl"""
|
||||||
cookies = io.StringIO()
|
cookies = io.StringIO()
|
||||||
|
@ -903,6 +894,7 @@ class OtherCog(commands.Cog):
|
||||||
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
formats = await self.list_formats(url)
|
formats = await self.list_formats(url)
|
||||||
if _format:
|
if _format:
|
||||||
_fmt = _format
|
_fmt = _format
|
||||||
|
@ -925,7 +917,7 @@ class OtherCog(commands.Cog):
|
||||||
stdout = tempdir / "stdout.txt"
|
stdout = tempdir / "stdout.txt"
|
||||||
stderr = tempdir / "stderr.txt"
|
stderr = tempdir / "stderr.txt"
|
||||||
|
|
||||||
default_cookies_txt = (Path.cwd() / "jimmy-cookies.txt")
|
default_cookies_txt = Path.cwd() / "jimmy-cookies.txt"
|
||||||
real_cookies_txt = tempdir / "cookies.txt"
|
real_cookies_txt = tempdir / "cookies.txt"
|
||||||
if cookies_txt is not None:
|
if cookies_txt is not None:
|
||||||
await cookies_txt.save(fp=real_cookies_txt)
|
await cookies_txt.save(fp=real_cookies_txt)
|
||||||
|
@ -988,11 +980,11 @@ class OtherCog(commands.Cog):
|
||||||
"acodec:opus",
|
"acodec:opus",
|
||||||
"acodec:vorbis",
|
"acodec:vorbis",
|
||||||
"vcodec:vp8",
|
"vcodec:vp8",
|
||||||
"ext"
|
"ext",
|
||||||
],
|
],
|
||||||
"merge_output_format": "webm/mp4/mov/flv/avi/ogg/m4a/wav/mp3/opus/mka/mkv",
|
"merge_output_format": "webm/mp4/mov/flv/avi/ogg/m4a/wav/mp3/opus/mka/mkv",
|
||||||
"source_address": "0.0.0.0",
|
"source_address": "0.0.0.0",
|
||||||
"cookiefile": str(real_cookies_txt.resolve().absolute())
|
"cookiefile": str(real_cookies_txt.resolve().absolute()),
|
||||||
}
|
}
|
||||||
description = ""
|
description = ""
|
||||||
proxy_url = "socks5://localhost:1090"
|
proxy_url = "socks5://localhost:1090"
|
||||||
|
@ -1000,23 +992,26 @@ class OtherCog(commands.Cog):
|
||||||
proxy_down = await self.check_proxy("socks5://localhost:1090")
|
proxy_down = await self.check_proxy("socks5://localhost:1090")
|
||||||
if proxy_down > 0:
|
if proxy_down > 0:
|
||||||
if proxy_down == 1:
|
if proxy_down == 1:
|
||||||
description += ":warning: (SHRoNK) Proxy check leaked IP - trying backup proxy\n"
|
description += ":warning: (SHRoNK) Proxy check leaked IP - trying backup proxy.\n"
|
||||||
elif proxy_down == 2:
|
elif proxy_down == 2:
|
||||||
description += ":warning: (SHRoNK) Proxy connection failed - trying backup proxy\n"
|
description += ":warning: (SHRoNK) Proxy connection failed - trying backup proxy.\n"
|
||||||
else:
|
else:
|
||||||
description += ":warning: (SHRoNK) Unknown proxy error - trying backup proxy\n"
|
description += ":warning: (SHRoNK) Unknown proxy error - trying backup proxy.\n"
|
||||||
|
|
||||||
proxy_down = await self.check_proxy("socks5://localhost:1080")
|
proxy_down = await self.check_proxy("socks5://localhost:1080")
|
||||||
if proxy_down > 0:
|
if proxy_down > 0:
|
||||||
if proxy_down == 1:
|
if proxy_down == 1:
|
||||||
description += ":warning: (NexBox) Proxy check leaked IP.\n"
|
description += ":warning: (NexBox) Proxy check leaked IP..\n"
|
||||||
elif proxy_down == 2:
|
elif proxy_down == 2:
|
||||||
description += ":warning: (NexBox) Proxy connection failed\n"
|
description += ":warning: (NexBox) Proxy connection failed.\n"
|
||||||
else:
|
else:
|
||||||
description += ":warning: (NexBox) Unknown proxy error\n"
|
description += ":warning: (NexBox) Unknown proxy error.\n"
|
||||||
proxy_url = None
|
proxy_url = None
|
||||||
else:
|
else:
|
||||||
proxy_url = "socks5://localhost:1080"
|
proxy_url = "socks5://localhost:1080"
|
||||||
|
description += "\N{white heavy check mark} Using fallback NexBox proxy."
|
||||||
|
else:
|
||||||
|
description += "\N{white heavy check mark} Using the SHRoNK proxy."
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
description += f":warning: Failed to check proxy (`{e}`). Going unproxied."
|
description += f":warning: Failed to check proxy (`{e}`). Going unproxied."
|
||||||
|
@ -1024,11 +1019,7 @@ class OtherCog(commands.Cog):
|
||||||
args["proxy"] = proxy_url
|
args["proxy"] = proxy_url
|
||||||
if extract_audio:
|
if extract_audio:
|
||||||
args["postprocessors"] = [
|
args["postprocessors"] = [
|
||||||
{
|
{"key": "FFmpegExtractAudio", "preferredquality": "24", "preferredcodec": "opus"}
|
||||||
"key": "FFmpegExtractAudio",
|
|
||||||
"preferredquality": "48",
|
|
||||||
"preferredcodec": "opus"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
args["format"] = args["format"] or f"(ba/b)[filesize<={MAX_SIZE_MB}M]/ba/b"
|
args["format"] = args["format"] or f"(ba/b)[filesize<={MAX_SIZE_MB}M]/ba/b"
|
||||||
|
|
||||||
|
@ -1037,11 +1028,22 @@ class OtherCog(commands.Cog):
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(args) as downloader:
|
with yt_dlp.YoutubeDL(args) as downloader:
|
||||||
try:
|
try:
|
||||||
await ctx.respond(
|
extracted_info = downloader.extract_info(url, download=False)
|
||||||
embed=discord.Embed(
|
except yt_dlp.utils.DownloadError:
|
||||||
title="Downloading...", description=description, colour=discord.Colour.blurple()
|
pass
|
||||||
)
|
else:
|
||||||
|
title = extracted_info.get("title", url)
|
||||||
|
thumbnail_url = extracted_info.get("thumbnail") or discord.Embed.Empty
|
||||||
|
webpage_url = extracted_info.get("webpage_url")
|
||||||
|
try:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Downloading %r..." % title,
|
||||||
|
description=description,
|
||||||
|
colour=discord.Colour.blurple(),
|
||||||
|
url=webpage_url,
|
||||||
)
|
)
|
||||||
|
embed.set_thumbnail(url=thumbnail_url)
|
||||||
|
await ctx.respond(embed=embed)
|
||||||
await self.bot.loop.run_in_executor(None, partial(downloader.download, [url]))
|
await self.bot.loop.run_in_executor(None, partial(downloader.download, [url]))
|
||||||
except yt_dlp.utils.DownloadError as e:
|
except yt_dlp.utils.DownloadError as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
@ -1049,27 +1051,26 @@ class OtherCog(commands.Cog):
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Error",
|
title="Error",
|
||||||
description=f"Download failed:\n```\n{e}\n```",
|
description=f"Download failed:\n```\n{e}\n```",
|
||||||
colour=discord.Colour.red()
|
colour=discord.Colour.red(),
|
||||||
|
url=webpage_url,
|
||||||
),
|
),
|
||||||
delete_after=60
|
delete_after=60,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
parsed_qs = parse_qs(url)
|
parsed_qs = parse_qs(url)
|
||||||
if 't' in parsed_qs and parsed_qs['t'] and parsed_qs['t'][0].isdigit():
|
if "t" in parsed_qs and parsed_qs["t"] and parsed_qs["t"][0].isdigit():
|
||||||
# Assume is timestamp
|
# Assume is timestamp
|
||||||
timestamp = round(float(parsed_qs['t'][0]))
|
timestamp = round(float(parsed_qs["t"][0]))
|
||||||
end_timestamp = None
|
end_timestamp = None
|
||||||
if len(parsed_qs["t"]) >= 2:
|
if len(parsed_qs["t"]) >= 2:
|
||||||
end_timestamp = round(float(parsed_qs['t'][1]))
|
end_timestamp = round(float(parsed_qs["t"][1]))
|
||||||
if end_timestamp < timestamp:
|
if end_timestamp < timestamp:
|
||||||
end_timestamp, timestamp = reversed(
|
end_timestamp, timestamp = reversed((end_timestamp, timestamp))
|
||||||
(end_timestamp, timestamp)
|
|
||||||
)
|
|
||||||
_end = "to %s" % end_timestamp if len(parsed_qs["t"]) == 2 else "onward"
|
_end = "to %s" % end_timestamp if len(parsed_qs["t"]) == 2 else "onward"
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Trimming...",
|
title="Trimming...",
|
||||||
description=f"Trimming from {timestamp} seconds {_end}.\nThis may take a while.",
|
description=f"Trimming from {timestamp} seconds {_end}.\nThis may take a while.",
|
||||||
colour=discord.Colour.blurple()
|
colour=discord.Colour.blurple(),
|
||||||
)
|
)
|
||||||
await ctx.edit(embed=embed)
|
await ctx.edit(embed=embed)
|
||||||
for file in tempdir.glob("%s-*" % ctx.user.id):
|
for file in tempdir.glob("%s-*" % ctx.user.id):
|
||||||
|
@ -1087,7 +1088,7 @@ class OtherCog(commands.Cog):
|
||||||
"-y",
|
"-y",
|
||||||
"-c",
|
"-c",
|
||||||
"copy",
|
"copy",
|
||||||
str(file)
|
str(file),
|
||||||
]
|
]
|
||||||
if end_timestamp is not None:
|
if end_timestamp is not None:
|
||||||
minutes, seconds = divmod(end_timestamp, 60)
|
minutes, seconds = divmod(end_timestamp, 60)
|
||||||
|
@ -1096,13 +1097,7 @@ class OtherCog(commands.Cog):
|
||||||
_args.insert(6, "{!s}:{!s}:{!s}".format(*map(round, (hours, minutes, seconds))))
|
_args.insert(6, "{!s}:{!s}:{!s}".format(*map(round, (hours, minutes, seconds))))
|
||||||
|
|
||||||
await self.bot.loop.run_in_executor(
|
await self.bot.loop.run_in_executor(
|
||||||
None,
|
None, partial(subprocess.run, _args, check=True, capture_output=True)
|
||||||
partial(
|
|
||||||
subprocess.run,
|
|
||||||
_args,
|
|
||||||
check=True,
|
|
||||||
capture_output=True
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
bak.unlink(True)
|
bak.unlink(True)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
@ -1111,16 +1106,15 @@ class OtherCog(commands.Cog):
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Error",
|
title="Error",
|
||||||
description=f"Trimming failed:\n```\n{e}\n```",
|
description=f"Trimming failed:\n```\n{e}\n```",
|
||||||
colour=discord.Colour.red()
|
colour=discord.Colour.red(),
|
||||||
),
|
),
|
||||||
delete_after=30
|
delete_after=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Downloaded!",
|
title="Downloaded %r!" % title, description="", colour=discord.Colour.green(), url=webpage_url
|
||||||
description="",
|
|
||||||
colour=discord.Colour.green()
|
|
||||||
)
|
)
|
||||||
|
embed.set_thumbnail(url=thumbnail_url)
|
||||||
del logger
|
del logger
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
|
@ -1135,13 +1129,15 @@ class OtherCog(commands.Cog):
|
||||||
while st_r > 1024:
|
while st_r > 1024:
|
||||||
st_r /= 1024
|
st_r /= 1024
|
||||||
units.pop(0)
|
units.pop(0)
|
||||||
embed.description += "\N{warning sign}\ufe0f {} is too large to upload ({!s}{}" \
|
embed.description += (
|
||||||
|
"\N{warning sign}\ufe0f {} is too large to upload ({!s}{}"
|
||||||
", max is {}MB).\n".format(
|
", max is {}MB).\n".format(
|
||||||
file.name,
|
file.name,
|
||||||
round(st_r, 2),
|
round(st_r, 2),
|
||||||
units[0],
|
units[0],
|
||||||
REAL_MAX_SIZE_MB,
|
REAL_MAX_SIZE_MB,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
files.append(discord.File(file, file.name))
|
files.append(discord.File(file, file.name))
|
||||||
|
@ -1149,13 +1145,13 @@ class OtherCog(commands.Cog):
|
||||||
|
|
||||||
if not files:
|
if not files:
|
||||||
embed.description += "No files to upload. Directory list:\n%s" % (
|
embed.description += "No files to upload. Directory list:\n%s" % (
|
||||||
"\n".join(r'\* ' + f.name for f in tempdir.iterdir())
|
"\n".join(r"\* " + f.name for f in tempdir.iterdir())
|
||||||
)
|
)
|
||||||
return await ctx.edit(embed=embed)
|
return await ctx.edit(embed=embed)
|
||||||
else:
|
else:
|
||||||
_desc = embed.description
|
_desc = embed.description
|
||||||
embed.description += f"Uploading {len(files)} file(s):\n%s" % (
|
embed.description += f"Uploading {len(files)} file(s):\n%s" % (
|
||||||
"\n".join('* `%s`' % f.filename for f in files)
|
"\n".join("* `%s`" % f.filename for f in files)
|
||||||
)
|
)
|
||||||
await ctx.edit(embed=embed)
|
await ctx.edit(embed=embed)
|
||||||
await ctx.channel.trigger_typing()
|
await ctx.channel.trigger_typing()
|
||||||
|
@ -1172,6 +1168,7 @@ class OtherCog(commands.Cog):
|
||||||
await ctx.edit(embed=None)
|
await ctx.edit(embed=None)
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.bot.loop.create_task(bgtask())
|
self.bot.loop.create_task(bgtask())
|
||||||
|
|
||||||
@commands.slash_command(name="text-to-mp3")
|
@commands.slash_command(name="text-to-mp3")
|
||||||
|
@ -1179,18 +1176,13 @@ class OtherCog(commands.Cog):
|
||||||
async def text_to_mp3(
|
async def text_to_mp3(
|
||||||
self,
|
self,
|
||||||
ctx: discord.ApplicationContext,
|
ctx: discord.ApplicationContext,
|
||||||
speed: discord.Option(
|
speed: discord.Option(int, "The speed of the voice. Default is 150.", required=False, default=150),
|
||||||
int,
|
|
||||||
"The speed of the voice. Default is 150.",
|
|
||||||
required=False,
|
|
||||||
default=150
|
|
||||||
),
|
|
||||||
voice: discord.Option(
|
voice: discord.Option(
|
||||||
str,
|
str,
|
||||||
"The voice to use. Some may cause timeout.",
|
"The voice to use. Some may cause timeout.",
|
||||||
autocomplete=discord.utils.basic_autocomplete(VOICES),
|
autocomplete=discord.utils.basic_autocomplete(VOICES),
|
||||||
default="default"
|
default="default",
|
||||||
)
|
),
|
||||||
):
|
):
|
||||||
"""Converts text to MP3. 5 uses per 10 minutes."""
|
"""Converts text to MP3. 5 uses per 10 minutes."""
|
||||||
if voice not in VOICES:
|
if voice not in VOICES:
|
||||||
|
@ -1207,9 +1199,9 @@ class OtherCog(commands.Cog):
|
||||||
placeholder="Enter text to read",
|
placeholder="Enter text to read",
|
||||||
min_length=1,
|
min_length=1,
|
||||||
max_length=4000,
|
max_length=4000,
|
||||||
style=discord.InputTextStyle.long
|
style=discord.InputTextStyle.long,
|
||||||
),
|
),
|
||||||
title="Convert text to an MP3"
|
title="Convert text to an MP3",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def callback(self, interaction: discord.Interaction):
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
@ -1261,9 +1253,7 @@ class OtherCog(commands.Cog):
|
||||||
_msg = await interaction.followup.send("Downloading text...")
|
_msg = await interaction.followup.send("Downloading text...")
|
||||||
try:
|
try:
|
||||||
response = await _self.http.get(
|
response = await _self.http.get(
|
||||||
_url,
|
_url, headers={"User-Agent": "Mozilla/5.0"}, follow_redirects=True
|
||||||
headers={"User-Agent": "Mozilla/5.0"},
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
await _msg.edit(content=f"Failed to download text. Status code: {response.status_code}")
|
await _msg.edit(content=f"Failed to download text. Status code: {response.status_code}")
|
||||||
|
@ -1291,10 +1281,7 @@ class OtherCog(commands.Cog):
|
||||||
start_time = time()
|
start_time = time()
|
||||||
task = _bot.loop.create_task(assurance_task())
|
task = _bot.loop.create_task(assurance_task())
|
||||||
try:
|
try:
|
||||||
mp3, size = await asyncio.wait_for(
|
mp3, size = await asyncio.wait_for(_bot.loop.run_in_executor(None, _convert, text_pre), timeout=600)
|
||||||
_bot.loop.run_in_executor(None, _convert, text_pre),
|
|
||||||
timeout=600
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
await _msg.edit(content="Failed to convert text to MP3 - Timeout. Try shorter/less complex text.")
|
await _msg.edit(content="Failed to convert text to MP3 - Timeout. Try shorter/less complex text.")
|
||||||
|
@ -1323,10 +1310,7 @@ class OtherCog(commands.Cog):
|
||||||
fn += word + "-"
|
fn += word + "-"
|
||||||
fn = fn[:-1]
|
fn = fn[:-1]
|
||||||
fn = fn[:28]
|
fn = fn[:28]
|
||||||
await _msg.edit(
|
await _msg.edit(content="Here's your MP3!", file=discord.File(mp3, filename=fn + ".mp3"))
|
||||||
content="Here's your MP3!",
|
|
||||||
file=discord.File(mp3, filename=fn + ".mp3")
|
|
||||||
)
|
|
||||||
|
|
||||||
await ctx.send_modal(TextModal())
|
await ctx.send_modal(TextModal())
|
||||||
|
|
||||||
|
@ -1335,7 +1319,7 @@ class OtherCog(commands.Cog):
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
@commands.max_concurrency(1, commands.BucketType.user)
|
||||||
async def quote(self, ctx: discord.ApplicationContext):
|
async def quote(self, ctx: discord.ApplicationContext):
|
||||||
"""Generates a random quote"""
|
"""Generates a random quote"""
|
||||||
emoji = discord.PartialEmoji(name='loading', animated=True, id=1101463077586735174)
|
emoji = discord.PartialEmoji(name="loading", animated=True, id=1101463077586735174)
|
||||||
|
|
||||||
async def get_quote() -> str | discord.File:
|
async def get_quote() -> str | discord.File:
|
||||||
try:
|
try:
|
||||||
|
@ -1358,10 +1342,7 @@ class OtherCog(commands.Cog):
|
||||||
|
|
||||||
class GenerateNewView(discord.ui.View):
|
class GenerateNewView(discord.ui.View):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(timeout=300, disable_on_timeout=True)
|
||||||
timeout=300,
|
|
||||||
disable_on_timeout=True
|
|
||||||
)
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
self.disable_all_items()
|
self.disable_all_items()
|
||||||
|
@ -1381,7 +1362,7 @@ class OtherCog(commands.Cog):
|
||||||
@discord.ui.button(
|
@discord.ui.button(
|
||||||
label="New Quote",
|
label="New Quote",
|
||||||
style=discord.ButtonStyle.green,
|
style=discord.ButtonStyle.green,
|
||||||
emoji=discord.PartialEmoji.from_str("\U000023ed\U0000fe0f")
|
emoji=discord.PartialEmoji.from_str("\U000023ed\U0000fe0f"),
|
||||||
)
|
)
|
||||||
async def new_quote(self, _, interaction: discord.Interaction):
|
async def new_quote(self, _, interaction: discord.Interaction):
|
||||||
await interaction.response.defer(invisible=True)
|
await interaction.response.defer(invisible=True)
|
||||||
|
@ -1394,9 +1375,7 @@ class OtherCog(commands.Cog):
|
||||||
return await followup.edit(content=new_result, view=GenerateNewView())
|
return await followup.edit(content=new_result, view=GenerateNewView())
|
||||||
|
|
||||||
@discord.ui.button(
|
@discord.ui.button(
|
||||||
label="Regenerate",
|
label="Regenerate", style=discord.ButtonStyle.blurple, emoji=discord.PartialEmoji.from_str("\U0001f504")
|
||||||
style=discord.ButtonStyle.blurple,
|
|
||||||
emoji=discord.PartialEmoji.from_str("\U0001f504")
|
|
||||||
)
|
)
|
||||||
async def regenerate(self, _, interaction: discord.Interaction):
|
async def regenerate(self, _, interaction: discord.Interaction):
|
||||||
await interaction.response.defer(invisible=True)
|
await interaction.response.defer(invisible=True)
|
||||||
|
@ -1406,7 +1385,7 @@ class OtherCog(commands.Cog):
|
||||||
return await interaction.followup.send(
|
return await interaction.followup.send(
|
||||||
"\N{cross mark} Message is starred and cannot be regenerated. You can press "
|
"\N{cross mark} Message is starred and cannot be regenerated. You can press "
|
||||||
"'New Quote' to generate a new quote instead.",
|
"'New Quote' to generate a new quote instead.",
|
||||||
ephemeral=True
|
ephemeral=True,
|
||||||
)
|
)
|
||||||
new_result = await get_quote()
|
new_result = await get_quote()
|
||||||
if isinstance(new_result, discord.File):
|
if isinstance(new_result, discord.File):
|
||||||
|
@ -1414,11 +1393,7 @@ class OtherCog(commands.Cog):
|
||||||
else:
|
else:
|
||||||
return await interaction.edit_original_response(content=new_result)
|
return await interaction.edit_original_response(content=new_result)
|
||||||
|
|
||||||
@discord.ui.button(
|
@discord.ui.button(label="Delete", style=discord.ButtonStyle.red, emoji="\N{wastebasket}\U0000fe0f")
|
||||||
label="Delete",
|
|
||||||
style=discord.ButtonStyle.red,
|
|
||||||
emoji="\N{wastebasket}\U0000fe0f"
|
|
||||||
)
|
|
||||||
async def delete(self, _, interaction: discord.Interaction):
|
async def delete(self, _, interaction: discord.Interaction):
|
||||||
await interaction.response.defer(invisible=True)
|
await interaction.response.defer(invisible=True)
|
||||||
await interaction.delete_original_response()
|
await interaction.delete_original_response()
|
||||||
|
@ -1440,8 +1415,7 @@ class OtherCog(commands.Cog):
|
||||||
attachment: discord.Option(
|
attachment: discord.Option(
|
||||||
discord.SlashCommandOptionType.attachment,
|
discord.SlashCommandOptionType.attachment,
|
||||||
description="Image to perform OCR on",
|
description="Image to perform OCR on",
|
||||||
)
|
),
|
||||||
|
|
||||||
):
|
):
|
||||||
"""OCRs an image"""
|
"""OCRs an image"""
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -1468,13 +1442,8 @@ class OtherCog(commands.Cog):
|
||||||
response = await self.http.put(
|
response = await self.http.put(
|
||||||
"https://api.mystb.in/paste",
|
"https://api.mystb.in/paste",
|
||||||
json={
|
json={
|
||||||
"files": [
|
"files": [{"filename": "ocr.txt", "content": text}],
|
||||||
{
|
},
|
||||||
"filename": "ocr.txt",
|
|
||||||
"content": text
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError:
|
||||||
|
@ -1484,7 +1453,7 @@ class OtherCog(commands.Cog):
|
||||||
with Timer(timings, "Respond (URL)"):
|
with Timer(timings, "Respond (URL)"):
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
description="View on [mystb.in](%s)" % ("https://mystb.in/" + data["id"]),
|
description="View on [mystb.in](%s)" % ("https://mystb.in/" + data["id"]),
|
||||||
colour=discord.Colour.dark_theme()
|
colour=discord.Colour.dark_theme(),
|
||||||
)
|
)
|
||||||
await ctx.respond(embed=embed)
|
await ctx.respond(embed=embed)
|
||||||
timings["Upload text to mystbin"] = _t.total
|
timings["Upload text to mystbin"] = _t.total
|
||||||
|
@ -1541,11 +1510,7 @@ class OtherCog(commands.Cog):
|
||||||
@commands.cooldown(1, 180, commands.BucketType.user)
|
@commands.cooldown(1, 180, commands.BucketType.user)
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
@commands.max_concurrency(1, commands.BucketType.user)
|
||||||
async def sherlock(
|
async def sherlock(
|
||||||
self,
|
self, ctx: discord.ApplicationContext, username: str, search_nsfw: bool = False, use_tor: bool = False
|
||||||
ctx: discord.ApplicationContext,
|
|
||||||
username: str,
|
|
||||||
search_nsfw: bool = False,
|
|
||||||
use_tor: bool = False
|
|
||||||
):
|
):
|
||||||
"""Sherlocks a username."""
|
"""Sherlocks a username."""
|
||||||
# git clone https://github.com/sherlock-project/sherlock.git && cd sherlock && docker build -t sherlock .
|
# git clone https://github.com/sherlock-project/sherlock.git && cd sherlock && docker build -t sherlock .
|
||||||
|
@ -1563,11 +1528,9 @@ class OtherCog(commands.Cog):
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Sherlocking username %s" % chars[n % 4],
|
title="Sherlocking username %s" % chars[n % 4],
|
||||||
description=f"Elapsed: {elapsed:.0f}s",
|
description=f"Elapsed: {elapsed:.0f}s",
|
||||||
colour=discord.Colour.dark_theme()
|
colour=discord.Colour.dark_theme(),
|
||||||
)
|
|
||||||
await ctx.edit(
|
|
||||||
embed=embed
|
|
||||||
)
|
)
|
||||||
|
await ctx.edit(embed=embed)
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -1582,9 +1545,10 @@ class OtherCog(commands.Cog):
|
||||||
"-v",
|
"-v",
|
||||||
f"{tempdir}:/opt/sherlock/results",
|
f"{tempdir}:/opt/sherlock/results",
|
||||||
"sherlock",
|
"sherlock",
|
||||||
"--folderoutput", "/opt/sherlock/results",
|
"--folderoutput",
|
||||||
|
"/opt/sherlock/results",
|
||||||
"--print-found",
|
"--print-found",
|
||||||
"--csv"
|
"--csv",
|
||||||
]
|
]
|
||||||
if search_nsfw:
|
if search_nsfw:
|
||||||
command.append("--nsfw")
|
command.append("--nsfw")
|
||||||
|
@ -1647,6 +1611,7 @@ class OtherCog(commands.Cog):
|
||||||
@discord.guild_only()
|
@discord.guild_only()
|
||||||
async def opusinate(self, ctx: discord.ApplicationContext, file: discord.Attachment, size_mb: float = 8):
|
async def opusinate(self, ctx: discord.ApplicationContext, file: discord.Attachment, size_mb: float = 8):
|
||||||
"""Converts the given file into opus with the given size."""
|
"""Converts the given file into opus with the given size."""
|
||||||
|
|
||||||
def humanise(v: int) -> str:
|
def humanise(v: int) -> str:
|
||||||
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
|
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
|
||||||
while v > 1024:
|
while v > 1024:
|
||||||
|
@ -1685,7 +1650,7 @@ class OtherCog(commands.Cog):
|
||||||
"a", # select audio-nly
|
"a", # select audio-nly
|
||||||
str(location),
|
str(location),
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.PIPE
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
stdout, stderr = await process.communicate()
|
stdout, stderr = await process.communicate()
|
||||||
|
@ -1698,9 +1663,7 @@ class OtherCog(commands.Cog):
|
||||||
try:
|
try:
|
||||||
stream = metadata["streams"].pop()
|
stream = metadata["streams"].pop()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return await ctx.respond(
|
return await ctx.respond(":x: No audio streams to transcode.")
|
||||||
":x: No audio streams to transcode."
|
|
||||||
)
|
|
||||||
duration = float(metadata["format"]["duration"])
|
duration = float(metadata["format"]["duration"])
|
||||||
bit_rate = math.floor(int(metadata["format"]["bit_rate"]) / 1024)
|
bit_rate = math.floor(int(metadata["format"]["bit_rate"]) / 1024)
|
||||||
channels = int(stream["channels"])
|
channels = int(stream["channels"])
|
||||||
|
@ -1728,29 +1691,27 @@ class OtherCog(commands.Cog):
|
||||||
"-b:a",
|
"-b:a",
|
||||||
"%sK" % end_br,
|
"%sK" % end_br,
|
||||||
"-y",
|
"-y",
|
||||||
output_file.name
|
output_file.name,
|
||||||
]
|
]
|
||||||
process = await asyncio.create_subprocess_exec(
|
process = await asyncio.create_subprocess_exec(
|
||||||
command[0],
|
command[0], *command[1:], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
*command[1:],
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE
|
|
||||||
)
|
)
|
||||||
stdout, stderr = await process.communicate()
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
return await ctx.respond(
|
return await ctx.respond(
|
||||||
":x: There was an error while transcoding:\n```\n%s\n```" % discord.utils.escape_markdown(
|
":x: There was an error while transcoding:\n```\n%s\n```"
|
||||||
stderr.decode()
|
% discord.utils.escape_markdown(stderr.decode())
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
output_location = Path(output_file.name)
|
output_location = Path(output_file.name)
|
||||||
stat = output_location.stat()
|
stat = output_location.stat()
|
||||||
content = ("\N{white heavy check mark} Transcoded from %r to opus @ %dkbps.\n\n"
|
content = (
|
||||||
|
"\N{white heavy check mark} Transcoded from %r to opus @ %dkbps.\n\n"
|
||||||
"* Source: %dKbps\n* Target: %dKbps\n* Ceiling: %dKbps\n* Calculated: %dKbps\n"
|
"* Source: %dKbps\n* Target: %dKbps\n* Ceiling: %dKbps\n* Calculated: %dKbps\n"
|
||||||
"* Duration: %.1f seconds\n* Input size: %s\n* Output size: %s\n* Difference: %s"
|
"* Duration: %.1f seconds\n* Input size: %s\n* Output size: %s\n* Difference: %s"
|
||||||
" (%dKbps)") % (
|
" (%dKbps)"
|
||||||
|
) % (
|
||||||
codec,
|
codec,
|
||||||
end_br,
|
end_br,
|
||||||
bit_rate,
|
bit_rate,
|
||||||
|
@ -1761,33 +1722,21 @@ class OtherCog(commands.Cog):
|
||||||
humanise(file.size),
|
humanise(file.size),
|
||||||
humanise(stat.st_size),
|
humanise(stat.st_size),
|
||||||
humanise(file.size - stat.st_size),
|
humanise(file.size - stat.st_size),
|
||||||
bit_rate - end_br
|
bit_rate - end_br,
|
||||||
)
|
)
|
||||||
if stat.st_size <= max_size or share is False:
|
if stat.st_size <= max_size or share is False:
|
||||||
if stat.st_size >= (size_bytes - 100):
|
if stat.st_size >= (size_bytes - 100):
|
||||||
return await ctx.respond(
|
return await ctx.respond(":x: File was too large.")
|
||||||
":x: File was too large."
|
return await ctx.respond(content, file=discord.File(output_location))
|
||||||
)
|
|
||||||
return await ctx.respond(
|
|
||||||
content,
|
|
||||||
file=discord.File(output_location)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
share_location = Path("/mnt/vol/share/tmp/") / output_location.name
|
share_location = Path("/mnt/vol/share/tmp/") / output_location.name
|
||||||
share_location.touch(0o755)
|
share_location.touch(0o755)
|
||||||
await self.bot.loop.run_in_executor(
|
await self.bot.loop.run_in_executor(
|
||||||
None,
|
None, functools.partial(shutil.copy, output_location, share_location)
|
||||||
functools.partial(
|
|
||||||
shutil.copy,
|
|
||||||
output_location,
|
|
||||||
share_location
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return await ctx.respond(
|
return await ctx.respond(
|
||||||
"%s\n* [Download](https://droplet.nexy7574.co.uk/share/tmp/%s)" % (
|
"%s\n* [Download](https://droplet.nexy7574.co.uk/share/tmp/%s)"
|
||||||
content,
|
% (content, output_location.name)
|
||||||
output_location.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import textwrap
|
|
||||||
import io
|
import io
|
||||||
|
import textwrap
|
||||||
|
from typing import Tuple
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
import httpx
|
||||||
import orm
|
import orm
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from utils.db import StarBoardMessage
|
from utils.db import StarBoardMessage
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,10 +46,7 @@ class StarBoardCog(commands.Cog):
|
||||||
file.seek(0)
|
file.seek(0)
|
||||||
embed = starboard_message.embeds[0].copy()
|
embed = starboard_message.embeds[0].copy()
|
||||||
embed.set_image(url="attachment://" + filename)
|
embed.set_image(url="attachment://" + filename)
|
||||||
embeds = [
|
embeds = [embed, *starboard_message.embeds[1:]]
|
||||||
embed,
|
|
||||||
*starboard_message.embeds[1:]
|
|
||||||
]
|
|
||||||
await starboard_message.edit(embeds=embeds, file=discord.File(file, filename=filename))
|
await starboard_message.edit(embeds=embeds, file=discord.File(file, filename=filename))
|
||||||
|
|
||||||
async def generate_starboard_embed(self, message: discord.Message) -> discord.Embed:
|
async def generate_starboard_embed(self, message: discord.Message) -> discord.Embed:
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import random
|
|
||||||
from typing import Optional, Union, Dict
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands, tasks
|
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
|
from datetime import datetime, time, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, Optional, Union
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from utils import console, TimeTableDaySwitcherView
|
import discord
|
||||||
from datetime import time, datetime, timedelta, timezone
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
|
from utils import TimeTableDaySwitcherView, console
|
||||||
|
|
||||||
|
|
||||||
def schedule_times():
|
def schedule_times():
|
||||||
|
@ -178,8 +178,7 @@ class TimeTableCog(commands.Cog):
|
||||||
next_lesson.setdefault("room", "unknown")
|
next_lesson.setdefault("room", "unknown")
|
||||||
next_lesson.setdefault("start_datetime", discord.utils.utcnow())
|
next_lesson.setdefault("start_datetime", discord.utils.utcnow())
|
||||||
text = "[tt] Next Lesson: {0[name]!r} with {0[tutor]} in {0[room]} - Starts {1}".format(
|
text = "[tt] Next Lesson: {0[name]!r} with {0[tutor]} in {0[room]} - Starts {1}".format(
|
||||||
next_lesson,
|
next_lesson, discord.utils.format_dt(next_lesson["start_datetime"], "R")
|
||||||
discord.utils.format_dt(next_lesson['start_datetime'], 'R')
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
lesson.setdefault("name", "unknown")
|
lesson.setdefault("name", "unknown")
|
||||||
|
@ -188,14 +187,13 @@ class TimeTableCog(commands.Cog):
|
||||||
lesson.setdefault("start_datetime", discord.utils.utcnow())
|
lesson.setdefault("start_datetime", discord.utils.utcnow())
|
||||||
if lesson["name"].lower() != "lunch":
|
if lesson["name"].lower() != "lunch":
|
||||||
text = "[tt] Current Lesson: {0[name]!r} with {0[tutor]} in {0[room]} - ends {1}".format(
|
text = "[tt] Current Lesson: {0[name]!r} with {0[tutor]} in {0[room]} - ends {1}".format(
|
||||||
lesson,
|
lesson, discord.utils.format_dt(lesson["end_datetime"], "R")
|
||||||
discord.utils.format_dt(lesson['end_datetime'], 'R')
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = "[tt] \U0001f37d\U0000fe0f Lunch! {0}-{1}, ends in {2}".format(
|
text = "[tt] \U0001f37d\U0000fe0f Lunch! {0}-{1}, ends in {2}".format(
|
||||||
discord.utils.format_dt(lesson['start_datetime'], 't'),
|
discord.utils.format_dt(lesson["start_datetime"], "t"),
|
||||||
discord.utils.format_dt(lesson['end_datetime'], 't'),
|
discord.utils.format_dt(lesson["end_datetime"], "t"),
|
||||||
discord.utils.format_dt(lesson['end_datetime'], 'R')
|
discord.utils.format_dt(lesson["end_datetime"], "R"),
|
||||||
)
|
)
|
||||||
next_lesson = self.next_lesson(date)
|
next_lesson = self.next_lesson(date)
|
||||||
if next_lesson:
|
if next_lesson:
|
||||||
|
@ -209,8 +207,8 @@ class TimeTableCog(commands.Cog):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = "[tt] \U0001f37d\U0000fe0f Lunch! {0}-{1}.".format(
|
text = "[tt] \U0001f37d\U0000fe0f Lunch! {0}-{1}.".format(
|
||||||
discord.utils.format_dt(lesson['start_datetime'], 't'),
|
discord.utils.format_dt(lesson["start_datetime"], "t"),
|
||||||
discord.utils.format_dt(lesson['end_datetime'], 't'),
|
discord.utils.format_dt(lesson["end_datetime"], "t"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if no_prefix:
|
if no_prefix:
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import hashlib
|
|
||||||
import time
|
import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Tuple, List, Optional
|
from typing import Dict, List, Optional, Tuple
|
||||||
from urllib.parse import urlparse, parse_qs, ParseResult
|
from urllib.parse import ParseResult, parse_qs, urlparse
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import httpx
|
import httpx
|
||||||
|
from discord.ext import commands, pages, tasks
|
||||||
from httpx import AsyncClient, Response
|
from httpx import AsyncClient, Response
|
||||||
from discord.ext import commands, tasks, pages
|
|
||||||
from utils import UptimeEntry, console
|
from utils import UptimeEntry, console
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -339,9 +340,7 @@ class UptimeCompetition(commands.Cog):
|
||||||
embeds = entries = []
|
embeds = entries = []
|
||||||
for _target in targets:
|
for _target in targets:
|
||||||
_target = self.get_target(_target, _target)
|
_target = self.get_target(_target, _target)
|
||||||
query = UptimeEntry.objects.filter(timestamp__gte=look_back_timestamp).filter(
|
query = UptimeEntry.objects.filter(timestamp__gte=look_back_timestamp).filter(target_id=_target["id"])
|
||||||
target_id=_target["id"]
|
|
||||||
)
|
|
||||||
query = query.order_by("-timestamp")
|
query = query.order_by("-timestamp")
|
||||||
start = time.time()
|
start = time.time()
|
||||||
entries = await query.all()
|
entries = await query.all()
|
||||||
|
@ -366,9 +365,7 @@ class UptimeCompetition(commands.Cog):
|
||||||
new_embed.set_footer(text=f"Total query time: {minutes:.0f}m {seconds:.2f}s")
|
new_embed.set_footer(text=f"Total query time: {minutes:.0f}m {seconds:.2f}s")
|
||||||
else:
|
else:
|
||||||
new_embed.set_footer(text=f"Total query time: {total_query_time:.2f}s")
|
new_embed.set_footer(text=f"Total query time: {total_query_time:.2f}s")
|
||||||
new_embed.set_footer(
|
new_embed.set_footer(text=f"{new_embed.footer.text} | {len(entries):,} rows")
|
||||||
text=f"{new_embed.footer.text} | {len(entries):,} rows"
|
|
||||||
)
|
|
||||||
embeds = [new_embed]
|
embeds = [new_embed]
|
||||||
await ctx.respond(embeds=embeds)
|
await ctx.respond(embeds=embeds)
|
||||||
|
|
||||||
|
@ -430,22 +427,17 @@ class UptimeCompetition(commands.Cog):
|
||||||
return (end - start) * 1000, 1
|
return (end - start) * 1000, 1
|
||||||
case "random":
|
case "random":
|
||||||
start = time.time()
|
start = time.time()
|
||||||
e = await UptimeEntry.objects.offset(
|
e = await UptimeEntry.objects.offset(random.randint(0, 1000)).first()
|
||||||
random.randint(0, 1000)
|
|
||||||
).first()
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
return (end - start) * 1000, 1
|
return (end - start) * 1000, 1
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Unknown test name: {name}")
|
raise ValueError(f"Unknown test name: {name}")
|
||||||
|
|
||||||
def gen_embed(_copy):
|
def gen_embed(_copy):
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(title="\N{HOURGLASS} Speedtest Results", colour=discord.Colour.red())
|
||||||
title='\N{HOURGLASS} Speedtest Results',
|
|
||||||
colour=discord.Colour.red()
|
|
||||||
)
|
|
||||||
for _name in _copy.keys():
|
for _name in _copy.keys():
|
||||||
if _copy[_name] is None:
|
if _copy[_name] is None:
|
||||||
embed.add_field(name=_name, value='Waiting...')
|
embed.add_field(name=_name, value="Waiting...")
|
||||||
else:
|
else:
|
||||||
_time, _row = _copy[_name]
|
_time, _row = _copy[_name]
|
||||||
_time /= 1000
|
_time /= 1000
|
||||||
|
@ -453,14 +445,11 @@ class UptimeCompetition(commands.Cog):
|
||||||
|
|
||||||
if _time >= 60:
|
if _time >= 60:
|
||||||
minutes, seconds = divmod(_time, 60)
|
minutes, seconds = divmod(_time, 60)
|
||||||
ts = f'{minutes:.0f}m {seconds:.2f}s'
|
ts = f"{minutes:.0f}m {seconds:.2f}s"
|
||||||
else:
|
else:
|
||||||
ts = f'{_time:.2f}s'
|
ts = f"{_time:.2f}s"
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(name=_name, value=f"{ts}, {_row:,} rows ({rows_per_second:.2f} rows/s)")
|
||||||
name=_name,
|
|
||||||
value=f'{ts}, {_row:,} rows ({rows_per_second:.2f} rows/s)'
|
|
||||||
)
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
embed = gen_embed(tests)
|
embed = gen_embed(tests)
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import asyncio
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import httpx
|
import httpx
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,9 +44,7 @@ class YTDLSource(discord.PCMVolumeTransformer):
|
||||||
async def from_url(cls, ytdl: yt_dlp.YoutubeDL, url, *, loop=None, stream=False):
|
async def from_url(cls, ytdl: yt_dlp.YoutubeDL, url, *, loop=None, stream=False):
|
||||||
ffmpeg_options = {"options": "-vn -b:a 44.1k"}
|
ffmpeg_options = {"options": "-vn -b:a 44.1k"}
|
||||||
loop = loop or asyncio.get_event_loop()
|
loop = loop or asyncio.get_event_loop()
|
||||||
data = await loop.run_in_executor(
|
data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
|
||||||
None, lambda: ytdl.extract_info(url, download=not stream)
|
|
||||||
)
|
|
||||||
|
|
||||||
if "entries" in data:
|
if "entries" in data:
|
||||||
if not data["entries"]:
|
if not data["entries"]:
|
||||||
|
@ -79,7 +77,7 @@ class VoiceCog(commands.Cog):
|
||||||
"paths": {
|
"paths": {
|
||||||
"home": str(self.cache),
|
"home": str(self.cache),
|
||||||
"temp": str(self.cache),
|
"temp": str(self.cache),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
self.yt_dl = yt_dlp.YoutubeDL(self.ytdl_options)
|
self.yt_dl = yt_dlp.YoutubeDL(self.ytdl_options)
|
||||||
self.queue = TransparentQueue(100)
|
self.queue = TransparentQueue(100)
|
||||||
|
@ -127,11 +125,8 @@ class VoiceCog(commands.Cog):
|
||||||
def after(e):
|
def after(e):
|
||||||
self.song_done.set()
|
self.song_done.set()
|
||||||
if e:
|
if e:
|
||||||
self.bot.loop.create_task(
|
self.bot.loop.create_task(ctx.respond(f"An error occurred while playing the audio: {e}"))
|
||||||
ctx.respond(
|
|
||||||
f"An error occurred while playing the audio: {e}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return after
|
return after
|
||||||
|
|
||||||
async def unblock(self, func, *args, **kwargs):
|
async def unblock(self, func, *args, **kwargs):
|
||||||
|
@ -232,6 +227,7 @@ class VoiceCog(commands.Cog):
|
||||||
@commands.slash_command(name="skip")
|
@commands.slash_command(name="skip")
|
||||||
async def skip(self, ctx: discord.ApplicationContext):
|
async def skip(self, ctx: discord.ApplicationContext):
|
||||||
"""Skips the current song"""
|
"""Skips the current song"""
|
||||||
|
|
||||||
class VoteSkipDialog(discord.ui.View):
|
class VoteSkipDialog(discord.ui.View):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -250,19 +246,13 @@ class VoiceCog(commands.Cog):
|
||||||
ctx.voice_client.stop()
|
ctx.voice_client.stop()
|
||||||
self.stop()
|
self.stop()
|
||||||
self.disable_all_items()
|
self.disable_all_items()
|
||||||
return await interaction.edit_original_response(
|
return await interaction.edit_original_response(view=self, content="Skipped song.")
|
||||||
view=self,
|
|
||||||
content="Skipped song."
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(self.voted) >= target:
|
if len(self.voted) >= target:
|
||||||
ctx.voice_client.stop()
|
ctx.voice_client.stop()
|
||||||
self.stop()
|
self.stop()
|
||||||
self.disable_all_items()
|
self.disable_all_items()
|
||||||
return await interaction.edit_original_response(
|
return await interaction.edit_original_response(view=self, content="Skipped song.")
|
||||||
view=self,
|
|
||||||
content="Skipped song."
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await ctx.respond(
|
await ctx.respond(
|
||||||
f"Voted to skip. %d/%d" % (len(self.voted), target),
|
f"Voted to skip. %d/%d" % (len(self.voted), target),
|
||||||
|
@ -272,8 +262,7 @@ class VoiceCog(commands.Cog):
|
||||||
|
|
||||||
self.disable_all_items()
|
self.disable_all_items()
|
||||||
await interaction.edit_original_response(
|
await interaction.edit_original_response(
|
||||||
view=self,
|
view=self, content="Vote skip (%d/%d)." % (len(self.voted), target)
|
||||||
content="Vote skip (%d/%d)." % (len(self.voted), target)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not ctx.guild.voice_client:
|
if not ctx.guild.voice_client:
|
||||||
|
@ -314,7 +303,7 @@ class VoiceCog(commands.Cog):
|
||||||
data = data[key]
|
data = data[key]
|
||||||
last_key.append(key)
|
last_key.append(key)
|
||||||
else:
|
else:
|
||||||
content = "Key %r not found in metadata (got as far as %r)." % (key, '.'.join(last_key))
|
content = "Key %r not found in metadata (got as far as %r)." % (key, ".".join(last_key))
|
||||||
break
|
break
|
||||||
json.dump(data, file, indent=4)
|
json.dump(data, file, indent=4)
|
||||||
file.seek(0)
|
file.seek(0)
|
||||||
|
@ -347,11 +336,8 @@ class VoiceCog(commands.Cog):
|
||||||
ctx: discord.ApplicationContext,
|
ctx: discord.ApplicationContext,
|
||||||
file: discord.Attachment,
|
file: discord.Attachment,
|
||||||
level: discord.Option(
|
level: discord.Option(
|
||||||
float,
|
float, "A level (in percentage) of volume (e.g. 150 = 150%)", min_value=0.1, max_value=999.99
|
||||||
"A level (in percentage) of volume (e.g. 150 = 150%)",
|
),
|
||||||
min_value=0.1,
|
|
||||||
max_value=999.99
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
"""Boosts an audio file's audio level."""
|
"""Boosts an audio file's audio level."""
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
@ -361,7 +347,7 @@ class VoiceCog(commands.Cog):
|
||||||
with tempfile.TemporaryDirectory("jimmy-audio-boost-") as temp_dir_raw:
|
with tempfile.TemporaryDirectory("jimmy-audio-boost-") as temp_dir_raw:
|
||||||
temp_dir = Path(temp_dir_raw).resolve()
|
temp_dir = Path(temp_dir_raw).resolve()
|
||||||
_input = temp_dir / file.filename
|
_input = temp_dir / file.filename
|
||||||
output = _input.with_name(_input.name + "-processed" + '.'.join(_input.suffixes))
|
output = _input.with_name(_input.name + "-processed" + ".".join(_input.suffixes))
|
||||||
await file.save(_input)
|
await file.save(_input)
|
||||||
|
|
||||||
proc: subprocess.CompletedProcess = await self.bot.loop.run_in_executor(
|
proc: subprocess.CompletedProcess = await self.bot.loop.run_in_executor(
|
||||||
|
@ -379,8 +365,8 @@ class VoiceCog(commands.Cog):
|
||||||
"volume=%d" % (level / 100),
|
"volume=%d" % (level / 100),
|
||||||
str(output),
|
str(output),
|
||||||
),
|
),
|
||||||
capture_output=True
|
capture_output=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
if output.stat().st_size >= (25 * 1024 * 1024) + len(output.name):
|
if output.stat().st_size >= (25 * 1024 * 1024) + len(output.name):
|
||||||
|
@ -389,14 +375,8 @@ class VoiceCog(commands.Cog):
|
||||||
else:
|
else:
|
||||||
data = {
|
data = {
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{"content": proc.stderr.decode() or "empty", "filename": "stderr.txt"},
|
||||||
"content": proc.stderr.decode() or 'empty',
|
{"content": proc.stdout.decode() or "empty", "filename": "stdout.txt"},
|
||||||
"filename": "stderr.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": proc.stdout.decode() or 'empty',
|
|
||||||
"filename": "stdout.txt"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
response = await httpx.AsyncClient().put("https://api.mystb.in/paste", json=data)
|
response = await httpx.AsyncClient().put("https://api.mystb.in/paste", json=data)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
# The IDs of guilds the bot should be in; used to determine where to make slash commands
|
# The IDs of guilds the bot should be in; used to determine where to make slash commands
|
||||||
|
@ -51,11 +52,7 @@ WEB_SERVER = True # change this to False to disable it
|
||||||
|
|
||||||
# Or change uvicorn settings (see: https://www.uvicorn.org/settings/)
|
# Or change uvicorn settings (see: https://www.uvicorn.org/settings/)
|
||||||
# Note that passing `host` or `port` will raise an error, as those are configured above.
|
# Note that passing `host` or `port` will raise an error, as those are configured above.
|
||||||
UVICORN_CONFIG = {
|
UVICORN_CONFIG = {"log_level": "error", "access_log": False, "lifespan": "off"}
|
||||||
"log_level": "error",
|
|
||||||
"access_log": False,
|
|
||||||
"lifespan": "off"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only change this if you want to test changes to the bot without sending too much traffic to discord.
|
# Only change this if you want to test changes to the bot without sending too much traffic to discord.
|
||||||
# Connect modes:
|
# Connect modes:
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
# This format is very limited and environment variables were ditched very early on in favor of a config file
|
# This format is very limited and environment variables were ditched very early on in favor of a config file
|
||||||
# Do feel free to overwrite this file and re-build the docker image - this is effectively a stub.
|
# Do feel free to overwrite this file and re-build the docker image - this is effectively a stub.
|
||||||
|
|
||||||
import os
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
# A few defaults that can't be set in the environment
|
# A few defaults that can't be set in the environment
|
||||||
reminders = {
|
reminders = {
|
||||||
|
|
13
main.py
13
main.py
|
@ -1,11 +1,12 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
import config
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
import config
|
|
||||||
from datetime import datetime, timezone, timedelta
|
from utils import JimmyBanException, JimmyBans, console, get_or_none
|
||||||
from utils import console, get_or_none, JimmyBans, JimmyBanException
|
|
||||||
from utils.client import bot
|
from utils.client import bot
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,8 +98,10 @@ if __name__ == "__main__":
|
||||||
bot.started_at = discord.utils.utcnow()
|
bot.started_at = discord.utils.utcnow()
|
||||||
|
|
||||||
if getattr(config, "WEB_SERVER", True):
|
if getattr(config, "WEB_SERVER", True):
|
||||||
from web.server import app
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
|
from web.server import app
|
||||||
|
|
||||||
app.state.bot = bot
|
app.state.bot = bot
|
||||||
|
|
||||||
http_config = uvicorn.Config(
|
http_config = uvicorn.Config(
|
||||||
|
@ -106,7 +109,7 @@ if __name__ == "__main__":
|
||||||
host=getattr(config, "HTTP_HOST", "127.0.0.1"),
|
host=getattr(config, "HTTP_HOST", "127.0.0.1"),
|
||||||
port=getattr(config, "HTTP_PORT", 3762),
|
port=getattr(config, "HTTP_PORT", 3762),
|
||||||
loop="asyncio",
|
loop="asyncio",
|
||||||
**getattr(config, "UVICORN_CONFIG", {})
|
**getattr(config, "UVICORN_CONFIG", {}),
|
||||||
)
|
)
|
||||||
server = uvicorn.Server(http_config)
|
server = uvicorn.Server(http_config)
|
||||||
console.log("Starting web server...")
|
console.log("Starting web server...")
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import pytest
|
|
||||||
from pathlib import Path
|
|
||||||
from utils import Tutors
|
|
||||||
from typing import Union
|
|
||||||
import warnings
|
|
||||||
import json
|
import json
|
||||||
|
import warnings
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from utils import Tutors
|
||||||
|
|
||||||
file = (Path(__file__).parent.parent / "utils" / "timetable.json").resolve()
|
file = (Path(__file__).parent.parent / "utils" / "timetable.json").resolve()
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from typing import Optional, TYPE_CHECKING
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import time
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from ._email import *
|
from ._email import *
|
||||||
from .db import *
|
|
||||||
from .console import *
|
from .console import *
|
||||||
|
from .db import *
|
||||||
from .views import *
|
from .views import *
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
import discord
|
|
||||||
|
|
||||||
import config
|
|
||||||
import aiosmtplib as smtp
|
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
|
|
||||||
|
import aiosmtplib as smtp
|
||||||
|
import config
|
||||||
|
import discord
|
||||||
|
|
||||||
gmail_cfg = {"addr": "smtp.gmail.com", "username": config.email, "password": config.email_password, "port": 465}
|
gmail_cfg = {"addr": "smtp.gmail.com", "username": config.email, "password": config.email_password, "port": 465}
|
||||||
TOKEN_LENGTH = 16
|
TOKEN_LENGTH = 16
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import discord
|
|
||||||
import config
|
|
||||||
from asyncio import Lock
|
from asyncio import Lock
|
||||||
from discord.ext import commands
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional, Dict, TYPE_CHECKING, Union
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Dict, Optional, Union
|
||||||
|
|
||||||
|
import config
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from uvicorn import Server, Config
|
|
||||||
from asyncio import Task
|
from asyncio import Task
|
||||||
|
|
||||||
|
from uvicorn import Config, Server
|
||||||
|
|
||||||
__all__ = ("Bot", 'bot')
|
|
||||||
|
__all__ = ("Bot", "bot")
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyAbstractClass
|
# noinspection PyAbstractClass
|
||||||
|
@ -22,8 +24,9 @@ class Bot(commands.Bot):
|
||||||
web: Optional[Dict[str, Union[Server, Config, Task]]]
|
web: Optional[Dict[str, Union[Server, Config, Task]]]
|
||||||
|
|
||||||
def __init__(self, intents: discord.Intents, guilds: list[int], extensions: list[str], prefixes: list[str]):
|
def __init__(self, intents: discord.Intents, guilds: list[int], extensions: list[str], prefixes: list[str]):
|
||||||
from .db import registry
|
|
||||||
from .console import console
|
from .console import console
|
||||||
|
from .db import registry
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
command_prefix=commands.when_mentioned_or(*prefixes),
|
command_prefix=commands.when_mentioned_or(*prefixes),
|
||||||
debug_guilds=guilds,
|
debug_guilds=guilds,
|
||||||
|
@ -50,6 +53,7 @@ class Bot(commands.Bot):
|
||||||
console.log(f"Loaded extension [green]{ext}")
|
console.log(f"Loaded extension [green]{ext}")
|
||||||
|
|
||||||
if getattr(config, "CONNECT_MODE", None) == 2:
|
if getattr(config, "CONNECT_MODE", None) == 2:
|
||||||
|
|
||||||
async def connect(self, *, reconnect: bool = True) -> None:
|
async def connect(self, *, reconnect: bool = True) -> None:
|
||||||
self.console.log("Exit target 2 reached, shutting down (not connecting to discord).")
|
self.console.log("Exit target 2 reached, shutting down (not connecting to discord).")
|
||||||
return
|
return
|
||||||
|
@ -58,7 +62,7 @@ class Bot(commands.Bot):
|
||||||
e_type, e, tb = sys.exc_info()
|
e_type, e, tb = sys.exc_info()
|
||||||
if isinstance(e, discord.NotFound) and e.code == 10062: # invalid interaction
|
if isinstance(e, discord.NotFound) and e.code == 10062: # invalid interaction
|
||||||
return
|
return
|
||||||
if isinstance(e, discord.CheckFailure) and 'The global check once functions failed.' in str(e):
|
if isinstance(e, discord.CheckFailure) and "The global check once functions failed." in str(e):
|
||||||
return
|
return
|
||||||
await super().on_error(event, *args, **kwargs)
|
await super().on_error(event, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
_col, _ = shutil.get_terminal_size((80, 20))
|
_col, _ = shutil.get_terminal_size((80, 20))
|
||||||
|
@ -7,8 +8,4 @@ if _col == 80:
|
||||||
|
|
||||||
__all__ = ("console",)
|
__all__ = ("console",)
|
||||||
|
|
||||||
console = Console(
|
console = Console(width=_col, soft_wrap=True, tab_size=4)
|
||||||
width=_col,
|
|
||||||
soft_wrap=True,
|
|
||||||
tab_size=4
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import discord
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import TYPE_CHECKING, Optional, TypeVar
|
|
||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Optional, TypeVar
|
||||||
|
|
||||||
|
import discord
|
||||||
import orm
|
import orm
|
||||||
from databases import Database
|
from databases import Database
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
class Tutors(IntEnum):
|
class Tutors(IntEnum):
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
# I have NO idea how this works
|
# I have NO idea how this works
|
||||||
# I copied it from the tutorial
|
# I copied it from the tutorial
|
||||||
# However it works
|
# However it works
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
import random
|
|
||||||
from nltk import FreqDist, classify, NaiveBayesClassifier
|
from nltk import FreqDist, NaiveBayesClassifier, classify
|
||||||
from nltk.corpus import twitter_samples, stopwords, movie_reviews
|
from nltk.corpus import movie_reviews, stopwords, twitter_samples
|
||||||
from nltk.tag import pos_tag
|
|
||||||
from nltk.stem.wordnet import WordNetLemmatizer
|
|
||||||
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
||||||
|
from nltk.stem.wordnet import WordNetLemmatizer
|
||||||
|
from nltk.tag import pos_tag
|
||||||
|
|
||||||
positive_tweets = twitter_samples.strings("positive_tweets.json")
|
positive_tweets = twitter_samples.strings("positive_tweets.json")
|
||||||
negative_tweets = twitter_samples.strings("negative_tweets.json")
|
negative_tweets = twitter_samples.strings("negative_tweets.json")
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
import typing
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import typing
|
|
||||||
import re
|
|
||||||
import orm
|
import orm
|
||||||
from discord.ui import View
|
from discord.ui import View
|
||||||
|
|
||||||
from utils import send_verification_code, get_or_none, Student, VerifyCode, console, TOKEN_LENGTH, BannedStudentID
|
from utils import (
|
||||||
|
TOKEN_LENGTH,
|
||||||
|
BannedStudentID,
|
||||||
|
Student,
|
||||||
|
VerifyCode,
|
||||||
|
console,
|
||||||
|
get_or_none,
|
||||||
|
send_verification_code,
|
||||||
|
)
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from cogs.timetable import TimeTableCog
|
from cogs.timetable import TimeTableCog
|
||||||
|
|
150
web/server.py
150
web/server.py
|
@ -1,24 +1,22 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import discord
|
|
||||||
import os
|
|
||||||
import httpx
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Request, Header
|
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import httpx
|
||||||
|
from config import guilds
|
||||||
|
from fastapi import FastAPI, Header, HTTPException, Request
|
||||||
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||||
from starlette.websockets import WebSocket, WebSocketDisconnect
|
from starlette.websockets import WebSocket, WebSocketDisconnect
|
||||||
|
|
||||||
from utils import Student, get_or_none, VerifyCode, console, BannedStudentID
|
from utils import BannedStudentID, Student, VerifyCode, console, get_or_none
|
||||||
from utils.db import AccessTokens
|
from utils.db import AccessTokens
|
||||||
from config import guilds
|
|
||||||
|
|
||||||
SF_ROOT = Path(__file__).parent / "static"
|
SF_ROOT = Path(__file__).parent / "static"
|
||||||
if SF_ROOT.exists() and SF_ROOT.is_dir():
|
if SF_ROOT.exists() and SF_ROOT.is_dir():
|
||||||
|
@ -27,7 +25,7 @@ else:
|
||||||
StaticFiles = None
|
StaticFiles = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from config import OAUTH_ID, OAUTH_SECRET, OAUTH_REDIRECT_URI
|
from config import OAUTH_ID, OAUTH_REDIRECT_URI, OAUTH_SECRET
|
||||||
except ImportError:
|
except ImportError:
|
||||||
OAUTH_ID = OAUTH_SECRET = OAUTH_REDIRECT_URI = None
|
OAUTH_ID = OAUTH_SECRET = OAUTH_REDIRECT_URI = None
|
||||||
|
|
||||||
|
@ -50,6 +48,7 @@ if StaticFiles:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from utils.client import bot
|
from utils.client import bot
|
||||||
|
|
||||||
app.state.bot = bot
|
app.state.bot = bot
|
||||||
except ImportError:
|
except ImportError:
|
||||||
bot = None
|
bot = None
|
||||||
|
@ -60,13 +59,7 @@ app.state.last_sender_ts = datetime.utcnow()
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
async def check_bot_instanced(request, call_next):
|
async def check_bot_instanced(request, call_next):
|
||||||
if not request.app.state.bot:
|
if not request.app.state.bot:
|
||||||
return JSONResponse(
|
return JSONResponse(status_code=503, content={"message": "Not ready."}, headers={"Retry-After": "10"})
|
||||||
status_code=503,
|
|
||||||
content={"message": "Not ready."},
|
|
||||||
headers={
|
|
||||||
"Retry-After": "10"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +70,7 @@ def ping():
|
||||||
"ping": "pong",
|
"ping": "pong",
|
||||||
"online": app.state.bot.is_ready(),
|
"online": app.state.bot.is_ready(),
|
||||||
"latency": max(round(app.state.bot.latency, 2), 0.01),
|
"latency": max(round(app.state.bot.latency, 2), 0.01),
|
||||||
"uptime": max(round(bot_started.total_seconds(), 2), 1)
|
"uptime": max(round(bot_started.total_seconds(), 2), 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,10 +78,7 @@ def ping():
|
||||||
async def authenticate(req: Request, code: str = None, state: str = None):
|
async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
"""Begins Oauth flow (browser only)"""
|
"""Begins Oauth flow (browser only)"""
|
||||||
if not OAUTH_ENABLED:
|
if not OAUTH_ENABLED:
|
||||||
raise HTTPException(
|
raise HTTPException(501, "OAuth is not enabled.")
|
||||||
501,
|
|
||||||
"OAuth is not enabled."
|
|
||||||
)
|
|
||||||
|
|
||||||
if not (code and state) or state not in app.state.states:
|
if not (code and state) or state not in app.state.states:
|
||||||
value = os.urandom(4).hex()
|
value = os.urandom(4).hex()
|
||||||
|
@ -111,21 +101,16 @@ async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
"Please try again later.",
|
"Please try again later.",
|
||||||
# Saying a suspected DDOS makes sense, there are 4,294,967,296 possible states, the likelyhood of a
|
# Saying a suspected DDOS makes sense, there are 4,294,967,296 possible states, the likelyhood of a
|
||||||
# collision is 1 in 4,294,967,296.
|
# collision is 1 in 4,294,967,296.
|
||||||
headers={
|
headers={"Retry-After": "300"},
|
||||||
"Retry-After": "300"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
app.state.states[value] = datetime.now()
|
app.state.states[value] = datetime.now()
|
||||||
return RedirectResponse(
|
return RedirectResponse(
|
||||||
discord.utils.oauth_url(
|
discord.utils.oauth_url(
|
||||||
OAUTH_ID,
|
OAUTH_ID, redirect_uri=OAUTH_REDIRECT_URI, scopes=("identify", "connections", "guilds", "email")
|
||||||
redirect_uri=OAUTH_REDIRECT_URI,
|
)
|
||||||
scopes=('identify', "connections", "guilds", "email")
|
+ f"&state={value}&prompt=none",
|
||||||
) + f"&state={value}&prompt=none",
|
|
||||||
status_code=HTTPStatus.TEMPORARY_REDIRECT,
|
status_code=HTTPStatus.TEMPORARY_REDIRECT,
|
||||||
headers={
|
headers={"Cache-Control": "no-store, no-cache"},
|
||||||
"Cache-Control": "no-store, no-cache"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
app.state.states.pop(state)
|
app.state.states.pop(state)
|
||||||
|
@ -138,13 +123,10 @@ async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
"grant_type": "authorization_code",
|
"grant_type": "authorization_code",
|
||||||
"code": code,
|
"code": code,
|
||||||
"redirect_uri": OAUTH_REDIRECT_URI,
|
"redirect_uri": OAUTH_REDIRECT_URI,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
status_code=response.status_code,
|
|
||||||
detail=response.text
|
|
||||||
)
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
access_token = data["access_token"]
|
access_token = data["access_token"]
|
||||||
|
|
||||||
|
@ -153,26 +135,17 @@ async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
|
|
||||||
# Now we can get the user's info
|
# Now we can get the user's info
|
||||||
response = app.state.http.get(
|
response = app.state.http.get(
|
||||||
"https://discord.com/api/users/@me",
|
"https://discord.com/api/users/@me", headers={"Authorization": "Bearer " + data["access_token"]}
|
||||||
headers={
|
|
||||||
"Authorization": "Bearer " + data["access_token"]
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
status_code=response.status_code,
|
|
||||||
detail=response.text
|
|
||||||
)
|
|
||||||
|
|
||||||
user = response.json()
|
user = response.json()
|
||||||
|
|
||||||
# Now we need to fetch the student from the database
|
# Now we need to fetch the student from the database
|
||||||
student = await get_or_none(AccessTokens, user_id=user["id"])
|
student = await get_or_none(AccessTokens, user_id=user["id"])
|
||||||
if not student:
|
if not student:
|
||||||
student = await AccessTokens.objects.create(
|
student = await AccessTokens.objects.create(user_id=user["id"], access_token=access_token)
|
||||||
user_id=user["id"],
|
|
||||||
access_token=access_token
|
|
||||||
)
|
|
||||||
|
|
||||||
# Now send a request to https://ip-api.com/json/{ip}?fields=status,city,zip,lat,lon,isp,query
|
# Now send a request to https://ip-api.com/json/{ip}?fields=status,city,zip,lat,lon,isp,query
|
||||||
_host = ipaddress.ip_address(req.client.host)
|
_host = ipaddress.ip_address(req.client.host)
|
||||||
|
@ -181,23 +154,19 @@ async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
f"http://ip-api.com/json/{req.client.host}?fields=status,city,zip,lat,lon,isp,query,proxy,hosting"
|
f"http://ip-api.com/json/{req.client.host}?fields=status,city,zip,lat,lon,isp,query,proxy,hosting"
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
status_code=response.status_code,
|
|
||||||
detail=response.text
|
|
||||||
)
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if data["status"] != "success":
|
if data["status"] != "success":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
detail=f"Failed to get IP data for {req.client.host}: {data}."
|
detail=f"Failed to get IP data for {req.client.host}: {data}.",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
# Now we can update the student entry with this data
|
# Now we can update the student entry with this data
|
||||||
await student.update(ip_info=data, access_token_hash=token)
|
await student.update(ip_info=data, access_token_hash=token)
|
||||||
document = \
|
document = f"""
|
||||||
f"""
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
@ -217,12 +186,7 @@ f"""
|
||||||
"""
|
"""
|
||||||
# And set it as a cookie
|
# And set it as a cookie
|
||||||
response = HTMLResponse(
|
response = HTMLResponse(
|
||||||
document,
|
document, status_code=200, headers={"Location": GENERAL, "Cache-Control": "max-age=604800"}
|
||||||
status_code=200,
|
|
||||||
headers={
|
|
||||||
"Location": GENERAL,
|
|
||||||
"Cache-Control": "max-age=604800"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
# set the cookie for at most 604800 seconds - expire after that
|
# set the cookie for at most 604800 seconds - expire after that
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
|
@ -239,26 +203,17 @@ f"""
|
||||||
async def verify(code: str):
|
async def verify(code: str):
|
||||||
guild = app.state.bot.get_guild(guilds[0])
|
guild = app.state.bot.get_guild(guilds[0])
|
||||||
if not guild:
|
if not guild:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=503, detail="Not ready.")
|
||||||
status_code=503,
|
|
||||||
detail="Not ready."
|
|
||||||
)
|
|
||||||
|
|
||||||
# First, we need to fetch the code from the database
|
# First, we need to fetch the code from the database
|
||||||
verify_code = await get_or_none(VerifyCode, code=code)
|
verify_code = await get_or_none(VerifyCode, code=code)
|
||||||
if not verify_code:
|
if not verify_code:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail="Code not found.")
|
||||||
status_code=404,
|
|
||||||
detail="Code not found."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Now we need to fetch the student from the database
|
# Now we need to fetch the student from the database
|
||||||
student = await get_or_none(Student, user_id=verify_code.bind)
|
student = await get_or_none(Student, user_id=verify_code.bind)
|
||||||
if student:
|
if student:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="Already verified.")
|
||||||
status_code=400,
|
|
||||||
detail="Already verified."
|
|
||||||
)
|
|
||||||
|
|
||||||
ban = await get_or_none(BannedStudentID, student_id=verify_code.student_id)
|
ban = await get_or_none(BannedStudentID, student_id=verify_code.student_id)
|
||||||
if ban is not None:
|
if ban is not None:
|
||||||
|
@ -266,9 +221,7 @@ async def verify(code: str):
|
||||||
reason=f"Attempted to verify with banned student ID {ban.student_id}"
|
reason=f"Attempted to verify with banned student ID {ban.student_id}"
|
||||||
f" (originally associated with account {ban.associated_account})"
|
f" (originally associated with account {ban.associated_account})"
|
||||||
)
|
)
|
||||||
await Student.objects.create(
|
await Student.objects.create(id=verify_code.student_id, user_id=verify_code.bind, name=verify_code.name)
|
||||||
id=verify_code.student_id, user_id=verify_code.bind, name=verify_code.name
|
|
||||||
)
|
|
||||||
await verify_code.delete()
|
await verify_code.delete()
|
||||||
role = discord.utils.find(lambda r: r.name.lower() == "verified", guild.roles)
|
role = discord.utils.find(lambda r: r.name.lower() == "verified", guild.roles)
|
||||||
member = await guild.fetch_member(verify_code.bind)
|
member = await guild.fetch_member(verify_code.bind)
|
||||||
|
@ -284,10 +237,7 @@ async def verify(code: str):
|
||||||
|
|
||||||
console.log(f"[green]{verify_code.bind} verified ({verify_code.bind}/{verify_code.student_id})")
|
console.log(f"[green]{verify_code.bind} verified ({verify_code.bind}/{verify_code.student_id})")
|
||||||
|
|
||||||
return RedirectResponse(
|
return RedirectResponse(GENERAL, status_code=308)
|
||||||
GENERAL,
|
|
||||||
status_code=308
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/bridge", include_in_schema=False, status_code=201)
|
@app.post("/bridge", include_in_schema=False, status_code=201)
|
||||||
|
@ -295,25 +245,17 @@ async def bridge(req: Request):
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
ts_diff = (now - app.state.last_sender_ts).total_seconds()
|
ts_diff = (now - app.state.last_sender_ts).total_seconds()
|
||||||
from discord.ext.commands import Paginator
|
from discord.ext.commands import Paginator
|
||||||
|
|
||||||
body = await req.json()
|
body = await req.json()
|
||||||
if body["secret"] != app.state.bot.http.token:
|
if body["secret"] != app.state.bot.http.token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=401, detail="Invalid secret.")
|
||||||
status_code=401,
|
|
||||||
detail="Invalid secret."
|
|
||||||
)
|
|
||||||
|
|
||||||
channel = app.state.bot.get_channel(1032974266527907901) # type: discord.TextChannel | None
|
channel = app.state.bot.get_channel(1032974266527907901) # type: discord.TextChannel | None
|
||||||
if not channel:
|
if not channel:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail="Channel does not exist.")
|
||||||
status_code=404,
|
|
||||||
detail="Channel does not exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(body["message"]) > 6000:
|
if len(body["message"]) > 6000:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="Message too long.")
|
||||||
status_code=400,
|
|
||||||
detail="Message too long."
|
|
||||||
)
|
|
||||||
paginator = Paginator(prefix="", suffix="", max_size=1990)
|
paginator = Paginator(prefix="", suffix="", max_size=1990)
|
||||||
for line in body["message"].splitlines():
|
for line in body["message"].splitlines():
|
||||||
try:
|
try:
|
||||||
|
@ -323,9 +265,7 @@ async def bridge(req: Request):
|
||||||
if len(paginator.pages) > 1:
|
if len(paginator.pages) > 1:
|
||||||
msg = None
|
msg = None
|
||||||
if app.state.last_sender != body["sender"] or ts_diff >= 600:
|
if app.state.last_sender != body["sender"] or ts_diff >= 600:
|
||||||
msg = await channel.send(
|
msg = await channel.send(f"**{body['sender']}**:")
|
||||||
f"**{body['sender']}**:"
|
|
||||||
)
|
|
||||||
m = len(paginator.pages)
|
m = len(paginator.pages)
|
||||||
for n, page in enumerate(paginator.pages, 1):
|
for n, page in enumerate(paginator.pages, 1):
|
||||||
await channel.send(
|
await channel.send(
|
||||||
|
@ -333,31 +273,23 @@ async def bridge(req: Request):
|
||||||
allowed_mentions=discord.AllowedMentions.none(),
|
allowed_mentions=discord.AllowedMentions.none(),
|
||||||
reference=msg,
|
reference=msg,
|
||||||
silent=True,
|
silent=True,
|
||||||
suppress=n != m
|
suppress=n != m,
|
||||||
)
|
)
|
||||||
app.state.last_sender = body["sender"]
|
app.state.last_sender = body["sender"]
|
||||||
else:
|
else:
|
||||||
content = f"**{body['sender']}**:\n>>> {body['message']}"
|
content = f"**{body['sender']}**:\n>>> {body['message']}"
|
||||||
if app.state.last_sender == body["sender"] and ts_diff < 600:
|
if app.state.last_sender == body["sender"] and ts_diff < 600:
|
||||||
content = f">>> {body['message']}"
|
content = f">>> {body['message']}"
|
||||||
await channel.send(
|
await channel.send(content, allowed_mentions=discord.AllowedMentions.none(), silent=True, suppress=False)
|
||||||
content,
|
|
||||||
allowed_mentions=discord.AllowedMentions.none(),
|
|
||||||
silent=True,
|
|
||||||
suppress=False
|
|
||||||
)
|
|
||||||
app.state.last_sender = body["sender"]
|
app.state.last_sender = body["sender"]
|
||||||
app.state.last_sender_ts = now
|
app.state.last_sender_ts = now
|
||||||
return {"status": "ok", "pages": len(paginator.pages)}
|
return {"status": "ok", "pages": len(paginator.pages)}
|
||||||
|
|
||||||
|
|
||||||
@app.websocket('/bridge/recv')
|
@app.websocket("/bridge/recv")
|
||||||
async def bridge_recv(ws: WebSocket, secret: str = Header(None)):
|
async def bridge_recv(ws: WebSocket, secret: str = Header(None)):
|
||||||
if secret != app.state.bot.http.token:
|
if secret != app.state.bot.http.token:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=401, detail="Invalid secret.")
|
||||||
status_code=401,
|
|
||||||
detail="Invalid secret."
|
|
||||||
)
|
|
||||||
queue: asyncio.Queue = app.state.bot.bridge_queue
|
queue: asyncio.Queue = app.state.bot.bridge_queue
|
||||||
|
|
||||||
await ws.accept()
|
await ws.accept()
|
||||||
|
|
Loading…
Reference in a new issue