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
7394f90bfc
9 changed files with 388 additions and 499 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.8770.66">
|
<component name="dataSourceStorageLocal" created-in="PY-231.9011.38">
|
||||||
<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>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.11 (LCC-bot)" jdkType="Python SDK" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
BIN
assets/virgin/14330-ping.png
Normal file
BIN
assets/virgin/14330-ping.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
assets/visio.png
Normal file
BIN
assets/visio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
|
@ -19,6 +19,7 @@ import httpx
|
||||||
from discord.ext import commands, pages, tasks
|
from discord.ext import commands, pages, tasks
|
||||||
from utils import Student, get_or_none, console
|
from utils import Student, get_or_none, console
|
||||||
from config import guilds
|
from config import guilds
|
||||||
|
from utils.db import AccessTokens
|
||||||
try:
|
try:
|
||||||
from config import dev
|
from config import dev
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -325,21 +326,13 @@ 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():
|
async def send_fuck_you() -> str:
|
||||||
student = await get_or_none(Student, user_id=message.author.id)
|
student = await get_or_none(AccessTokens, user_id=message.author.id)
|
||||||
if student is None:
|
if student.ip_info is None or student.expires >= discord.utils.utcnow().timestamp():
|
||||||
return await message.reply("You aren't even verified...", delete_after=10)
|
|
||||||
elif student.ip_info is None:
|
|
||||||
if OAUTH_REDIRECT_URI:
|
if OAUTH_REDIRECT_URI:
|
||||||
return await message.reply(
|
return f"Let me see who you are, and then we'll talk... <{OAUTH_REDIRECT_URI}>"
|
||||||
f"Let me see who you are, and then we'll talk... <{OAUTH_REDIRECT_URI}>",
|
|
||||||
delete_after=30
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return await message.reply(
|
return "I literally don't even know who you are..."
|
||||||
"I literally don't even know who you are...",
|
|
||||||
delete_after=10
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
ip = student.ip_info
|
ip = student.ip_info
|
||||||
is_proxy = ip.get("proxy")
|
is_proxy = ip.get("proxy")
|
||||||
|
@ -354,7 +347,7 @@ class Events(commands.Cog):
|
||||||
else:
|
else:
|
||||||
is_hosting = "\N{WHITE HEAVY CHECK MARK}" if is_hosting else "\N{CROSS MARK}"
|
is_hosting = "\N{WHITE HEAVY CHECK MARK}" if is_hosting else "\N{CROSS MARK}"
|
||||||
|
|
||||||
return await message.reply(
|
return (
|
||||||
"Nice argument, however,\n"
|
"Nice argument, however,\n"
|
||||||
"IP: {0[query]}\n"
|
"IP: {0[query]}\n"
|
||||||
"ISP: {0[isp]}\n"
|
"ISP: {0[isp]}\n"
|
||||||
|
@ -366,8 +359,7 @@ class Events(commands.Cog):
|
||||||
ip,
|
ip,
|
||||||
is_proxy,
|
is_proxy,
|
||||||
is_hosting
|
is_hosting
|
||||||
),
|
)
|
||||||
delete_after=30
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not message.guild:
|
if not message.guild:
|
||||||
|
@ -396,9 +388,9 @@ class Events(commands.Cog):
|
||||||
"content": "https://ferdi-is.gay/bee",
|
"content": "https://ferdi-is.gay/bee",
|
||||||
},
|
},
|
||||||
r"it just works": {
|
r"it just works": {
|
||||||
"func": play_voice(assets / "it-just-works.mp3"),
|
"func": play_voice(assets / "it-just-works.ogg"),
|
||||||
"meta": {
|
"meta": {
|
||||||
"check": (assets / "it-just-works.mp3").exists
|
"check": (assets / "it-just-works.ogg").exists
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
r"^linux$": {
|
r"^linux$": {
|
||||||
|
@ -442,7 +434,7 @@ class Events(commands.Cog):
|
||||||
"delete_after": None
|
"delete_after": None
|
||||||
},
|
},
|
||||||
r"fuck you(\W)*": {
|
r"fuck you(\W)*": {
|
||||||
"func": send_fuck_you,
|
"content": send_fuck_you,
|
||||||
"meta": {
|
"meta": {
|
||||||
"check": lambda: message.content.startswith(self.bot.user.mention)
|
"check": lambda: message.content.startswith(self.bot.user.mention)
|
||||||
}
|
}
|
||||||
|
@ -453,7 +445,7 @@ class Events(commands.Cog):
|
||||||
"check": (assets / "mine-diamonds.opus").exists
|
"check": (assets / "mine-diamonds.opus").exists
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
r"v[ei]r[mg]in(\sme(d|m[a]?)ia\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()))
|
||||||
|
@ -461,7 +453,22 @@ class Events(commands.Cog):
|
||||||
"meta": {
|
"meta": {
|
||||||
"check": (assets / 'virgin').exists
|
"check": (assets / 'virgin').exists
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
r"richard|(dick\W*$)": {
|
||||||
|
"file": discord.File(assets / "visio.png"),
|
||||||
|
"meta": {
|
||||||
|
"check": (assets / "visio.png").exists
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
r"thank(\syou|s)(,)? jimmy": {
|
||||||
|
"content": "You're welcome, %s!" % message.author.mention,
|
||||||
|
},
|
||||||
|
r"(ok )?jimmy (we|i) g[eo]t it": {
|
||||||
|
"content": "No need to be so rude! Cunt.",
|
||||||
|
},
|
||||||
|
r"c(mon|ome on) jimmy": {
|
||||||
|
"content": "IM TRYING"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
# Stop responding to any bots
|
# Stop responding to any bots
|
||||||
if message.author.bot is True:
|
if message.author.bot is True:
|
||||||
|
@ -516,7 +523,7 @@ class Events(commands.Cog):
|
||||||
|
|
||||||
if "func" in data:
|
if "func" in data:
|
||||||
try:
|
try:
|
||||||
if inspect.iscoroutinefunction(data["func"]):
|
if inspect.iscoroutinefunction(data["func"]) or inspect.iscoroutine(data["func"]):
|
||||||
await data["func"]()
|
await data["func"]()
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -527,9 +534,12 @@ class Events(commands.Cog):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
for k, v in data.copy().items():
|
for k, v in data.copy().items():
|
||||||
if callable(v):
|
if inspect.iscoroutinefunction(data[k]) or inspect.iscoroutine(data[k]):
|
||||||
|
data[k] = await v()
|
||||||
|
elif callable(v):
|
||||||
data[k] = v()
|
data[k] = v()
|
||||||
data.setdefault("delete_after", 30)
|
data.setdefault("delete_after", 30)
|
||||||
|
await message.channel.trigger_typing()
|
||||||
await message.reply(**data)
|
await message.reply(**data)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
255
cogs/info.py
255
cogs/info.py
|
@ -1,195 +1,112 @@
|
||||||
import asyncio
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import humanize
|
import httpx
|
||||||
import psutil
|
|
||||||
from functools import partial
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from pathlib import Path
|
from utils import get_or_none
|
||||||
|
from utils.db import AccessTokens
|
||||||
|
try:
|
||||||
|
from config import OAUTH_ID, OAUTH_REDIRECT_URI
|
||||||
|
except ImportError:
|
||||||
|
OAUTH_REDIRECT_URI = OAUTH_ID = None
|
||||||
|
|
||||||
|
|
||||||
class InfoCog(commands.Cog):
|
class InfoCog(commands.Cog):
|
||||||
EMOJIS = {
|
|
||||||
"CPU": "\N{brain}",
|
|
||||||
"RAM": "\N{ram}",
|
|
||||||
"SWAP": "\N{swan}",
|
|
||||||
"DISK": "\N{minidisc}",
|
|
||||||
"NETWORK": "\N{satellite antenna}",
|
|
||||||
"SENSORS": "\N{thermometer}",
|
|
||||||
"UPTIME": "\N{alarm clock}",
|
|
||||||
"ON": "\N{large green circle}",
|
|
||||||
"OFF": "\N{large red circle}",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
self.client = httpx.AsyncClient(base_url="https://discord.com/api")
|
||||||
|
|
||||||
async def run_subcommand(self, *args: str):
|
async def get_user_info(self, token: str):
|
||||||
"""Runs a command in a shell in the background, asynchronously, returning status, stdout, and stderr."""
|
try:
|
||||||
proc = await asyncio.create_subprocess_exec(
|
response = await self.client.get("/users/@me", headers={"Authorization": f"Bearer {token}"})
|
||||||
*args,
|
response.raise_for_status()
|
||||||
stdout=asyncio.subprocess.PIPE,
|
except (httpx.HTTPError, httpx.RequestError, ConnectionError):
|
||||||
stderr=asyncio.subprocess.PIPE,
|
return
|
||||||
loop=self.bot.loop,
|
return response.json()
|
||||||
)
|
|
||||||
stdout, stderr = await proc.communicate()
|
|
||||||
return proc.returncode, stdout.decode("utf-8", "replace"), stderr.decode("utf-8", "replace")
|
|
||||||
|
|
||||||
async def unblock(self, function: callable, *args, **kwargs):
|
async def get_user_guilds(self, token: str):
|
||||||
"""Runs a function in the background, asynchronously, returning the result."""
|
try:
|
||||||
return await self.bot.loop.run_in_executor(None, partial(function, *args, **kwargs))
|
response = await self.client.get("/users/@me/guilds", headers={"Authorization": f"Bearer {token}"})
|
||||||
|
response.raise_for_status()
|
||||||
|
except (httpx.HTTPError, httpx.RequestError, ConnectionError):
|
||||||
|
return
|
||||||
|
return response.json()
|
||||||
|
|
||||||
@staticmethod
|
async def get_user_connections(self, token: str):
|
||||||
def bar_fill(
|
try:
|
||||||
filled: int,
|
response = await self.client.get("/users/@me/connections", headers={"Authorization": f"Bearer {token}"})
|
||||||
total: int,
|
response.raise_for_status()
|
||||||
bar_width: int = 10,
|
except (httpx.HTTPError, httpx.RequestError, ConnectionError):
|
||||||
char: str = "\N{black large square}",
|
return
|
||||||
unfilled_char: str = "\N{white large square}"
|
return response.json()
|
||||||
):
|
|
||||||
"""Returns a progress bar with the given length and fill character."""
|
|
||||||
if filled == 0:
|
|
||||||
filled = 0.01
|
|
||||||
if total == 0:
|
|
||||||
total = 0.01
|
|
||||||
percent = filled / total
|
|
||||||
to_fill = int(bar_width * percent)
|
|
||||||
return f"{char * to_fill}{unfilled_char * (bar_width - to_fill)}"
|
|
||||||
|
|
||||||
@commands.slash_command(name="system-info")
|
@commands.slash_command()
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
@commands.cooldown(1, 10, commands.BucketType.user)
|
||||||
async def system_info(self, ctx: discord.ApplicationContext):
|
async def me(self, ctx: discord.ApplicationCommand):
|
||||||
"""Gather statistics on the current host."""
|
"""Displays oauth info about you"""
|
||||||
bar_emojis = {
|
|
||||||
75: "\N{large yellow square}",
|
|
||||||
80: "\N{large orange square}",
|
|
||||||
90: "\N{large red square}",
|
|
||||||
}
|
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
root_drive = Path(__file__).root
|
|
||||||
temperature = fans = {}
|
|
||||||
binary = os.name != "nt"
|
|
||||||
|
|
||||||
# Gather statistics
|
|
||||||
start = time.time()
|
|
||||||
cpu: List[float] = await self.unblock(psutil.cpu_percent, interval=1.0, percpu=True)
|
|
||||||
ram = await self.unblock(psutil.virtual_memory)
|
|
||||||
swap = await self.unblock(psutil.swap_memory)
|
|
||||||
disk = await self.unblock(psutil.disk_usage, root_drive)
|
|
||||||
network = await self.unblock(psutil.net_io_counters)
|
|
||||||
if getattr(psutil, "sensors_temperatures", None):
|
|
||||||
temperature = await self.unblock(psutil.sensors_temperatures)
|
|
||||||
if getattr(psutil, "sensors_fans", None):
|
|
||||||
fans = await self.unblock(psutil.sensors_fans)
|
|
||||||
uptime = datetime.datetime.fromtimestamp(await self.unblock(psutil.boot_time), datetime.timezone.utc)
|
|
||||||
end = time.time()
|
|
||||||
|
|
||||||
|
user = await get_or_none(AccessTokens, user_id=ctx.author.id)
|
||||||
|
if not user:
|
||||||
|
url = OAUTH_REDIRECT_URI
|
||||||
|
return await ctx.respond(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title="You must link your account first!",
|
||||||
|
description="Don't worry, [I only store your IP information. And the access token.](%s)" % url,
|
||||||
|
url=url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
user_data = await self.get_user_info(user.access_token)
|
||||||
|
guilds = await self.get_user_guilds(user.access_token)
|
||||||
|
connections = await self.get_user_connections(user.access_token)
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="System Statistics",
|
title="Your info",
|
||||||
description=f"Collected in {humanize.precisedelta(datetime.timedelta(seconds=end - start))}.",
|
)
|
||||||
color=discord.Color.blurple(),
|
if user_data:
|
||||||
|
for field in ("bot", "system", "mfa_enabled", "banner", "accent_color", "mfa_enabled", "locale",
|
||||||
|
"verified", "email", "flags", "premium_type", "public_flags"):
|
||||||
|
user_data.setdefault(field, None)
|
||||||
|
lines = [
|
||||||
|
"ID: {0[id]}",
|
||||||
|
"Username: {0[username]}",
|
||||||
|
"Discriminator: #{0[discriminator]}",
|
||||||
|
"Avatar: {0[avatar]}",
|
||||||
|
"Bot: {0[bot]}",
|
||||||
|
"System: {0[system]}",
|
||||||
|
"MFA Enabled: {0[mfa_enabled]}",
|
||||||
|
"Banner: {0[banner]}",
|
||||||
|
"Banner Color: {0[banner_color]}",
|
||||||
|
"Locale: {0[locale]}",
|
||||||
|
"Email Verified: {0[verified]}",
|
||||||
|
"Email: {1}",
|
||||||
|
"Flags: {0[flags]}",
|
||||||
|
"Premium Type: {0[premium_type]}",
|
||||||
|
"Public Flags: {0[public_flags]}",
|
||||||
|
]
|
||||||
|
email = user_data["email"]
|
||||||
|
if email:
|
||||||
|
email = email.replace("@", "\u200b@\u200b").replace(".", "\u200b.\u200b")
|
||||||
|
embed.add_field(
|
||||||
|
name="User Info",
|
||||||
|
value="\n".join(lines).format(user_data, email),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Format statistics
|
if guilds:
|
||||||
per_core = "\n".join(f"{i}: {c:.2f}%" for i, c in enumerate(cpu))
|
guilds = sorted(guilds, key=lambda x: x["name"])
|
||||||
total_cpu = sum(cpu)
|
|
||||||
pct = total_cpu / len(cpu)
|
|
||||||
cpu_bar_emoji = "\N{large green square}"
|
|
||||||
for threshold, emoji in bar_emojis.items():
|
|
||||||
if pct >= threshold:
|
|
||||||
cpu_bar_emoji = emoji
|
|
||||||
|
|
||||||
bar = self.bar_fill(sum(cpu), len(cpu) * 100, 16, cpu_bar_emoji, "\u2581")
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name=f"{self.EMOJIS['CPU']} CPU",
|
name="Guilds (%d):" % len(guilds),
|
||||||
value=f"**Usage:** {sum(cpu):.2f}%\n"
|
value="\n".join(f"{guild['name']} ({guild['id']})" for guild in guilds),
|
||||||
f"**Cores:** {len(cpu)}\n"
|
|
||||||
f"**Usage Per Core:**\n{per_core}\n"
|
|
||||||
f"{bar}",
|
|
||||||
inline=False,
|
|
||||||
)
|
|
||||||
if "coretemp" in temperature:
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['SENSORS']} Temperature (coretemp)",
|
|
||||||
value="\n".join(f"{s.label}: {s.current:.2f}°C" for s in temperature["coretemp"]),
|
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
elif "acpitz" in temperature:
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['SENSORS']} Temperature (acpitz)",
|
|
||||||
value="\n".join(f"{s.label}: {s.current:.2f}°C" for s in temperature["acpitz"]),
|
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
elif "cpu_thermal" in temperature:
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['SENSORS']} Temperature (cpu_thermal)",
|
|
||||||
value="\n".join(f"{s.label}: {s.current:.2f}°C" for s in temperature["cpu_thermal"]),
|
|
||||||
inline=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if fans:
|
if connections:
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name=f"{self.EMOJIS['SENSORS']} Fans",
|
name="Connections (%d):" % len(connections),
|
||||||
value="\n".join(f"{s.label}: {s.current:.2f} RPM" for s in fans),
|
value="\n".join(f"{connection['type'].title()} ({connection['id']})" for connection in connections),
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
if Path("/tmp/fanstate").exists():
|
|
||||||
with open("/tmp/fanstate", "r") as f:
|
|
||||||
fan_active = f.read().strip() == "1"
|
|
||||||
LED_colour = "unknown"
|
|
||||||
fan_state = f"{self.EMOJIS['OFF']} Inactive"
|
|
||||||
if fan_active:
|
|
||||||
fan_state = f"{self.EMOJIS['ON']} Active"
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['SENSORS']} Fan",
|
|
||||||
value=f"{fan_state} (LED: #{LED_colour})",
|
|
||||||
inline=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
await ctx.respond(embed=embed)
|
||||||
name=f"{self.EMOJIS['RAM']} RAM",
|
|
||||||
value=f"**Usage:** {ram.percent}%\n"
|
|
||||||
f"**Total:** {humanize.naturalsize(ram.total, binary=binary)}\n"
|
|
||||||
f"**Available:** {humanize.naturalsize(ram.available, binary=binary)}",
|
|
||||||
inline=False,
|
|
||||||
)
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['SWAP']} Swap",
|
|
||||||
value=f"**Usage:** {swap.percent}%\n"
|
|
||||||
f"**Total:** {humanize.naturalsize(swap.total, binary=binary)}\n"
|
|
||||||
f"**Free:** {humanize.naturalsize(swap.free, binary=binary)}\n"
|
|
||||||
f"**Used:** {humanize.naturalsize(swap.used, binary=binary)}",
|
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['DISK']} Disk ({root_drive})",
|
|
||||||
value=f"**Usage:** {disk.percent}%\n"
|
|
||||||
f"**Total:** {humanize.naturalsize(disk.total, binary=binary)}\n"
|
|
||||||
f"**Free:** {humanize.naturalsize(disk.free, binary=binary)}\n"
|
|
||||||
f"**Used:** {humanize.naturalsize(disk.used, binary=binary)}",
|
|
||||||
inline=False,
|
|
||||||
)
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['NETWORK']} Network",
|
|
||||||
value=f"**Sent:** {humanize.naturalsize(network.bytes_sent, binary=binary)}\n"
|
|
||||||
f"**Received:** {humanize.naturalsize(network.bytes_recv, binary=binary)}",
|
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
embed.add_field(
|
|
||||||
name=f"{self.EMOJIS['UPTIME']} Uptime",
|
|
||||||
value=f"Booted {discord.utils.format_dt(uptime, 'R')}"
|
|
||||||
f"({humanize.precisedelta(datetime.datetime.now(datetime.timezone.utc) - uptime)})",
|
|
||||||
inline=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
await ctx.edit(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
|
if OAUTH_REDIRECT_URI and OAUTH_ID:
|
||||||
bot.add_cog(InfoCog(bot))
|
bot.add_cog(InfoCog(bot))
|
||||||
|
else:
|
||||||
|
print("OAUTH_REDIRECT_URI not set, not loading info cog")
|
||||||
|
|
488
cogs/other.py
488
cogs/other.py
|
@ -2,6 +2,7 @@ import asyncio
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -711,9 +712,9 @@ class OtherCog(commands.Cog):
|
||||||
window_width = max(min(1080 * 6, window_width), 1080 // 6)
|
window_width = max(min(1080 * 6, window_width), 1080 // 6)
|
||||||
window_height = max(min(1920 * 6, window_height), 1920 // 6)
|
window_height = max(min(1920 * 6, window_height), 1920 // 6)
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
if ctx.user.id == 1019233057519177778 and ctx.me.guild_permissions.moderate_members:
|
# if ctx.user.id == 1019233057519177778 and ctx.me.guild_permissions.moderate_members:
|
||||||
if ctx.user.communication_disabled_until is None:
|
# if ctx.user.communication_disabled_until is None:
|
||||||
await ctx.user.timeout_for(timedelta(minutes=2), reason="no")
|
# await ctx.user.timeout_for(timedelta(minutes=2), reason="no")
|
||||||
url = urlparse(url)
|
url = urlparse(url)
|
||||||
if not url.scheme:
|
if not url.scheme:
|
||||||
if "/" in url.path:
|
if "/" in url.path:
|
||||||
|
@ -846,255 +847,6 @@ class OtherCog(commands.Cog):
|
||||||
await blacklist.write(line)
|
await blacklist.write(line)
|
||||||
await ctx.respond("Removed domain from blacklist.")
|
await ctx.respond("Removed domain from blacklist.")
|
||||||
|
|
||||||
# noinspection PyTypeHints
|
|
||||||
@commands.slash_command(name="yt-dl")
|
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
|
||||||
async def yt_dl(
|
|
||||||
self,
|
|
||||||
ctx: discord.ApplicationContext,
|
|
||||||
url: str,
|
|
||||||
video_format: discord.Option(
|
|
||||||
description="The format to download the video in.",
|
|
||||||
autocomplete=format_autocomplete,
|
|
||||||
default=""
|
|
||||||
) = "",
|
|
||||||
upload_log: bool = True,
|
|
||||||
list_formats: bool = False,
|
|
||||||
proxy_mode: discord.Option(
|
|
||||||
str,
|
|
||||||
choices=[
|
|
||||||
"No Proxy",
|
|
||||||
"Dedicated Proxy",
|
|
||||||
"Random Public Proxy"
|
|
||||||
],
|
|
||||||
description="Only use if a download was blocked or 403'd.",
|
|
||||||
default="No Proxy",
|
|
||||||
) = "No Proxy",
|
|
||||||
):
|
|
||||||
"""Downloads a video from <URL> using youtube-dl"""
|
|
||||||
use_proxy = ["No Proxy", "Dedicated Proxy", "Random Public Proxy"].index(proxy_mode)
|
|
||||||
embed = discord.Embed(
|
|
||||||
description="Loading..."
|
|
||||||
)
|
|
||||||
embed.set_thumbnail(url="https://cdn.discordapp.com/emojis/1101463077586735174.gif?v=1")
|
|
||||||
await ctx.defer()
|
|
||||||
|
|
||||||
await ctx.respond(embed=embed)
|
|
||||||
if list_formats:
|
|
||||||
# Nothing actually downloads here
|
|
||||||
try:
|
|
||||||
formats = await self.list_formats(url, use_proxy=use_proxy)
|
|
||||||
except FileNotFoundError:
|
|
||||||
_embed = embed.copy()
|
|
||||||
_embed.description = "yt-dlp not found."
|
|
||||||
_embed.colour = discord.Colour.red()
|
|
||||||
_embed.set_thumbnail(url=discord.Embed.Empty)
|
|
||||||
return await ctx.edit(embed=_embed)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
_embed = embed.copy()
|
|
||||||
_embed.description = "Unable to find formats. You're on your own. Wing it."
|
|
||||||
_embed.colour = discord.Colour.red()
|
|
||||||
_embed.set_thumbnail(url=discord.Embed.Empty)
|
|
||||||
return await ctx.edit(embed=_embed)
|
|
||||||
else:
|
|
||||||
embeds = []
|
|
||||||
for fmt in formats.keys():
|
|
||||||
fs = formats[fmt]["filesize"] or 0.1
|
|
||||||
if fs == float("inf"):
|
|
||||||
fs = 0
|
|
||||||
units = ["B"]
|
|
||||||
else:
|
|
||||||
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
||||||
while fs > 1024:
|
|
||||||
fs /= 1024
|
|
||||||
units.pop(0)
|
|
||||||
embeds.append(
|
|
||||||
discord.Embed(
|
|
||||||
title=fmt,
|
|
||||||
description="- Encoding: {0[vcodec]} + {0[acodec]}\n"
|
|
||||||
"- Extension: `.{0[ext]}`\n"
|
|
||||||
"- Resolution: {0[resolution]}\n"
|
|
||||||
"- Filesize: {1}\n"
|
|
||||||
"- Protocol: {0[protocol]}\n".format(formats[fmt], f"{round(fs, 2)}{units[0]}"),
|
|
||||||
colour=discord.Colour.blurple()
|
|
||||||
).add_field(
|
|
||||||
name="Download:",
|
|
||||||
value="{} url:{} video_format:{}".format(
|
|
||||||
self.bot.get_application_command("yt-dl").mention,
|
|
||||||
url,
|
|
||||||
fmt
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_paginator = pages.Paginator(embeds, loop_pages=True)
|
|
||||||
await ctx.delete(delay=0.1)
|
|
||||||
return await _paginator.respond(ctx.interaction)
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory(prefix="jimmy-ytdl-") as tempdir:
|
|
||||||
video_format = video_format.lower()
|
|
||||||
MAX_SIZE = round(ctx.guild.filesize_limit / 1024 / 1024)
|
|
||||||
if MAX_SIZE == 8:
|
|
||||||
MAX_SIZE = 25
|
|
||||||
options = [
|
|
||||||
"--no-colors",
|
|
||||||
"--no-playlist",
|
|
||||||
"--no-check-certificates",
|
|
||||||
"--no-warnings",
|
|
||||||
"--newline",
|
|
||||||
"--restrict-filenames",
|
|
||||||
"--output",
|
|
||||||
f"{ctx.user.id}.%(title)s.%(ext)s",
|
|
||||||
]
|
|
||||||
if video_format:
|
|
||||||
options.extend(["--format", f"({video_format})[filesize<={MAX_SIZE}M]"])
|
|
||||||
else:
|
|
||||||
options.extend(["--format", f"(bv*+ba/b/ba)[filesize<={MAX_SIZE}M]"])
|
|
||||||
|
|
||||||
if use_proxy == 1 and proxy:
|
|
||||||
options.append("--proxy")
|
|
||||||
options.append(proxy)
|
|
||||||
console.log("yt-dlp using proxy: %r", proxy)
|
|
||||||
elif use_proxy == 2 and proxies:
|
|
||||||
options.append("--proxy")
|
|
||||||
options.append(random.choice(proxies))
|
|
||||||
console.log("yt-dlp using random proxy: %r", options[-1])
|
|
||||||
|
|
||||||
_embed = embed.copy()
|
|
||||||
_embed.description = "Downloading..."
|
|
||||||
_embed.colour = discord.Colour.blurple()
|
|
||||||
await ctx.edit(
|
|
||||||
embed=_embed,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
venv = Path.cwd() / "venv" / ("Scripts" if os.name == "nt" else "bin")
|
|
||||||
if venv:
|
|
||||||
venv = venv.absolute().resolve()
|
|
||||||
if str(venv) not in os.environ["PATH"]:
|
|
||||||
os.environ["PATH"] += os.pathsep + str(venv)
|
|
||||||
|
|
||||||
process = await asyncio.create_subprocess_exec(
|
|
||||||
"yt-dlp",
|
|
||||||
url,
|
|
||||||
*options,
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE,
|
|
||||||
cwd=Path(tempdir).resolve()
|
|
||||||
)
|
|
||||||
async with ctx.channel.typing():
|
|
||||||
stdout, stderr = await process.communicate()
|
|
||||||
stdout_log = io.BytesIO(stdout)
|
|
||||||
stdout_log_file = discord.File(stdout_log, filename="stdout.txt")
|
|
||||||
stderr_log = io.BytesIO(stderr)
|
|
||||||
stderr_log_file = discord.File(stderr_log, filename="stderr.txt")
|
|
||||||
await process.wait()
|
|
||||||
except FileNotFoundError:
|
|
||||||
return await ctx.edit(
|
|
||||||
embed=discord.Embed(
|
|
||||||
description="Downloader not found.",
|
|
||||||
color=discord.Color.red()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if process.returncode != 0:
|
|
||||||
files = [
|
|
||||||
stdout_log_file,
|
|
||||||
stderr_log_file
|
|
||||||
]
|
|
||||||
if b"format is not available" in stderr:
|
|
||||||
formats = await self.list_formats(url)
|
|
||||||
embeds = []
|
|
||||||
for fmt in formats.keys():
|
|
||||||
fs = formats[fmt]["filesize"] or 0.1
|
|
||||||
if fs == float("inf"):
|
|
||||||
fs = 0
|
|
||||||
units = ["B"]
|
|
||||||
else:
|
|
||||||
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
||||||
while fs > 1024:
|
|
||||||
fs /= 1024
|
|
||||||
units.pop(0)
|
|
||||||
embeds.append(
|
|
||||||
discord.Embed(
|
|
||||||
title=fmt,
|
|
||||||
description="- Encoding: {0[vcodec]} + {0[acodec]}\n"
|
|
||||||
"- Extension: `.{0[ext]}`\n"
|
|
||||||
"- Resolution: {0[resolution]}\n"
|
|
||||||
"- Filesize: {1}\n"
|
|
||||||
"- Protocol: {0[protocol]}\n".format(formats[fmt],
|
|
||||||
f"{round(fs, 2)}{units[0]}"),
|
|
||||||
colour=discord.Colour.blurple()
|
|
||||||
).add_field(
|
|
||||||
name="Download:",
|
|
||||||
value="{} url:{} video_format:{}".format(
|
|
||||||
self.bot.get_application_command("yt-dl").mention,
|
|
||||||
url,
|
|
||||||
fmt
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_paginator = pages.Paginator(embeds, loop_pages=True)
|
|
||||||
await ctx.delete(delay=0.1)
|
|
||||||
return await _paginator.respond(ctx.interaction)
|
|
||||||
return await ctx.edit(content=f"Download failed:\n```\n{stderr.decode()}\n```", files=files)
|
|
||||||
|
|
||||||
_embed = embed.copy()
|
|
||||||
_embed.description = "Download complete."
|
|
||||||
_embed.colour = discord.Colour.green()
|
|
||||||
_embed.set_thumbnail(url=discord.Embed.Empty)
|
|
||||||
await ctx.edit(embed=_embed)
|
|
||||||
files = [
|
|
||||||
stdout_log_file,
|
|
||||||
stderr_log_file
|
|
||||||
] if upload_log else []
|
|
||||||
cum_size = 0
|
|
||||||
for file in files:
|
|
||||||
n_b = len(file.fp.read())
|
|
||||||
file.fp.seek(0)
|
|
||||||
if n_b == 0:
|
|
||||||
files.remove(file)
|
|
||||||
continue
|
|
||||||
elif n_b >= 1024 * 1024 * 256:
|
|
||||||
data = file.fp.read()
|
|
||||||
compressed = await self.bot.loop.run_in_executor(
|
|
||||||
gzip.compress, data, 9
|
|
||||||
)
|
|
||||||
file.fp.close()
|
|
||||||
file.fp = io.BytesIO(compressed)
|
|
||||||
file.fp.seek(0)
|
|
||||||
file.filename += ".gz"
|
|
||||||
cum_size += len(compressed)
|
|
||||||
else:
|
|
||||||
cum_size += n_b
|
|
||||||
|
|
||||||
for file_name in Path(tempdir).glob(f"{ctx.user.id}.*"):
|
|
||||||
stat = file_name.stat()
|
|
||||||
size_mb = stat.st_size / 1024 / 1024
|
|
||||||
if (size_mb * 1024 * 1024 + cum_size) >= (MAX_SIZE - 0.256) * 1024 * 1024:
|
|
||||||
warning = f"File {file_name.name} was too large ({size_mb:,.1f}MB vs {MAX_SIZE:.1f}MB)".encode()
|
|
||||||
_x = io.BytesIO(
|
|
||||||
warning
|
|
||||||
)
|
|
||||||
_x.seek(0)
|
|
||||||
cum_size += len(warning)
|
|
||||||
files.append(discord.File(_x, filename=file_name.name + ".txt"))
|
|
||||||
try:
|
|
||||||
video = discord.File(file_name, filename=file_name.name)
|
|
||||||
files.append(video)
|
|
||||||
except FileNotFoundError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
cum_size += size_mb * 1024 * 1024
|
|
||||||
|
|
||||||
if not files:
|
|
||||||
return await ctx.edit(embed=discord.Embed(description="No files found.", color=discord.Colour.red()))
|
|
||||||
await ctx.edit(
|
|
||||||
embed=discord.Embed(
|
|
||||||
title="Here's your video!",
|
|
||||||
color=discord.Colour.green()
|
|
||||||
),
|
|
||||||
files=files
|
|
||||||
)
|
|
||||||
|
|
||||||
@commands.slash_command(name="yt-dl-beta")
|
@commands.slash_command(name="yt-dl-beta")
|
||||||
@commands.max_concurrency(1, commands.BucketType.user)
|
@commands.max_concurrency(1, commands.BucketType.user)
|
||||||
async def yt_dl_2(
|
async def yt_dl_2(
|
||||||
|
@ -1112,10 +864,17 @@ class OtherCog(commands.Cog):
|
||||||
autocomplete=format_autocomplete,
|
autocomplete=format_autocomplete,
|
||||||
default=""
|
default=""
|
||||||
) = "",
|
) = "",
|
||||||
|
extract_audio: bool = False,
|
||||||
upload_log: bool = False,
|
upload_log: bool = False,
|
||||||
|
# cookies: discord.Option(
|
||||||
|
# bool,
|
||||||
|
# description="Whether to ask for cookies.",
|
||||||
|
# default=False
|
||||||
|
# ) = False
|
||||||
):
|
):
|
||||||
"""Downloads a video using youtube-dl"""
|
"""Downloads a video using youtube-dl"""
|
||||||
await ctx.defer()
|
await ctx.defer()
|
||||||
|
compress_if_possible = False
|
||||||
formats = await self.list_formats(url)
|
formats = await self.list_formats(url)
|
||||||
if list_formats:
|
if list_formats:
|
||||||
embeds = []
|
embeds = []
|
||||||
|
@ -1132,12 +891,16 @@ class OtherCog(commands.Cog):
|
||||||
embeds.append(
|
embeds.append(
|
||||||
discord.Embed(
|
discord.Embed(
|
||||||
title=fmt,
|
title=fmt,
|
||||||
description="- Encoding: {0[vcodec]} + {0[acodec]}\n"
|
description="- Encoding: {3} + {2}\n"
|
||||||
"- Extension: `.{0[ext]}`\n"
|
"- Extension: `.{0[ext]}`\n"
|
||||||
"- Resolution: {0[resolution]}\n"
|
"- Resolution: {0[resolution]}\n"
|
||||||
"- Filesize: {1}\n"
|
"- Filesize: {1}\n"
|
||||||
"- Protocol: {0[protocol]}\n".format(formats[fmt],
|
"- Protocol: {0[protocol]}\n".format(
|
||||||
f"{round(fs, 2)}{units[0]}"),
|
formats[fmt],
|
||||||
|
formats[fmt].get("acodec", 'N/A'),
|
||||||
|
formats[fmt].get("vcodec", 'N/A'),
|
||||||
|
f"{round(fs, 2)}{units[0]}"
|
||||||
|
),
|
||||||
colour=discord.Colour.blurple()
|
colour=discord.Colour.blurple()
|
||||||
).add_field(
|
).add_field(
|
||||||
name="Download:",
|
name="Download:",
|
||||||
|
@ -1159,6 +922,7 @@ class OtherCog(commands.Cog):
|
||||||
_format = fmt
|
_format = fmt
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
if not await self.bot.is_owner(ctx.user):
|
||||||
return await ctx.edit(
|
return await ctx.edit(
|
||||||
embed=discord.Embed(
|
embed=discord.Embed(
|
||||||
title="Error",
|
title="Error",
|
||||||
|
@ -1170,9 +934,10 @@ class OtherCog(commands.Cog):
|
||||||
MAX_SIZE_MB = ctx.guild.filesize_limit / 1024 / 1024
|
MAX_SIZE_MB = ctx.guild.filesize_limit / 1024 / 1024
|
||||||
if MAX_SIZE_MB == 8.0:
|
if MAX_SIZE_MB == 8.0:
|
||||||
MAX_SIZE_MB = 25.0
|
MAX_SIZE_MB = 25.0
|
||||||
|
BYTES_REMAINING = (MAX_SIZE_MB - 0.256) * 1024 * 1024
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory(prefix="jimmy-ytdl-wat") as tempdir_str:
|
with tempfile.TemporaryDirectory(prefix="jimmy-ytdl-") as tempdir_str:
|
||||||
tempdir = Path(tempdir_str).resolve()
|
tempdir = Path(tempdir_str).resolve()
|
||||||
stdout = tempdir / "stdout.txt"
|
stdout = tempdir / "stdout.txt"
|
||||||
stderr = tempdir / "stderr.txt"
|
stderr = tempdir / "stderr.txt"
|
||||||
|
@ -1212,8 +977,7 @@ class OtherCog(commands.Cog):
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(
|
args = {
|
||||||
{
|
|
||||||
"windowsfilenames": True,
|
"windowsfilenames": True,
|
||||||
"restrictfilenames": True,
|
"restrictfilenames": True,
|
||||||
"noplaylist": True,
|
"noplaylist": True,
|
||||||
|
@ -1221,17 +985,35 @@ class OtherCog(commands.Cog):
|
||||||
"no_color": True,
|
"no_color": True,
|
||||||
"noprogress": True,
|
"noprogress": True,
|
||||||
"logger": logger,
|
"logger": logger,
|
||||||
"format": _format or f"(bv*+ba/bv/ba/b)[filesize<={MAX_SIZE_MB}M]",
|
"format": _format or None,
|
||||||
"paths": paths,
|
"paths": paths,
|
||||||
"outtmpl": f"{ctx.user.id}-%(title)s.%(ext)s",
|
"outtmpl": f"{ctx.user.id}-%(title).50s.%(ext)s",
|
||||||
"format_sort": "codec:h264,ext"
|
"trim_file_name": 128,
|
||||||
|
"extract_audio": extract_audio,
|
||||||
}
|
}
|
||||||
) as downloader:
|
if extract_audio:
|
||||||
|
args["postprocessors"] = [
|
||||||
|
{
|
||||||
|
"key": "FFmpegExtractAudio",
|
||||||
|
"preferredquality": "192",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
args["format"] = args["format"] or f"(ba/b)[filesize<={MAX_SIZE_MB}M]"
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = urlparse(url)
|
||||||
|
if url.netloc in ("www.instagram.com", "instagram.com"):
|
||||||
|
args["cookiesfrombrowser"] = ("firefox", "default")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if args["format"] is None:
|
||||||
|
args["format"] = f"(bv*+ba/bv/ba/b)[filesize<={MAX_SIZE_MB}M]"
|
||||||
|
with yt_dlp.YoutubeDL(args) as downloader:
|
||||||
try:
|
try:
|
||||||
await ctx.respond(
|
await ctx.respond(
|
||||||
embed=discord.Embed(title="Downloading...", colour=discord.Colour.blurple())
|
embed=discord.Embed(title="Downloading...", colour=discord.Colour.blurple())
|
||||||
)
|
)
|
||||||
async with ctx.channel.typing():
|
|
||||||
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:
|
||||||
return await ctx.edit(
|
return await ctx.edit(
|
||||||
|
@ -1250,31 +1032,80 @@ class OtherCog(commands.Cog):
|
||||||
del logger
|
del logger
|
||||||
files = []
|
files = []
|
||||||
if upload_log:
|
if upload_log:
|
||||||
if stdout.stat().st_size:
|
if out_size := stdout.stat().st_size:
|
||||||
files.append(discord.File(stdout, "stdout.txt"))
|
files.append(discord.File(stdout, "stdout.txt"))
|
||||||
if stderr.stat().st_size:
|
BYTES_REMAINING -= out_size
|
||||||
|
if err_size := stderr.stat().st_size:
|
||||||
files.append(discord.File(stderr, "stderr.txt"))
|
files.append(discord.File(stderr, "stderr.txt"))
|
||||||
|
BYTES_REMAINING -= err_size
|
||||||
|
|
||||||
for file in tempdir.glob(f"{ctx.user.id}-*"):
|
for file in tempdir.glob(f"{ctx.user.id}-*"):
|
||||||
if file.stat().st_size == 0:
|
if file.stat().st_size == 0:
|
||||||
embed.description += f"\N{warning sign}\ufe0f {file.name} is empty.\n"
|
embed.description += f"\N{warning sign}\ufe0f {file.name} is empty.\n"
|
||||||
continue
|
continue
|
||||||
st = file.stat().st_size
|
st = file.stat().st_size
|
||||||
if st / 1024 / 1024 >= MAX_SIZE_MB:
|
COMPRESS_FAILED = False
|
||||||
|
if st / 1024 / 1024 >= MAX_SIZE_MB or st >= BYTES_REMAINING:
|
||||||
|
if compress_if_possible and file.suffix in (
|
||||||
|
".mp4",
|
||||||
|
".mkv",
|
||||||
|
".mov",
|
||||||
|
'.aac',
|
||||||
|
'.opus',
|
||||||
|
'.webm'
|
||||||
|
):
|
||||||
|
await ctx.edit(
|
||||||
|
embed=discord.Embed(
|
||||||
|
title="Compressing...",
|
||||||
|
description="File name: `%s`\nThis will take a long time." % file.name,
|
||||||
|
colour=discord.Colour.blurple()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
target = file.with_name(file.name + '.compressed' + file.suffix)
|
||||||
|
ffmpeg_command = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-hide_banner",
|
||||||
|
"-i",
|
||||||
|
str(file),
|
||||||
|
"-crf",
|
||||||
|
"30",
|
||||||
|
"-preset",
|
||||||
|
"slow",
|
||||||
|
str(target)
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.bot.loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
partial(
|
||||||
|
subprocess.run,
|
||||||
|
ffmpeg_command,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
COMPRESS_FAILED = True
|
||||||
|
else:
|
||||||
|
file = target
|
||||||
|
st = file.stat().st_size
|
||||||
|
|
||||||
units = ["B", "KB", "MB", "GB", "TB"]
|
units = ["B", "KB", "MB", "GB", "TB"]
|
||||||
st_r = st
|
st_r = st
|
||||||
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],
|
||||||
MAX_SIZE_MB
|
MAX_SIZE_MB,
|
||||||
|
', compressing failed' if COMPRESS_FAILED else ', compressed fine.'
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
files.append(discord.File(file, file.name))
|
files.append(discord.File(file, file.name))
|
||||||
|
BYTES_REMAINING -= st
|
||||||
|
|
||||||
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" % (
|
||||||
|
@ -1287,7 +1118,19 @@ class OtherCog(commands.Cog):
|
||||||
await ctx.edit(embed=embed)
|
await ctx.edit(embed=embed)
|
||||||
await ctx.channel.trigger_typing()
|
await ctx.channel.trigger_typing()
|
||||||
embed.description = _desc
|
embed.description = _desc
|
||||||
|
start = time()
|
||||||
await ctx.edit(embed=embed, files=files)
|
await ctx.edit(embed=embed, files=files)
|
||||||
|
end = time()
|
||||||
|
if (end - start) < 10:
|
||||||
|
await ctx.respond("*clearing typing*", delete_after=0.01)
|
||||||
|
|
||||||
|
async def bgtask():
|
||||||
|
await asyncio.sleep(120.0)
|
||||||
|
try:
|
||||||
|
await ctx.edit(embed=None)
|
||||||
|
except discord.NotFound:
|
||||||
|
pass
|
||||||
|
self.bot.loop.create_task(bgtask())
|
||||||
|
|
||||||
@commands.slash_command(name="text-to-mp3")
|
@commands.slash_command(name="text-to-mp3")
|
||||||
@commands.cooldown(5, 600, commands.BucketType.user)
|
@commands.cooldown(5, 600, commands.BucketType.user)
|
||||||
|
@ -1374,7 +1217,11 @@ class OtherCog(commands.Cog):
|
||||||
_url = text_pre[4:].strip()
|
_url = text_pre[4:].strip()
|
||||||
_msg = await interaction.followup.send("Downloading text...")
|
_msg = await interaction.followup.send("Downloading text...")
|
||||||
try:
|
try:
|
||||||
response = await _self.http.get(_url, headers={"User-Agent": "Mozilla/5.0"})
|
response = await _self.http.get(
|
||||||
|
_url,
|
||||||
|
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}")
|
||||||
return
|
return
|
||||||
|
@ -1387,15 +1234,34 @@ class OtherCog(commands.Cog):
|
||||||
except (ConnectionError, httpx.HTTPError, httpx.NetworkError) as e:
|
except (ConnectionError, httpx.HTTPError, httpx.NetworkError) as e:
|
||||||
await _msg.edit(content="Failed to download text. " + str(e))
|
await _msg.edit(content="Failed to download text. " + str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await _msg.edit(content="Text downloaded; Converting to MP3...")
|
_msg = await interaction.followup.send("Converting text to MP3... (0 seconds elapsed)")
|
||||||
else:
|
|
||||||
_msg = await interaction.followup.send("Converting text to MP3...")
|
async def assurance_task():
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(5.5)
|
||||||
|
await _msg.edit(
|
||||||
|
content=f"Converting text to MP3... ({time() - start_time:.1f} seconds elapsed)"
|
||||||
|
)
|
||||||
|
|
||||||
|
start_time = time()
|
||||||
|
task = _bot.loop.create_task(assurance_task())
|
||||||
try:
|
try:
|
||||||
mp3, size = await _bot.loop.run_in_executor(None, _convert, text_pre)
|
mp3, size = await asyncio.wait_for(
|
||||||
|
_bot.loop.run_in_executor(None, _convert, text_pre),
|
||||||
|
timeout=600
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
task.cancel()
|
||||||
|
await _msg.edit(content="Failed to convert text to MP3 - Timeout. Try shorter/less complex text.")
|
||||||
|
return
|
||||||
except (Exception, IOError) as e:
|
except (Exception, IOError) as e:
|
||||||
|
task.cancel()
|
||||||
await _msg.edit(content="failed. " + str(e))
|
await _msg.edit(content="failed. " + str(e))
|
||||||
raise e
|
raise e
|
||||||
|
task.cancel()
|
||||||
|
del task
|
||||||
if size >= ctx.guild.filesize_limit - 1500:
|
if size >= ctx.guild.filesize_limit - 1500:
|
||||||
await _msg.edit(
|
await _msg.edit(
|
||||||
content=f"MP3 is too large ({size / 1024 / 1024}Mb vs "
|
content=f"MP3 is too large ({size / 1024 / 1024}Mb vs "
|
||||||
|
@ -1579,10 +1445,86 @@ class OtherCog(commands.Cog):
|
||||||
out_file = io.BytesIO(text.encode("utf-8", "replace"))
|
out_file = io.BytesIO(text.encode("utf-8", "replace"))
|
||||||
await ctx.respond(file=discord.File(out_file, filename="ocr.txt"))
|
await ctx.respond(file=discord.File(out_file, filename="ocr.txt"))
|
||||||
|
|
||||||
|
if timings:
|
||||||
await ctx.edit(
|
await ctx.edit(
|
||||||
content="Timings:\n" + "\n".join("%s: %s" % (k.title(), v) for k, v in timings.items()),
|
content="Timings:\n" + "\n".join("%s: %s" % (k.title(), v) for k, v in timings.items()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@commands.slash_command(name="image-to-gif")
|
||||||
|
@commands.cooldown(1, 30, commands.BucketType.user)
|
||||||
|
@commands.max_concurrency(1, commands.BucketType.user)
|
||||||
|
async def convert_image_to_gif(
|
||||||
|
self,
|
||||||
|
ctx: discord.ApplicationContext,
|
||||||
|
image: discord.Option(
|
||||||
|
discord.SlashCommandOptionType.attachment,
|
||||||
|
description="Image to convert. PNG/JPEG only.",
|
||||||
|
),
|
||||||
|
backup: discord.Option(
|
||||||
|
discord.SlashCommandOptionType.boolean,
|
||||||
|
description="Sends the GIF to your DM as well so you'll never lose it.",
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
):
|
||||||
|
"""Converts a static image to a gif, so you can save it"""
|
||||||
|
await ctx.defer()
|
||||||
|
image: discord.Attachment
|
||||||
|
with tempfile.TemporaryFile("wb+") as f:
|
||||||
|
await image.save(f)
|
||||||
|
f.seek(0)
|
||||||
|
img = await self.bot.loop.run_in_executor(None, Image.open, f)
|
||||||
|
if img.format.upper() not in ("PNG", "JPEG", "WEBP", "HEIF", "BMP", "TIFF"):
|
||||||
|
return await ctx.respond("Image must be PNG, JPEG, WEBP, or HEIF.")
|
||||||
|
|
||||||
|
with tempfile.TemporaryFile("wb+") as f2:
|
||||||
|
caller = partial(img.save, f2, format="GIF")
|
||||||
|
await self.bot.loop.run_in_executor(None, caller)
|
||||||
|
f2.seek(0)
|
||||||
|
try:
|
||||||
|
await ctx.respond(file=discord.File(f2, filename="image.gif"))
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
if e.code == 40005:
|
||||||
|
return await ctx.respond("Image is too large.")
|
||||||
|
return await ctx.respond(f"Failed to upload: `{e}`")
|
||||||
|
if backup:
|
||||||
|
try:
|
||||||
|
await ctx.user.send(file=discord.File(f2, filename="image.gif"))
|
||||||
|
except discord.Forbidden:
|
||||||
|
return await ctx.respond("Unable to mirror to your DM - am I blocked?", ephemeral=True)
|
||||||
|
|
||||||
|
@commands.message_command(name="Convert Image to GIF")
|
||||||
|
async def convert_image_to_gif(self, ctx: discord.ApplicationContext, message: discord.Message):
|
||||||
|
await ctx.defer()
|
||||||
|
for attachment in message.attachments:
|
||||||
|
if attachment.content_type.startswith("image/"):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return await ctx.respond("No image found.")
|
||||||
|
image = attachment
|
||||||
|
image: discord.Attachment
|
||||||
|
with tempfile.TemporaryFile("wb+") as f:
|
||||||
|
await image.save(f)
|
||||||
|
f.seek(0)
|
||||||
|
img = await self.bot.loop.run_in_executor(None, Image.open, f)
|
||||||
|
if img.format.upper() not in ("PNG", "JPEG", "WEBP", "HEIF", "BMP", "TIFF"):
|
||||||
|
return await ctx.respond("Image must be PNG, JPEG, WEBP, or HEIF.")
|
||||||
|
|
||||||
|
with tempfile.TemporaryFile("wb+") as f2:
|
||||||
|
caller = partial(img.save, f2, format="GIF")
|
||||||
|
await self.bot.loop.run_in_executor(None, caller)
|
||||||
|
f2.seek(0)
|
||||||
|
try:
|
||||||
|
await ctx.respond(file=discord.File(f2, filename="image.gif"))
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
if e.code == 40005:
|
||||||
|
return await ctx.respond("Image is too large.")
|
||||||
|
return await ctx.respond(f"Failed to upload: `{e}`")
|
||||||
|
try:
|
||||||
|
f2.seek(0)
|
||||||
|
await ctx.user.send(file=discord.File(f2, filename="image.gif"))
|
||||||
|
except discord.Forbidden:
|
||||||
|
return await ctx.respond("Unable to mirror to your DM - am I blocked?", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(OtherCog(bot))
|
bot.add_cog(OtherCog(bot))
|
||||||
|
|
19
utils/db.py
19
utils/db.py
|
@ -1,5 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
|
import discord
|
||||||
import uuid
|
import uuid
|
||||||
from typing import TYPE_CHECKING, Optional, TypeVar
|
from typing import TYPE_CHECKING, Optional, TypeVar
|
||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
|
@ -203,3 +204,21 @@ class JimmyBans(orm.Model):
|
||||||
reason: str | None
|
reason: str | None
|
||||||
timestamp: float
|
timestamp: float
|
||||||
until: float | None
|
until: float | None
|
||||||
|
|
||||||
|
|
||||||
|
class AccessTokens(orm.Model):
|
||||||
|
tablename = "access_tokens"
|
||||||
|
registry = registry
|
||||||
|
fields = {
|
||||||
|
"entry_id": orm.UUID(primary_key=True, default=uuid.uuid4),
|
||||||
|
"user_id": orm.BigInteger(unique=True),
|
||||||
|
"access_token": orm.String(min_length=6, max_length=128),
|
||||||
|
"expires": orm.Float(default=lambda: discord.utils.utcnow().timestamp() + 604800),
|
||||||
|
"ip_info": orm.JSON(default=None, allow_null=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
entry_id: uuid.UUID
|
||||||
|
user_id: int
|
||||||
|
access_token: str
|
||||||
|
ip_info: dict | None
|
||||||
|
|
|
@ -12,6 +12,7 @@ from fastapi import FastAPI, HTTPException, Request
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
|
from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
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 config import guilds
|
from config import guilds
|
||||||
|
|
||||||
SF_ROOT = Path(__file__).parent / "static"
|
SF_ROOT = Path(__file__).parent / "static"
|
||||||
|
@ -112,7 +113,7 @@ async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
discord.utils.oauth_url(
|
discord.utils.oauth_url(
|
||||||
OAUTH_ID,
|
OAUTH_ID,
|
||||||
redirect_uri=OAUTH_REDIRECT_URI,
|
redirect_uri=OAUTH_REDIRECT_URI,
|
||||||
scopes=('identify',)
|
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={
|
||||||
|
@ -159,11 +160,11 @@ async def authenticate(req: Request, code: str = None, state: str = None):
|
||||||
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(Student, user_id=user["id"])
|
student = await get_or_none(AccessTokens, user_id=user["id"])
|
||||||
if not student:
|
if not student:
|
||||||
raise HTTPException(
|
student = await AccessTokens.objects.create(
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
user_id=user["id"],
|
||||||
detail="Student not found. Please run /verify first."
|
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
|
||||||
|
|
Loading…
Reference in a new issue