mirror of
https://github.com/nexy7574/LCC-bot.git
synced 2024-09-20 02:26:32 +01:00
Merge branch 'master' of github.com:EEKIM10/LCC-Bot
This commit is contained in:
commit
71fbcfb2aa
8 changed files with 304 additions and 76 deletions
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="dataSourceStorageLocal" created-in="PY-231.9011.38">
|
<component name="dataSourceStorageLocal" created-in="PY-232.9559.11">
|
||||||
<data-source name="main" uuid="28efee07-d306-4126-bf69-01008b4887e2">
|
<data-source name="main" uuid="28efee07-d306-4126-bf69-01008b4887e2">
|
||||||
<database-info product="SQLite" version="3.39.2" jdbc-version="2.1" driver-name="SQLite JDBC" driver-version="3.39.2.0" dbms="SQLITE" exact-version="3.39.2" exact-driver-version="3.39">
|
<database-info product="SQLite" version="3.39.2" jdbc-version="2.1" driver-name="SQLite JDBC" driver-version="3.39.2.0" dbms="SQLITE" exact-version="3.39.2" exact-driver-version="3.39">
|
||||||
<identifier-quote-string>"</identifier-quote-string>
|
<identifier-quote-string>"</identifier-quote-string>
|
||||||
|
|
BIN
assets/count-to-three.ogg
Normal file
BIN
assets/count-to-three.ogg
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/mine-diamonds.ogg
Normal file
BIN
assets/mine-diamonds.ogg
Normal file
Binary file not shown.
Binary file not shown.
114
cogs/events.py
114
cogs/events.py
|
@ -61,7 +61,10 @@ class Events(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.http = httpx.AsyncClient()
|
self.http = httpx.AsyncClient()
|
||||||
|
if not hasattr(self.bot, "bridge_queue") or self.bot.bridge_queue.empty():
|
||||||
|
self.bot.bridge_queue = asyncio.Queue()
|
||||||
self.fetch_discord_atom_feed.start()
|
self.fetch_discord_atom_feed.start()
|
||||||
|
self.bridge_health = False
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
self.fetch_discord_atom_feed.cancel()
|
self.fetch_discord_atom_feed.cancel()
|
||||||
|
@ -163,7 +166,7 @@ class Events(commands.Cog):
|
||||||
end_line = int(_re.group("end_line")) if _re.group("end_line") else start_line + 1
|
end_line = int(_re.group("end_line")) if _re.group("end_line") else start_line + 1
|
||||||
lines = lines[start_line:end_line]
|
lines = lines[start_line:end_line]
|
||||||
|
|
||||||
paginator = commands.Paginator(prefix="```" + _p[1:], suffix="```", max_size=1000)
|
paginator = commands.Paginator(prefix="```" + _p[1:], suffix="```", max_size=2000)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
paginator.add_line(line)
|
paginator.add_line(line)
|
||||||
|
|
||||||
|
@ -255,7 +258,6 @@ class Events(commands.Cog):
|
||||||
await voice.move_to(message.author.voice.channel)
|
await voice.move_to(message.author.voice.channel)
|
||||||
|
|
||||||
if message.guild.me.voice.self_mute or message.guild.me.voice.mute:
|
if message.guild.me.voice.self_mute or message.guild.me.voice.mute:
|
||||||
await _dc(voice)
|
|
||||||
await message.channel.trigger_typing()
|
await message.channel.trigger_typing()
|
||||||
await message.reply("Unmute me >:(", file=discord.File(_file))
|
await message.reply("Unmute me >:(", file=discord.File(_file))
|
||||||
else:
|
else:
|
||||||
|
@ -291,16 +293,6 @@ class Events(commands.Cog):
|
||||||
await message.reply(file=discord.File(_file))
|
await message.reply(file=discord.File(_file))
|
||||||
return internal
|
return internal
|
||||||
|
|
||||||
async def send_smeg():
|
|
||||||
directory = Path.cwd() / "assets" / "smeg"
|
|
||||||
if directory:
|
|
||||||
choice = random.choice(list(directory.iterdir()))
|
|
||||||
_file = discord.File(
|
|
||||||
choice,
|
|
||||||
filename="%s.%s" % (os.urandom(32).hex(), choice.suffix)
|
|
||||||
)
|
|
||||||
await message.reply(file=_file, delete_after=60)
|
|
||||||
|
|
||||||
async def send_what():
|
async def send_what():
|
||||||
msg = message.reference.cached_message
|
msg = message.reference.cached_message
|
||||||
if not msg:
|
if not msg:
|
||||||
|
@ -326,46 +318,33 @@ class Events(commands.Cog):
|
||||||
)
|
)
|
||||||
await message.reply(_content, allowed_mentions=discord.AllowedMentions.none())
|
await message.reply(_content, allowed_mentions=discord.AllowedMentions.none())
|
||||||
|
|
||||||
async def send_fuck_you() -> str:
|
|
||||||
student = await get_or_none(AccessTokens, user_id=message.author.id)
|
|
||||||
if student.ip_info is None or student.expires >= discord.utils.utcnow().timestamp():
|
|
||||||
if OAUTH_REDIRECT_URI:
|
|
||||||
return f"Let me see who you are, and then we'll talk... <{OAUTH_REDIRECT_URI}>"
|
|
||||||
else:
|
|
||||||
return "I literally don't even know who you are..."
|
|
||||||
else:
|
|
||||||
ip = student.ip_info
|
|
||||||
is_proxy = ip.get("proxy")
|
|
||||||
if is_proxy is None:
|
|
||||||
is_proxy = "?"
|
|
||||||
else:
|
|
||||||
is_proxy = "\N{WHITE HEAVY CHECK MARK}" if is_proxy else "\N{CROSS MARK}"
|
|
||||||
|
|
||||||
is_hosting = ip.get("hosting")
|
|
||||||
if is_hosting is None:
|
|
||||||
is_hosting = "?"
|
|
||||||
else:
|
|
||||||
is_hosting = "\N{WHITE HEAVY CHECK MARK}" if is_hosting else "\N{CROSS MARK}"
|
|
||||||
|
|
||||||
return (
|
|
||||||
"Nice argument, however,\n"
|
|
||||||
"IP: {0[query]}\n"
|
|
||||||
"ISP: {0[isp]}\n"
|
|
||||||
"Latitude: {0[lat]}\n"
|
|
||||||
"Longitude: {0[lon]}\n"
|
|
||||||
"Proxy server: {1}\n"
|
|
||||||
"VPS (or other hosting) provider: {2}\n\n"
|
|
||||||
"\N{smiling face with sunglasses}".format(
|
|
||||||
ip,
|
|
||||||
is_proxy,
|
|
||||||
is_hosting
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not message.guild:
|
if not message.guild:
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.channel.name == "pinboard":
|
if message.channel.name == "femboy-hole":
|
||||||
|
payload = {
|
||||||
|
"author": message.author.name,
|
||||||
|
"avatar": message.author.display_avatar.with_format("png").with_size(512).url,
|
||||||
|
"content": message.content,
|
||||||
|
"at": message.created_at.timestamp(),
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"url": a.url,
|
||||||
|
"filename": a.filename,
|
||||||
|
"size": a.size,
|
||||||
|
"width": a.width,
|
||||||
|
"height": a.height,
|
||||||
|
"content_type": a.content_type,
|
||||||
|
}
|
||||||
|
for a in message.attachments
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if message.author.discriminator != "0":
|
||||||
|
payload["author"] += '#%s' % message.author.discriminator
|
||||||
|
if message.author != self.bot.user and (payload["content"] or payload["attachments"]):
|
||||||
|
await self.bot.bridge_queue.put(payload)
|
||||||
|
|
||||||
|
if message.channel.name == "pinboard" and not message.content.startswith(("#", "//", ";", "h!")):
|
||||||
if message.type == discord.MessageType.pins_add:
|
if message.type == discord.MessageType.pins_add:
|
||||||
await message.delete(delay=0.01)
|
await message.delete(delay=0.01)
|
||||||
else:
|
else:
|
||||||
|
@ -380,10 +359,6 @@ class Events(commands.Cog):
|
||||||
else:
|
else:
|
||||||
assets = Path.cwd() / "assets"
|
assets = Path.cwd() / "assets"
|
||||||
responses: Dict[str | tuple, Dict[str, Any]] = {
|
responses: Dict[str | tuple, Dict[str, Any]] = {
|
||||||
r"ferdi": {
|
|
||||||
"content": "https://ferdi-is.gay/",
|
|
||||||
"delete_after": 15,
|
|
||||||
},
|
|
||||||
r"\bbee(s)*\b": {
|
r"\bbee(s)*\b": {
|
||||||
"content": "https://ferdi-is.gay/bee",
|
"content": "https://ferdi-is.gay/bee",
|
||||||
},
|
},
|
||||||
|
@ -393,6 +368,12 @@ class Events(commands.Cog):
|
||||||
"check": (assets / "it-just-works.ogg").exists
|
"check": (assets / "it-just-works.ogg").exists
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"count to (3|three)": {
|
||||||
|
"func": play_voice(assets / "count-to-three.ogg"),
|
||||||
|
"meta": {
|
||||||
|
"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": {
|
||||||
|
@ -414,7 +395,9 @@ class Events(commands.Cog):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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()))),
|
||||||
|
"delete_after": 30,
|
||||||
"meta": {
|
"meta": {
|
||||||
"sub": {
|
"sub": {
|
||||||
r"pattern": r"([-_.\s\u200b])+",
|
r"pattern": r"([-_.\s\u200b])+",
|
||||||
|
@ -433,16 +416,10 @@ class Events(commands.Cog):
|
||||||
"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"fuck you(\W)*": {
|
|
||||||
"content": send_fuck_you,
|
|
||||||
"meta": {
|
|
||||||
"check": lambda: message.content.startswith(self.bot.user.mention)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
r"mine(ing|d)? (diamonds|away)": {
|
r"mine(ing|d)? (diamonds|away)": {
|
||||||
"func": play_voice(assets / "mine-diamonds.opus"),
|
"func": play_voice(assets / "mine-diamonds.ogg"),
|
||||||
"meta": {
|
"meta": {
|
||||||
"check": (assets / "mine-diamonds.opus").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*)?$": {
|
||||||
|
@ -541,6 +518,9 @@ class Events(commands.Cog):
|
||||||
data[k] = await v()
|
data[k] = await v()
|
||||||
elif callable(v):
|
elif callable(v):
|
||||||
data[k] = v()
|
data[k] = v()
|
||||||
|
if data.get("file") is not None:
|
||||||
|
if not isinstance(data["file"], discord.File):
|
||||||
|
data["file"] = discord.File(data["file"])
|
||||||
data.setdefault("delete_after", 30)
|
data.setdefault("delete_after", 30)
|
||||||
await message.channel.trigger_typing()
|
await message.channel.trigger_typing()
|
||||||
await message.reply(**data)
|
await message.reply(**data)
|
||||||
|
@ -653,13 +633,13 @@ class Events(commands.Cog):
|
||||||
content += "\n\n"
|
content += "\n\n"
|
||||||
|
|
||||||
_status = {
|
_status = {
|
||||||
"Resolved": discord.Color.green(),
|
"resolved": discord.Color.green(),
|
||||||
"Investigating": discord.Color.dark_orange(),
|
"investigating": discord.Color.dark_orange(),
|
||||||
"Identified": discord.Color.orange(),
|
"identified": discord.Color.orange(),
|
||||||
"Monitoring": discord.Color.blurple(),
|
"monitoring": discord.Color.blurple(),
|
||||||
}
|
}
|
||||||
|
|
||||||
colour = _status.get(content.splitlines()[1].split(" - ")[0], discord.Color.greyple())
|
colour = _status.get(content.splitlines()[1].split(" - ")[0].lower(), discord.Color.greyple())
|
||||||
|
|
||||||
if len(content) > 4096:
|
if len(content) > 4096:
|
||||||
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']})"
|
||||||
|
|
169
cogs/other.py
169
cogs/other.py
|
@ -1,16 +1,17 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import functools
|
||||||
import glob
|
import glob
|
||||||
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
import gzip
|
|
||||||
from datetime import timedelta
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -52,10 +53,15 @@ except ImportError:
|
||||||
else:
|
else:
|
||||||
proxies = []
|
proxies = []
|
||||||
|
|
||||||
_engine = pyttsx3.init()
|
try:
|
||||||
# noinspection PyTypeChecker
|
_engine = pyttsx3.init()
|
||||||
VOICES = [x.id for x in _engine.getProperty("voices")]
|
# noinspection PyTypeChecker
|
||||||
del _engine
|
VOICES = [x.id for x in _engine.getProperty("voices")]
|
||||||
|
del _engine
|
||||||
|
except Exception as _pyttsx3_err:
|
||||||
|
print("Failed to load pyttsx3:", _pyttsx3_err, file=sys.stderr)
|
||||||
|
pyttsx3 = None
|
||||||
|
VOICES = []
|
||||||
|
|
||||||
|
|
||||||
def format_autocomplete(ctx: discord.AutocompleteContext):
|
def format_autocomplete(ctx: discord.AutocompleteContext):
|
||||||
|
@ -1121,6 +1127,7 @@ class OtherCog(commands.Cog):
|
||||||
|
|
||||||
async def callback(self, interaction: discord.Interaction):
|
async def callback(self, interaction: discord.Interaction):
|
||||||
def _convert(text: str) -> Tuple[BytesIO, int]:
|
def _convert(text: str) -> Tuple[BytesIO, int]:
|
||||||
|
assert pyttsx3
|
||||||
tmp_dir = tempfile.gettempdir()
|
tmp_dir = tempfile.gettempdir()
|
||||||
target_fn = Path(tmp_dir) / f"jimmy-tts-{ctx.user.id}-{ctx.interaction.id}.mp3"
|
target_fn = Path(tmp_dir) / f"jimmy-tts-{ctx.user.id}-{ctx.interaction.id}.mp3"
|
||||||
target_fn = str(target_fn)
|
target_fn = str(target_fn)
|
||||||
|
@ -1453,6 +1460,8 @@ class OtherCog(commands.Cog):
|
||||||
use_tor: 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 .
|
||||||
|
|
||||||
if re.search(r"\s", username) is not None:
|
if re.search(r"\s", username) is not None:
|
||||||
return await ctx.respond("Username cannot contain spaces.")
|
return await ctx.respond("Username cannot contain spaces.")
|
||||||
|
|
||||||
|
@ -1546,6 +1555,152 @@ class OtherCog(commands.Cog):
|
||||||
)
|
)
|
||||||
shutil.rmtree(tempdir, ignore_errors=True)
|
shutil.rmtree(tempdir, ignore_errors=True)
|
||||||
|
|
||||||
|
@commands.slash_command()
|
||||||
|
@discord.guild_only()
|
||||||
|
async def opusinate(self, ctx: discord.ApplicationContext, file: discord.Attachment, size_mb: float = 8):
|
||||||
|
"""Converts the given file into opus with the given size."""
|
||||||
|
def humanise(v: int) -> str:
|
||||||
|
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
|
||||||
|
while v > 1024:
|
||||||
|
v /= 1024
|
||||||
|
units.pop(0)
|
||||||
|
n = round(v, 2) if v % 1 else v
|
||||||
|
return "%s%s" % (n, units[0])
|
||||||
|
|
||||||
|
await ctx.defer()
|
||||||
|
size_bytes = size_mb * 1024 * 1024
|
||||||
|
max_size = ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024
|
||||||
|
share = False
|
||||||
|
if os.path.exists("/mnt/vol/share/droplet.secret"):
|
||||||
|
share = True
|
||||||
|
|
||||||
|
if size_bytes > max_size or share is False or (share is True and size_mb >= 250):
|
||||||
|
return await ctx.respond(":x: Max file size is %dMB" % round(max_size / 1024 / 1024))
|
||||||
|
|
||||||
|
ct, suffix = file.content_type.split("/")
|
||||||
|
if ct not in ("audio", "video"):
|
||||||
|
return await ctx.respond(":x: Only audio or video please.")
|
||||||
|
with tempfile.NamedTemporaryFile(suffix="." + suffix) as raw_file:
|
||||||
|
location = Path(raw_file.name)
|
||||||
|
location.write_bytes(await file.read(use_cached=False))
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-of",
|
||||||
|
"json",
|
||||||
|
"-show_entries",
|
||||||
|
"format=duration,bit_rate,channels",
|
||||||
|
"-show_streams",
|
||||||
|
"-select_streams",
|
||||||
|
"a", # select audio-nly
|
||||||
|
str(location),
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
if process.returncode != 0:
|
||||||
|
return await ctx.respond(
|
||||||
|
":x: Error gathering metadata.\n```\n%s\n```" % discord.utils.escape_markdown(stderr.decode())
|
||||||
|
)
|
||||||
|
|
||||||
|
metadata = json.loads(stdout.decode())
|
||||||
|
try:
|
||||||
|
stream = metadata["streams"].pop()
|
||||||
|
except IndexError:
|
||||||
|
return await ctx.respond(
|
||||||
|
":x: No audio streams to transcode."
|
||||||
|
)
|
||||||
|
duration = float(metadata["format"]["duration"])
|
||||||
|
bit_rate = math.floor(int(metadata["format"]["bit_rate"]) / 1024)
|
||||||
|
channels = int(stream["channels"])
|
||||||
|
codec = stream["codec_name"]
|
||||||
|
|
||||||
|
target_bitrate = math.floor((size_mb * 8192) / duration)
|
||||||
|
if target_bitrate <= 0:
|
||||||
|
return await ctx.respond(
|
||||||
|
":x: Target size too small (would've had a negative bitrate of %d)" % target_bitrate
|
||||||
|
)
|
||||||
|
br_ceiling = 255 * channels
|
||||||
|
end_br = min(bit_rate, target_bitrate, br_ceiling)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".ogg", prefix=file.filename) as output_file:
|
||||||
|
command = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
str(location),
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-vn",
|
||||||
|
"-sn",
|
||||||
|
"-c:a",
|
||||||
|
"libopus",
|
||||||
|
"-b:a",
|
||||||
|
"%sK" % end_br,
|
||||||
|
"-y",
|
||||||
|
output_file.name
|
||||||
|
]
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
command[0],
|
||||||
|
*command[1:],
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
return await ctx.respond(
|
||||||
|
":x: There was an error while transcoding:\n```\n%s\n```" % discord.utils.escape_markdown(
|
||||||
|
stderr.decode()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
output_location = Path(output_file.name)
|
||||||
|
stat = output_location.stat()
|
||||||
|
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"
|
||||||
|
"* Duration: %.1f seconds\n* Input size: %s\n* Output size: %s\n* Difference: %s"
|
||||||
|
" (%dKbps)") % (
|
||||||
|
codec,
|
||||||
|
end_br,
|
||||||
|
bit_rate,
|
||||||
|
target_bitrate,
|
||||||
|
br_ceiling,
|
||||||
|
end_br,
|
||||||
|
duration,
|
||||||
|
humanise(file.size),
|
||||||
|
humanise(stat.st_size),
|
||||||
|
humanise(file.size - stat.st_size, )
|
||||||
|
)
|
||||||
|
if stat.st_size <= max_size or share is False:
|
||||||
|
if stat.st_size >= (size_bytes - 100):
|
||||||
|
return await ctx.respond(
|
||||||
|
":x: File was too large."
|
||||||
|
)
|
||||||
|
return await ctx.respond(
|
||||||
|
content,
|
||||||
|
file=discord.File(output_location)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
share_location = Path("/mnt/vol/share/tmp/") / output_location.name
|
||||||
|
share_location.touch(0o755)
|
||||||
|
await self.bot.loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
functools.partial(
|
||||||
|
shutil.copy,
|
||||||
|
output_location,
|
||||||
|
share_location
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return await ctx.respond(
|
||||||
|
"%s\n* [Download](https://droplet.nexy7574.co.uk/share/tmp/%s)" % (
|
||||||
|
content,
|
||||||
|
output_location.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(OtherCog(bot))
|
bot.add_cog(OtherCog(bot))
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import os
|
import os
|
||||||
|
@ -8,9 +10,12 @@ 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
|
from fastapi import FastAPI, HTTPException, Request, Header
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
|
from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from starlette.websockets import WebSocket, WebSocketDisconnect
|
||||||
|
|
||||||
from utils import Student, get_or_none, VerifyCode, console, BannedStudentID
|
from utils import Student, get_or_none, VerifyCode, console, BannedStudentID
|
||||||
from utils.db import AccessTokens
|
from utils.db import AccessTokens
|
||||||
from config import guilds
|
from config import guilds
|
||||||
|
@ -48,6 +53,8 @@ try:
|
||||||
app.state.bot = bot
|
app.state.bot = bot
|
||||||
except ImportError:
|
except ImportError:
|
||||||
bot = None
|
bot = None
|
||||||
|
app.state.last_sender = None
|
||||||
|
app.state.last_sender_ts = datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
|
@ -281,3 +288,89 @@ async def verify(code: str):
|
||||||
GENERAL,
|
GENERAL,
|
||||||
status_code=308
|
status_code=308
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/bridge", include_in_schema=False, status_code=201)
|
||||||
|
async def bridge(req: Request):
|
||||||
|
now = datetime.utcnow()
|
||||||
|
ts_diff = (now - app.state.last_sender_ts).total_seconds()
|
||||||
|
from discord.ext.commands import Paginator
|
||||||
|
body = await req.json()
|
||||||
|
if body["secret"] != app.state.bot.http.token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Invalid secret."
|
||||||
|
)
|
||||||
|
|
||||||
|
channel = app.state.bot.get_channel(1032974266527907901) # type: discord.TextChannel | None
|
||||||
|
if not channel:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="Channel does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(body["message"]) > 6000:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Message too long."
|
||||||
|
)
|
||||||
|
paginator = Paginator(prefix="", suffix="", max_size=1990)
|
||||||
|
for line in body["message"].splitlines():
|
||||||
|
try:
|
||||||
|
paginator.add_line(line)
|
||||||
|
except ValueError:
|
||||||
|
paginator.add_line(textwrap.shorten(line, width=1900, placeholder="<...>"))
|
||||||
|
if len(paginator.pages) > 1:
|
||||||
|
msg = None
|
||||||
|
if app.state.last_sender != body["sender"] or ts_diff >= 600:
|
||||||
|
msg = await channel.send(
|
||||||
|
f"**{body['sender']}**:"
|
||||||
|
)
|
||||||
|
m = len(paginator.pages)
|
||||||
|
for n, page in enumerate(paginator.pages, 1):
|
||||||
|
await channel.send(
|
||||||
|
f"[{n}/{m}]\n>>> {page}",
|
||||||
|
allowed_mentions=discord.AllowedMentions.none(),
|
||||||
|
reference=msg,
|
||||||
|
silent=True,
|
||||||
|
suppress=n != m
|
||||||
|
)
|
||||||
|
app.state.last_sender = body["sender"]
|
||||||
|
else:
|
||||||
|
content = f"**{body['sender']}**:\n>>> {body['message']}"
|
||||||
|
if app.state.last_sender == body["sender"] and ts_diff < 600:
|
||||||
|
content = f">>> {body['message']}"
|
||||||
|
await channel.send(
|
||||||
|
content,
|
||||||
|
allowed_mentions=discord.AllowedMentions.none(),
|
||||||
|
silent=True,
|
||||||
|
suppress=False
|
||||||
|
)
|
||||||
|
app.state.last_sender = body["sender"]
|
||||||
|
app.state.last_sender_ts = now
|
||||||
|
return {"status": "ok", "pages": len(paginator.pages)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.websocket('/bridge/recv')
|
||||||
|
async def bridge_recv(ws: WebSocket, secret: str = Header(None)):
|
||||||
|
if secret != app.state.bot.http.token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Invalid secret."
|
||||||
|
)
|
||||||
|
queue: asyncio.Queue = app.state.bot.bridge_queue
|
||||||
|
|
||||||
|
await ws.accept()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = queue.get_nowait()
|
||||||
|
except asyncio.QueueEmpty:
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ws.send_json(data)
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
queue.task_done()
|
||||||
|
|
Loading…
Reference in a new issue