college-bot-v1/cogs/events.py

440 lines
22 KiB
Python
Raw Normal View History

2023-03-17 11:00:42 +00:00
import io
2022-12-28 21:26:56 +00:00
import random
2023-03-17 10:41:29 +00:00
import re
2023-03-22 14:56:33 +00:00
import asyncio
2023-02-24 08:40:25 +00:00
import textwrap
2023-03-22 14:56:33 +00:00
import subprocess
2022-12-31 17:13:40 +00:00
from pathlib import Path
from typing import Optional, Tuple
2022-10-06 09:20:23 +01:00
import discord
2023-03-17 10:41:29 +00:00
import httpx
from discord.ext import commands, pages
2022-12-07 13:45:58 +00:00
from utils import Student, get_or_none, console
2023-02-09 10:14:20 +00:00
from config import guilds
2023-02-23 14:30:49 +00:00
try:
from config import OAUTH_REDIRECT_URI
except ImportError:
OAUTH_REDIRECT_URI = None
2023-03-17 10:41:29 +00:00
try:
from config import GITHUB_USERNAME
from config import GITHUB_PASSWORD
except ImportError:
GITHUB_USERNAME = None
GITHUB_PASSWORD = None
2022-10-06 09:20:23 +01:00
LTR = "\N{black rightwards arrow}\U0000fe0f"
RTL = "\N{leftwards black arrow}\U0000fe0f"
2023-03-31 11:55:36 +01:00
async def _dc(client: discord.VoiceClient | None):
if client is None:
return
2023-03-22 16:51:54 +00:00
if client.is_playing():
client.stop()
2023-03-22 15:37:06 +00:00
try:
2023-03-22 16:51:54 +00:00
await client.disconnect(force=True)
2023-03-22 15:37:06 +00:00
finally:
2023-03-22 16:51:54 +00:00
# client.cleanup()
pass
2023-03-22 15:37:06 +00:00
2022-10-06 09:20:23 +01:00
class Events(commands.Cog):
def __init__(self, bot):
self.bot = bot
2023-03-17 10:41:29 +00:00
self.http = httpx.AsyncClient()
2022-10-13 08:53:45 +01:00
# noinspection DuplicatedCode
async def analyse_text(self, text: str) -> Optional[Tuple[float, float, float, float]]:
"""Analyse text for positivity, negativity and neutrality."""
def inner():
try:
from utils.sentiment_analysis import intensity_analyser
except ImportError:
return None
scores = intensity_analyser.polarity_scores(text)
return scores["pos"], scores["neu"], scores["neg"], scores["compound"]
async with self.bot.training_lock:
return await self.bot.loop.run_in_executor(None, inner)
2022-10-06 09:34:23 +01:00
@commands.Cog.listener("on_raw_reaction_add")
2022-10-06 09:20:23 +01:00
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
channel: Optional[discord.TextChannel] = self.bot.get_channel(payload.channel_id)
2022-10-06 09:31:20 +01:00
if channel is not None:
2022-10-06 09:20:23 +01:00
try:
message: discord.Message = await channel.fetch_message(payload.message_id)
except discord.HTTPException:
return
2023-03-31 11:55:36 +01:00
if payload.emoji.name == "\N{wastebasket}\U0000fe0f":
if message.author.id == self.bot.user.id:
await message.delete(delay=0.25)
else:
reactions = 0
mod_reactions = 0
for reaction in message.reactions:
if reaction.emoji == payload.emoji:
async for member in reaction.users():
if member.id == self.bot.user.id:
continue
if member.guild_permissions.manage_messages:
mod_reactions += 1
reactions += 1
2023-03-31 11:57:45 +01:00
if reactions >= 2 or mod_reactions >= 1:
2023-03-31 11:55:36 +01:00
await message.delete(delay=0.1)
2022-10-06 09:20:23 +01:00
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member):
if member.guild is None or member.guild.id not in guilds:
return
student: Optional[Student] = await get_or_none(Student, user_id=member.id)
if student and student.id:
role = discord.utils.find(lambda r: r.name.lower() == "verified", member.guild.roles)
if role and role < member.guild.me.top_role:
await member.add_roles(role, reason="Verified")
channel: discord.TextChannel = discord.utils.get(member.guild.text_channels, name="general")
if channel and channel.can_send():
await channel.send(
f"{LTR} {member.mention} (`{member}`, {f'{student.id}' if student else 'pending verification'})"
)
2022-10-06 09:20:23 +01:00
@commands.Cog.listener()
async def on_member_remove(self, member: discord.Member):
if member.guild is None or member.guild.id not in guilds:
return
student: Optional[Student] = await get_or_none(Student, user_id=member.id)
channel: discord.TextChannel = discord.utils.get(member.guild.text_channels, name="general")
if channel and channel.can_send():
await channel.send(
f"{RTL} {member.mention} (`{member}`, {f'{student.id}' if student else 'pending verification'})"
)
2022-10-06 09:20:23 +01:00
2023-03-17 10:41:29 +00:00
async def process_message_for_github_links(self, message: discord.Message):
RAW_URL = "https://github.com/{repo}/raw/{branch}/{path}"
_re = re.match(
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+))?)",
message.content
)
if _re:
branch, path = _re.group("path").split("/", 1)
2023-03-17 10:48:07 +00:00
_p = Path(path).suffix
2023-03-17 10:41:29 +00:00
url = RAW_URL.format(
repo=_re.group("repo"),
branch=branch,
path=path
)
if all((GITHUB_PASSWORD, GITHUB_USERNAME)):
auth = (GITHUB_USERNAME, GITHUB_PASSWORD)
else:
auth = None
response = await self.http.get(url, follow_redirects=True, auth=auth)
if response.status_code == 200:
ctx = await self.bot.get_context(message)
lines = response.text.splitlines()
if _re.group("start_line"):
start_line = int(_re.group("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]
2023-03-17 10:48:07 +00:00
paginator = commands.Paginator(prefix="```" + _p[1:], suffix="```", max_size=1000)
2023-03-17 10:41:29 +00:00
for line in lines:
paginator.add_line(line)
_pages = paginator.pages
paginator2 = pages.Paginator(_pages, timeout=300)
# noinspection PyTypeChecker
await paginator2.send(ctx, reference=message.to_reference())
if message.channel.permissions_for(message.guild.me).manage_messages:
await message.edit(suppress=True)
2023-03-17 11:00:42 +00:00
else:
RAW_URL = "https://github.com/{repo}/archive/refs/heads/{branch}.zip"
_full_re = re.finditer(
r"https://github\.com/(?P<repo>[a-zA-Z0-9-]+/[\w.-]+)(/tree/(?P<branch>[^#>]+))?\.(git|zip)",
message.content
)
for _match in _full_re:
repo = _match.group("repo")
branch = _match.group("branch") or "master"
url = RAW_URL.format(
repo=repo,
branch=branch,
)
if all((GITHUB_PASSWORD, GITHUB_USERNAME)):
auth = (GITHUB_USERNAME, GITHUB_PASSWORD)
else:
auth = None
async with message.channel.typing():
response = await self.http.get(url, follow_redirects=True, auth=auth)
if response.status_code == 200:
content = response.content
if len(content) > message.guild.filesize_limit - 1000:
continue
_io = io.BytesIO(content)
fn = f"{repo.replace('/', '-')}-{branch}.zip"
await message.reply(file=discord.File(_io, filename=fn))
if message.channel.permissions_for(message.guild.me).manage_messages:
await message.edit(suppress=True)
2023-03-17 10:41:29 +00:00
2023-03-22 14:56:33 +00:00
@commands.Cog.listener()
async def on_voice_state_update(
self,
member: discord.Member,
*_
):
2023-03-22 15:02:46 +00:00
me_voice = member.guild.me.voice
2023-03-22 16:30:24 +00:00
if me_voice is None or me_voice.channel is None or member.guild.voice_client is None:
2023-03-22 14:56:33 +00:00
return
channel = me_voice.channel
if len(channel.members) - 1 == 0:
# We are the only one in the channel
2023-03-22 15:37:06 +00:00
await _dc(member.guild.voice_client)
2023-03-22 14:56:33 +00:00
2022-10-09 19:27:02 +01:00
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
2022-10-30 16:05:54 +00:00
if not message.guild:
2023-01-01 20:55:36 +00:00
return
2023-01-25 20:33:15 +00:00
2022-10-09 19:27:02 +01:00
if message.channel.name == "pinboard":
2022-10-10 18:15:48 +01:00
if message.type == discord.MessageType.pins_add:
await message.delete(delay=0.01)
else:
try:
await message.pin(reason="Automatic pinboard pinning")
except discord.HTTPException as e:
return await message.reply(f"Failed to auto-pin: {e}", delete_after=10)
elif message.channel.name in ("verify", "timetable") and message.author != self.bot.user:
if message.channel.permissions_for(message.guild.me).manage_messages:
await message.delete(delay=1)
2022-10-09 19:27:02 +01:00
2022-11-10 15:54:40 +00:00
else:
2023-02-24 08:40:25 +00:00
# Respond to shronk bot
2023-02-18 12:10:54 +00:00
if message.author.id == 1063875884274163732 and message.channel.can_send():
2023-02-28 23:12:01 +00:00
if "pissylicious 💦💦" in message.content:
from dns import asyncresolver
import httpx
response = await asyncresolver.resolve("shronkservz.tk", "A")
2023-02-28 23:14:40 +00:00
ip_info_response = await httpx.AsyncClient().get(f"http://ip-api.com/json/{response[0].address}")
2023-02-28 23:12:01 +00:00
if ip_info_response.status_code == 200:
return await message.reply(
f"Scattylicious\N{pile of poo}\N{pile of poo}\n"
"IP: {0[query]}\n"
"ISP: {0[isp]}\n"
"Latitude: {0[lat]}\n"
"Longitude: {0[lon]}\n".format(
2023-02-28 23:16:39 +00:00
ip_info_response.json(),
2023-02-28 23:12:01 +00:00
)
)
2023-02-18 12:10:54 +00:00
RESPONSES = {
"Congratulations!!": "Shut up SHRoNK Bot, nobody loves you.",
2023-03-14 11:13:11 +00:00
"You run on a Raspberry Pi... I run on a real server": "At least my server gets action, "
"while yours just sits and collects dust!"
2023-02-18 12:10:54 +00:00
}
for k, v in RESPONSES.items():
if k in message.content:
2023-03-14 11:13:11 +00:00
await message.reply("shut up", delete_after=3)
await message.delete(delay=3)
2023-02-18 12:10:54 +00:00
break
2023-02-24 08:40:25 +00:00
# Stop responding to any bots
2022-11-15 21:45:39 +00:00
if message.author.bot is True:
2022-11-10 15:54:40 +00:00
return
2023-03-20 17:54:39 +00:00
if message.channel.can_send() and "ferdi" in message.content.lower():
await message.reply("https://ferdi-is.gay/")
2023-02-24 08:40:25 +00:00
# Only respond if the message has content...
if message.content:
2023-02-24 08:40:25 +00:00
if message.channel.can_send(): # ... and we can send messages
2023-03-22 14:56:33 +00:00
if "it just works" in message.content.lower():
file = Path.cwd() / "assets" / "it-just-works.ogg"
2023-03-22 15:02:46 +00:00
if message.author.voice is not None and message.author.voice.channel is not None:
2023-03-31 11:55:36 +01:00
voice: discord.VoiceClient | None = None
2023-03-22 15:57:10 +00:00
if message.guild.voice_client is not None:
2023-03-31 11:55:36 +01:00
# noinspection PyUnresolvedReferences
2023-03-22 16:50:37 +00:00
if message.guild.voice_client.is_playing():
return
2023-03-22 15:41:34 +00:00
try:
await _dc(message.guild.voice_client)
except discord.HTTPException:
pass
2023-03-22 16:07:46 +00:00
try:
voice = await message.author.voice.channel.connect(timeout=10, reconnect=False)
except asyncio.TimeoutError:
2023-03-22 16:11:12 +00:00
await message.channel.trigger_typing()
await message.reply(
"I'd play the song but discord's voice servers are shit.",
file=discord.File(file)
)
2023-03-22 16:07:46 +00:00
region = message.author.voice.channel.rtc_region
2023-03-31 11:55:36 +01:00
# noinspection PyUnresolvedReferences
2023-03-22 16:07:46 +00:00
console.log(
"Timed out connecting to voice channel: {0.name} in {0.guild.name} "
"(region {1})".format(
message.author.voice.channel,
region.name if region else "auto (unknown)"
)
)
return
2023-03-22 15:28:19 +00:00
if voice.channel != message.author.voice.channel:
await voice.move_to(message.author.voice.channel)
2023-03-22 15:20:40 +00:00
if message.guild.me.voice.self_mute or message.guild.me.voice.mute:
2023-03-22 15:37:06 +00:00
await _dc(voice)
2023-03-22 15:19:48 +00:00
await message.channel.trigger_typing()
2023-03-22 15:02:46 +00:00
await message.reply("Unmute me >:(", file=discord.File(file))
else:
2023-03-22 15:37:06 +00:00
2023-03-22 15:26:29 +00:00
def after(e):
asyncio.run_coroutine_threadsafe(
2023-03-22 15:37:06 +00:00
_dc(voice),
2023-03-22 15:26:29 +00:00
self.bot.loop
)
if e is not None:
console.log(f"Error playing audio: {e}")
2023-03-31 11:55:36 +01:00
# noinspection PyTypeChecker
2023-03-22 15:26:29 +00:00
src = discord.FFmpegPCMAudio(str(file.absolute()), stderr=subprocess.DEVNULL)
2023-03-22 16:07:46 +00:00
src = discord.PCMVolumeTransformer(src, volume=0.5)
2023-03-22 15:02:46 +00:00
voice.play(
2023-03-22 15:26:29 +00:00
src,
2023-03-22 15:30:19 +00:00
after=after
2023-03-22 14:56:33 +00:00
)
else:
2023-03-22 15:19:48 +00:00
await message.channel.trigger_typing()
2023-03-22 14:56:33 +00:00
await message.reply(file=discord.File(file))
if "linux" in message.content.lower() and self.bot.user in message.mentions:
try:
with open("./assets/copypasta.txt", "r") as f:
await message.reply(f.read())
except FileNotFoundError:
await message.reply(
"I'd just like to interject for a moment. What you're referring to as Linux, "
"is in fact, uh... I don't know, I forgot."
)
2022-12-31 17:13:40 +00:00
if "carat" in message.content.lower():
file = discord.File(Path.cwd() / "assets" / "carat.png", filename="carat.png")
2022-12-31 17:13:40 +00:00
await message.reply(file=file)
2023-02-24 08:40:25 +00:00
if message.reference is not None and message.reference.cached_message is not None:
if message.content.lower().strip() in ("what", "what?", "huh", "huh?", "?"):
2023-02-24 08:40:25 +00:00
text = "{0.author.mention} said %r, you deaf sod.".format(
message.reference.cached_message
)
_content = textwrap.shorten(
text % message.reference.cached_message.content, width=2000, placeholder="[...]"
)
await message.reply(_content)
2023-03-17 10:41:29 +00:00
await self.process_message_for_github_links(message)
if message.channel.permissions_for(message.guild.me).add_reactions:
if "mpreg" in message.content.lower() or "\U0001fac3" in message.content.lower():
try:
await message.add_reaction("\U0001fac3")
except discord.HTTPException as e:
console.log("Failed to add mpreg reaction:", e)
if "lupupa" in message.content.lower():
try:
await message.add_reaction("\U0001fac3")
except discord.HTTPException as e:
console.log("Failed to add mpreg reaction:", e)
2022-12-28 21:26:56 +00:00
is_naus = random.randint(1, 100) == 32
if self.bot.user in message.mentions or message.channel.id == 1032974266527907901 or is_naus:
T_EMOJI = "\U0001f3f3\U0000fe0f\U0000200d\U000026a7\U0000fe0f"
G_EMOJI = "\U0001f3f3\U0000fe0f\U0000200d\U0001f308"
N_EMOJI = "\U0001f922"
C_EMOJI = "\U0000271d\U0000fe0f"
2022-12-28 21:26:56 +00:00
if any((x in message.content.lower() for x in ("trans", T_EMOJI, "femboy"))) or is_naus:
try:
await message.add_reaction(N_EMOJI)
except discord.HTTPException as e:
console.log("Failed to add trans reaction:", e)
if "gay" in message.content.lower() or G_EMOJI in message.content.lower():
try:
await message.add_reaction(C_EMOJI)
except discord.HTTPException as e:
console.log("Failed to add gay reaction:", e)
if self.bot.user in message.mentions:
if message.content.startswith(self.bot.user.mention):
if message.content.lower().endswith("bot"):
pos, neut, neg, _ = await self.analyse_text(message.content)
if pos > neg:
embed = discord.Embed(description=":D", color=discord.Color.green())
embed.set_footer(
text=f"Pos: {pos*100:.2f}% | Neutral: {neut*100:.2f}% | Neg: {neg*100:.2f}%"
)
elif pos == neg:
embed = discord.Embed(description=":|", color=discord.Color.greyple())
embed.set_footer(
text=f"Pos: {pos * 100:.2f}% | Neutral: {neut * 100:.2f}% | Neg: {neg * 100:.2f}%"
)
else:
embed = discord.Embed(description=":(", color=discord.Color.red())
embed.set_footer(
text=f"Pos: {pos*100:.2f}% | Neutral: {neut*100:.2f}% | Neg: {neg*100:.2f}%"
)
return await message.reply(embed=embed)
2023-01-01 20:55:36 +00:00
if message.content.lower().endswith(
2023-01-03 15:20:50 +00:00
(
"when is the year of the linux desktop?",
"year of the linux desktop?",
"year of the linux desktop",
)
2023-01-01 20:55:36 +00:00
):
2023-01-01 20:51:03 +00:00
date = discord.utils.utcnow()
# date = date.replace(year=date.year + 1)
2023-01-01 20:52:53 +00:00
return await message.reply(date.strftime("%Y") + " will be the year of the GNU+Linux desktop.")
2022-11-10 15:54:40 +00:00
2023-02-23 14:30:49 +00:00
if message.content.lower().endswith("fuck you"):
student = await get_or_none(Student, user_id=message.author.id)
if student is None:
return await message.reply("You aren't even verified...", delete_after=10)
elif student.ip_info is None:
if OAUTH_REDIRECT_URI:
return await message.reply(
f"Let me see who you are, and then we'll talk... <{OAUTH_REDIRECT_URI}>",
delete_after=30
)
else:
return await message.reply(
"I literally don't even know who you are...",
delete_after=10
)
else:
ip = student.ip_info
2023-02-23 14:42:03 +00:00
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}"
2023-02-23 14:30:49 +00:00
return await message.reply(
"Nice argument, however,\n"
"IP: {0[query]}\n"
"ISP: {0[isp]}\n"
2023-02-23 14:34:47 +00:00
"Latitude: {0[lat]}\n"
2023-02-23 14:42:03 +00:00
"Longitude: {0[lon]}\n"
"Proxy server: {1}\n"
2023-02-23 14:47:50 +00:00
"VPS (or other hosting) provider: {2}\n\n"
2023-02-23 14:30:49 +00:00
"\N{smiling face with sunglasses}".format(
2023-02-23 14:42:03 +00:00
ip,
is_proxy,
is_hosting
2023-02-23 14:30:49 +00:00
),
delete_after=30
)
2022-10-06 09:20:23 +01:00
def setup(bot):
bot.add_cog(Events(bot))