886 lines
28 KiB
Python
886 lines
28 KiB
Python
from datetime import datetime, timezone, timedelta
|
|
|
|
import secrets
|
|
|
|
import discord
|
|
from discord import errors
|
|
from discord.ext import commands, tasks
|
|
from discord.utils import find
|
|
|
|
from tortoise import transactions, exceptions
|
|
|
|
import asyncpg.exceptions
|
|
|
|
import aiohttp
|
|
|
|
from payphone import bot
|
|
from payphone.db import models, utils
|
|
from payphone.cogs import base
|
|
from payphone.cogs.calls import embeds
|
|
from payphone.core import constants, checks, censorship, formatting
|
|
|
|
|
|
class Calls(base.Cog):
|
|
def __init__(self, bot_instance: bot.Payphone):
|
|
self.bot = bot_instance
|
|
|
|
self.hangup_calls.add_exception_type(exceptions.ConfigurationError)
|
|
self.hangup_calls.start()
|
|
|
|
def cog_unload(self) -> None:
|
|
self.hangup_calls.cancel()
|
|
|
|
return super().cog_unload()
|
|
|
|
@tasks.loop(seconds=15)
|
|
async def hangup_calls(self) -> None:
|
|
"""Hangs up inactive calls.
|
|
|
|
Hangs up inactive calls. Calls are hung up when they either:
|
|
- Have had no messages and more than one minute has passed since the start of the call.
|
|
- Have had no messages for the past 15 minutes.
|
|
|
|
In case they are hung up, both channels are attempted to be notified. In case of failure,
|
|
that channel is not notified and hung up silently.
|
|
"""
|
|
|
|
calls = await models.Call.filter(ended_at=None).prefetch_related("channels")
|
|
|
|
hung_up_channel_ids = []
|
|
|
|
for call in calls:
|
|
message = (
|
|
await models.Message.filter(call=call).order_by("-id").limit(1).first()
|
|
)
|
|
|
|
if (
|
|
message is None
|
|
and call.started_at < datetime.now(timezone.utc) - timedelta(minutes=1)
|
|
) or (
|
|
message is not None
|
|
and message.created_at
|
|
< datetime.now(timezone.utc) - timedelta(minutes=15)
|
|
):
|
|
for channel in call.channels:
|
|
hung_up_channel_ids.append(channel.id)
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(channel.id).send(
|
|
"☎️ The call was hung up because nobody replied."
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
pass
|
|
|
|
if len(hung_up_channel_ids) > 0:
|
|
await models.Channel.filter(id__in=hung_up_channel_ids).update(call=None)
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET, *hung_up_channel_ids
|
|
)
|
|
|
|
@commands.command(aliases=["c"])
|
|
@commands.guild_only()
|
|
@checks.is_unbanned()
|
|
@transactions.atomic()
|
|
async def call(self, ctx: commands.Context) -> None:
|
|
"""
|
|
Call and connect with other people.
|
|
"""
|
|
|
|
if ctx.channel.is_nsfw():
|
|
await ctx.send("❌ Calls cannot be started in NSFW channels.")
|
|
return
|
|
|
|
user_id = ctx.author.id
|
|
channel_id = ctx.channel.id
|
|
|
|
is_on_call = await self.bot.redis.sismember(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id)
|
|
)
|
|
|
|
if is_on_call:
|
|
await ctx.reply(
|
|
f"☎️ You're already on a call! Use `{ctx.clean_prefix}hangup` to hangup."
|
|
)
|
|
return
|
|
|
|
is_searching = await self.bot.redis.sismember(
|
|
constants.CHANNELS_SEARCH_QUEUE, str(channel_id)
|
|
)
|
|
|
|
if is_searching:
|
|
await ctx.reply(
|
|
f"☎️ You're already searching for a call! Use `{ctx.clean_prefix}hangup` to stop searching."
|
|
)
|
|
return
|
|
|
|
last_channel_calls = [
|
|
i[1]
|
|
for i in (
|
|
await models.CallUserChannelRelationship.filter(channel_id=channel_id)
|
|
.limit(5)
|
|
.distinct()
|
|
.order_by("-id")
|
|
.values_list("id", "call_id")
|
|
)
|
|
]
|
|
last_user_calls = [
|
|
i[1]
|
|
for i in (
|
|
await models.CallUserChannelRelationship.filter(user_id=user_id)
|
|
.limit(5)
|
|
.limit(5)
|
|
.distinct()
|
|
.order_by("-id")
|
|
.values_list("id", "call_id")
|
|
)
|
|
]
|
|
|
|
last_calls = set(last_user_calls + last_channel_calls)
|
|
|
|
last_calls_rss = await models.CallUserChannelRelationship.filter(
|
|
call_id__in=last_calls
|
|
)
|
|
last_calls_rss_channels_map = {}
|
|
|
|
for call_rs in last_calls_rss:
|
|
if call_rs.user_id == user_id or call_rs.channel_id == channel_id:
|
|
last_calls_rss_channels_map[call_rs.call_id] = call_rs.channel_id
|
|
|
|
last_calls_other_channels_ids = set()
|
|
|
|
for call_rs in last_calls_rss:
|
|
if last_calls_rss_channels_map[call_rs.call_id] != call_rs.channel_id:
|
|
last_calls_other_channels_ids.add(call_rs.channel_id)
|
|
|
|
other_channel_id: int = await self.bot.redis.spop(
|
|
constants.CHANNELS_SEARCH_QUEUE
|
|
)
|
|
i = 0
|
|
|
|
while True:
|
|
# For some weird reason, sometimes it returns None and sometimes b"None".
|
|
if other_channel_id is None or other_channel_id == b"None":
|
|
other_channel_id = None
|
|
break
|
|
|
|
# Redis returns the ID as a bytes string.
|
|
other_channel_id = int(other_channel_id)
|
|
|
|
if i == 5:
|
|
break
|
|
|
|
i += 1
|
|
|
|
if other_channel_id in last_calls_other_channels_ids:
|
|
await self.bot.redis.sadd(
|
|
constants.CHANNELS_SEARCH_QUEUE, str(other_channel_id)
|
|
)
|
|
|
|
other_channel_id = await self.bot.redis.spop(
|
|
constants.CHANNELS_SEARCH_QUEUE
|
|
)
|
|
|
|
if other_channel_id is None:
|
|
break
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
channel = await utils.get_channel(ctx.channel)
|
|
|
|
if other_channel_id is not None:
|
|
await self.bot.redis.sadd(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel_id)
|
|
)
|
|
|
|
msg_phone_answered = f"☎️ {secrets.choice(constants.MSGS_PHONE_ANSWERED)}"
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(other_channel_id).send(
|
|
msg_phone_answered
|
|
)
|
|
|
|
call = await models.Call.create(is_nsfw=ctx.channel.is_nsfw())
|
|
|
|
user, profile = await utils.get_user(ctx.author)
|
|
|
|
await models.CallUserChannelRelationship.create(
|
|
call=call,
|
|
channel_id=channel_id,
|
|
user=user,
|
|
profile=profile,
|
|
is_masked=user.is_masked,
|
|
mask=secrets.choice(constants.MASKS_LIST).value,
|
|
)
|
|
|
|
await models.Channel.filter(
|
|
id__in=[other_channel_id, channel.id]
|
|
).update(call=call)
|
|
|
|
except errors.DiscordException:
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET,
|
|
str(channel_id),
|
|
str(other_channel_id),
|
|
)
|
|
await self.bot.redis.sadd(
|
|
constants.CHANNELS_SEARCH_QUEUE, str(channel_id)
|
|
)
|
|
msg_phone_answered = ""
|
|
|
|
await ctx.send(
|
|
(
|
|
f"**TIP**: {secrets.choice(constants.TIPS).replace("{prefix}", ctx.clean_prefix)}\n"
|
|
f"☎️ {secrets.choice(constants.MSGS_PHONE_CALLING)}\n"
|
|
)
|
|
+ msg_phone_answered
|
|
)
|
|
|
|
else:
|
|
await self.bot.redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id))
|
|
|
|
await ctx.send(
|
|
(
|
|
f"**TIP**: {secrets.choice(constants.TIPS).replace("{prefix}", ctx.clean_prefix)}\n"
|
|
f"☎️ {secrets.choice(constants.MSGS_PHONE_CALLING)}\n"
|
|
)
|
|
)
|
|
|
|
@commands.command(aliases=["s"])
|
|
@commands.guild_only()
|
|
@checks.is_unbanned()
|
|
@transactions.atomic()
|
|
async def skip(self, ctx: commands.Context) -> None:
|
|
"""
|
|
Hang up the current call and search for another.
|
|
"""
|
|
|
|
if ctx.channel.is_nsfw():
|
|
await ctx.send("❌ Calls cannot be started in NSFW channels.")
|
|
return
|
|
|
|
user_id = ctx.author.id
|
|
channel_id = ctx.channel.id
|
|
|
|
is_on_call = await self.bot.redis.sismember(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id)
|
|
)
|
|
|
|
if not is_on_call:
|
|
await ctx.send("❌ You're not in a call!")
|
|
return
|
|
|
|
call = await models.Call.get(channels__id=channel_id).prefetch_related(
|
|
"channels"
|
|
)
|
|
|
|
call.ended_at = datetime.now(timezone.utc)
|
|
await call.save()
|
|
|
|
channel = find(lambda c: c.id == channel_id, call.channels)
|
|
other_channel = find(lambda c: c.id != channel_id, call.channels)
|
|
|
|
other_channel.call = None
|
|
await other_channel.save()
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(other_channel.id).send(
|
|
f"☎️ {secrets.choice(constants.MSGS_PHONE_HANGUP_YOU)}"
|
|
)
|
|
except errors.DiscordException:
|
|
pass
|
|
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel.id)
|
|
)
|
|
|
|
last_channel_calls = [
|
|
i[1]
|
|
for i in (
|
|
await models.CallUserChannelRelationship.filter(channel_id=channel_id)
|
|
.limit(5)
|
|
.distinct()
|
|
.order_by("-id")
|
|
.values_list("id", "call_id")
|
|
)
|
|
]
|
|
last_user_calls = [
|
|
i[1]
|
|
for i in (
|
|
await models.CallUserChannelRelationship.filter(user_id=user_id)
|
|
.limit(5)
|
|
.limit(5)
|
|
.distinct()
|
|
.order_by("-id")
|
|
.values_list("id", "call_id")
|
|
)
|
|
]
|
|
|
|
last_calls = set(last_user_calls + last_channel_calls)
|
|
|
|
last_calls_rss = await models.CallUserChannelRelationship.filter(
|
|
call_id__in=last_calls
|
|
)
|
|
last_calls_rss_channels_map = {}
|
|
|
|
for call_rs in last_calls_rss:
|
|
if call_rs.user_id == user_id or call_rs.channel_id == channel_id:
|
|
last_calls_rss_channels_map[call_rs.call_id] = call_rs.channel_id
|
|
|
|
last_calls_other_channels_ids = set()
|
|
|
|
for call_rs in last_calls_rss:
|
|
if last_calls_rss_channels_map[call_rs.call_id] != call_rs.channel_id:
|
|
last_calls_other_channels_ids.add(call_rs.channel_id)
|
|
|
|
other_channel_id: int = await self.bot.redis.spop(
|
|
constants.CHANNELS_SEARCH_QUEUE
|
|
)
|
|
i = 0
|
|
|
|
while True:
|
|
# For some weird reason, sometimes it returns None and sometimes b"b'None'".
|
|
if other_channel_id is None or other_channel_id == b"b'None'":
|
|
other_channel_id = None
|
|
break
|
|
|
|
# Redis returns the ID as a bytes string.
|
|
other_channel_id = int(other_channel_id)
|
|
|
|
if i == 5:
|
|
break
|
|
|
|
i += 1
|
|
|
|
if other_channel_id in last_calls_other_channels_ids:
|
|
await self.bot.redis.sadd(
|
|
constants.CHANNELS_SEARCH_QUEUE, str(other_channel_id)
|
|
)
|
|
|
|
other_channel_id = await self.bot.redis.spop(
|
|
constants.CHANNELS_SEARCH_QUEUE
|
|
)
|
|
|
|
if other_channel_id is None:
|
|
break
|
|
|
|
continue
|
|
|
|
break
|
|
|
|
if other_channel_id is not None:
|
|
await self.bot.redis.sadd(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel_id)
|
|
)
|
|
|
|
msg_phone_answered = f"☎️ {secrets.choice(constants.MSGS_PHONE_ANSWERED)}"
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(other_channel_id).send(
|
|
msg_phone_answered
|
|
)
|
|
|
|
call = await models.Call.create(is_nsfw=ctx.channel.is_nsfw())
|
|
|
|
user, profile = await utils.get_user(ctx.author)
|
|
|
|
await models.CallUserChannelRelationship.create(
|
|
call=call,
|
|
channel_id=channel_id,
|
|
user=user,
|
|
profile=profile,
|
|
is_masked=user.is_masked,
|
|
mask=secrets.choice(constants.MASKS_LIST).value,
|
|
)
|
|
|
|
await models.Channel.filter(
|
|
id__in=[other_channel_id, channel.id]
|
|
).update(call=call)
|
|
|
|
except errors.DiscordException:
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET,
|
|
str(channel_id),
|
|
str(other_channel_id),
|
|
)
|
|
await self.bot.redis.sadd(
|
|
constants.CHANNELS_SEARCH_QUEUE, str(channel_id)
|
|
)
|
|
msg_phone_answered = ""
|
|
|
|
await ctx.send(
|
|
(
|
|
f"**TIP**: {secrets.choice(constants.TIPS).replace("{prefix}", ctx.clean_prefix)}\n"
|
|
f"☎️ You skipped the call.\n"
|
|
)
|
|
+ (
|
|
msg_phone_answered
|
|
if msg_phone_answered
|
|
else f"☎️ {secrets.choice(constants.MSGS_PHONE_CALLING)}\n"
|
|
)
|
|
)
|
|
|
|
else:
|
|
await self.bot.redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id))
|
|
|
|
channel.call = None
|
|
await channel.save()
|
|
|
|
await ctx.send(
|
|
(
|
|
f"**TIP**: {secrets.choice(constants.TIPS).replace("{prefix}", ctx.clean_prefix)}\n"
|
|
f"☎️ You skipped the call.\n"
|
|
f"☎️ {secrets.choice(constants.MSGS_PHONE_CALLING)}\n"
|
|
)
|
|
)
|
|
|
|
@commands.command(aliases=["hang", "h"], extras={"help_name": "Hang Up"})
|
|
@commands.guild_only()
|
|
@checks.is_unbanned()
|
|
@transactions.atomic()
|
|
async def hangup(self, ctx: commands.Context) -> None:
|
|
"""
|
|
Hang up the phone.
|
|
"""
|
|
|
|
channel_id = ctx.channel.id
|
|
|
|
is_on_call = await self.bot.redis.sismember(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id)
|
|
)
|
|
|
|
if is_on_call:
|
|
channel = await utils.get_channel(ctx.channel)
|
|
|
|
call = await models.Call.get(id=channel.call_id).prefetch_related(
|
|
"channels"
|
|
)
|
|
|
|
other_channel: models.Channel = find(
|
|
lambda c: c.id != channel_id, call.channels
|
|
)
|
|
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel.id)
|
|
)
|
|
|
|
if other_channel is not None:
|
|
try:
|
|
await (await self.bot.get_or_fetch_channel(other_channel.id)).send(
|
|
f"☎️ {secrets.choice(constants.MSGS_PHONE_HANGUP_YOU)}"
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
pass
|
|
|
|
other_channel.call = None
|
|
|
|
await ctx.send(f"☎️ {secrets.choice(constants.MSGS_PHONE_HANGUP_ME)}")
|
|
|
|
channel.call = None
|
|
|
|
await models.Channel.bulk_update([channel] + ([other_channel] if other_channel is not None else []), ["call_id"])
|
|
|
|
call.ended_at = datetime.now(timezone.utc)
|
|
await call.save()
|
|
|
|
is_searching = await self.bot.redis.sismember(
|
|
constants.CHANNELS_SEARCH_QUEUE, str(channel_id)
|
|
)
|
|
|
|
if is_searching:
|
|
await self.bot.redis.srem(constants.CHANNELS_SEARCH_QUEUE, str(channel_id))
|
|
|
|
await ctx.send(f"☎️ {secrets.choice(constants.MSGS_PHONE_NO_ANSWER)}")
|
|
|
|
if not is_searching and not is_on_call:
|
|
await ctx.send(f"❌ {secrets.choice(constants.MSGS_PHONE_NOT_IN_CALL)}")
|
|
|
|
@commands.command()
|
|
@commands.guild_only()
|
|
@checks.is_unbanned()
|
|
async def report(
|
|
self, ctx: commands.Context, call_id: int | None = None, *, reason: str
|
|
) -> None:
|
|
"""
|
|
Report a call by it's ID, or the current/last call if no ID is specified.
|
|
|
|
Options:
|
|
- `[<call_id>]`: The ID of the call to report.
|
|
- `<reason>`: The reason why you're reporting the call.
|
|
"""
|
|
|
|
user, _ = await utils.get_user(ctx.author)
|
|
channel_id = ctx.channel.id
|
|
|
|
if call_id is None:
|
|
call_rs = (
|
|
await models.CallUserChannelRelationship.filter(channel_id=channel_id)
|
|
.order_by("-id")
|
|
.limit(1)
|
|
.first()
|
|
)
|
|
|
|
if call_rs is None:
|
|
await ctx.send("❌ No calls occurred on this channel.")
|
|
return
|
|
|
|
call_id = call_rs.call_id
|
|
|
|
call = await models.Call.get_or_none(id=call_id).prefetch_related(
|
|
"channels", "report", "call_rss"
|
|
)
|
|
|
|
if call is None:
|
|
await ctx.send(f"❌ There is no call with ID `{call_id}`.")
|
|
|
|
if call.report is not None:
|
|
await ctx.send(
|
|
"✅ This call has already been reported. Our moderator team will review it as soon as possible."
|
|
)
|
|
return
|
|
|
|
if not any(map(lambda rs: rs.user_id == user.id, call.call_rss)):
|
|
await ctx.send("❌ You have not participated in this call.")
|
|
return
|
|
|
|
await models.Report.create(
|
|
call_id=call_id, reported_by_id=user.id, reason=reason, priority=1
|
|
)
|
|
|
|
message = "✅ The call has been reported."
|
|
|
|
if call.ended_at is None:
|
|
call.ended_at = datetime.now(timezone.utc)
|
|
await call.save()
|
|
|
|
updated_channels = []
|
|
|
|
for channel in call.channels:
|
|
channel.call = None
|
|
updated_channels.append(channel)
|
|
|
|
await models.Channel.bulk_update(updated_channels, ["call_id"])
|
|
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET,
|
|
str(updated_channels[0]),
|
|
str(updated_channels[1]),
|
|
)
|
|
|
|
message += f"\n☎️ {secrets.choice(constants.MSGS_PHONE_HANGUP_ME)}"
|
|
|
|
other_channel = find(lambda c: c.id != channel_id, updated_channels)
|
|
|
|
await self.bot.redis.srem(
|
|
constants.CHANNELS_ONCALL_SET, str(other_channel.id), str(channel_id)
|
|
)
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(other_channel.id).send(
|
|
f"☎️ {secrets.choice(constants.MSGS_PHONE_HANGUP_YOU)}"
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
pass
|
|
|
|
await ctx.send(message)
|
|
|
|
@commands.command(aliases=["fr"], extras={"help_name": "Friend Request"})
|
|
@commands.guild_only()
|
|
@checks.is_unbanned()
|
|
@checks.is_on_call()
|
|
async def friendreq(self, ctx: commands.Context) -> None:
|
|
"""
|
|
Ask the other party to send you a friend request.
|
|
"""
|
|
|
|
user_id = ctx.author.id
|
|
channel_id = ctx.channel.id
|
|
|
|
call = await models.Call.get(channels__id=channel_id).prefetch_related(
|
|
"channels", "call_rss", "call_rss__profile"
|
|
)
|
|
|
|
other_channel = find(lambda c: c.id != channel_id, call.channels)
|
|
call_rs: models.CallUserChannelRelationship = find(
|
|
lambda rs: rs.user_id == user_id, call.call_rss
|
|
)
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(other_channel.id).send(
|
|
f"**{call_rs.mask_emoji + " " if call_rs.is_masked else ""}{call_rs.display_name}** (`{ctx.author.name}`) wants you to add them as a friend on Discord."
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
await ctx.send("⚠️ Your friend request couldn't be sent.")
|
|
return
|
|
|
|
await ctx.send("✅ Your friend request was sent!")
|
|
|
|
@commands.Cog.listener()
|
|
@commands.guild_only()
|
|
@transactions.atomic()
|
|
async def on_message(self, message: discord.Message) -> None:
|
|
"""Processes every message received.
|
|
|
|
Args:
|
|
message (discord.Message): The message received.
|
|
"""
|
|
|
|
if (
|
|
message.author.bot
|
|
or message.is_system()
|
|
or len(message.stickers) > 0
|
|
or not message.content
|
|
):
|
|
return
|
|
|
|
if (await self.bot.get_context(message)).valid:
|
|
return
|
|
|
|
user_id = message.author.id
|
|
channel_id = message.channel.id
|
|
|
|
is_on_call = await self.bot.redis.sismember(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id)
|
|
)
|
|
|
|
if not is_on_call:
|
|
return
|
|
|
|
is_banned = await self.bot.is_banned(user_id)
|
|
|
|
if is_banned:
|
|
return
|
|
|
|
channel = await utils.get_channel(message.channel)
|
|
|
|
try:
|
|
call = await models.Call.get(id=channel.call_id).prefetch_related(
|
|
"channels"
|
|
)
|
|
|
|
except exceptions.DoesNotExist:
|
|
await self.bot.redis.srem(constants.CHANNELS_ONCALL_SET, str(channel_id))
|
|
return
|
|
|
|
other_channel: models.Channel = find(
|
|
lambda c: c.id != channel_id, call.channels
|
|
)
|
|
|
|
user, profile = await utils.get_user(message.author)
|
|
|
|
call_rss = await models.CallUserChannelRelationship.filter(
|
|
call=call, channel=channel
|
|
).select_related("profile")
|
|
used_masks = [rs.mask for rs in call_rss]
|
|
|
|
call_rs, _ = await models.CallUserChannelRelationship.get_or_create(
|
|
{
|
|
"mask": secrets.choice(
|
|
[i for i in constants.MASKS_LIST if i not in used_masks]
|
|
),
|
|
"is_masked": user.is_masked,
|
|
"profile": profile,
|
|
},
|
|
call=call,
|
|
user=user,
|
|
channel=channel,
|
|
)
|
|
|
|
# Since .select_related() can't be used and profile was already fetched, we need to
|
|
# manually set it.
|
|
call_rs.profile = profile
|
|
|
|
blocked_word = censorship.moderate(message.content)
|
|
|
|
webhook_message = None
|
|
cleaned_content = formatting.clean(message, call_rss)
|
|
|
|
if blocked_word:
|
|
try:
|
|
await self.bot.get_partial_messageable(channel_id).send(
|
|
f"Your message wasn't delivered!\nBlocked word: `{blocked_word}`."
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
pass
|
|
|
|
prefix = await self.bot.get_prefix(server_id=other_channel.server_id)
|
|
|
|
try:
|
|
await self.bot.get_partial_messageable(other_channel.id).send(
|
|
f"**{call_rs.mask_emoji + " " if call_rs.is_masked else ""}{call_rs.display_name}** said something that's not allowed. If you think they're doing something inappropiate, use `{prefix}report`."
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
pass
|
|
|
|
else:
|
|
async with aiohttp.ClientSession() as session:
|
|
webhook = other_channel.webhook(session)
|
|
|
|
embed = None
|
|
|
|
if message.reference:
|
|
embed = await embeds.ReplyEmbed(message.reference, call_id=call.id)
|
|
|
|
webhook_message = await webhook.send(
|
|
cleaned_content,
|
|
username=call_rs.display_name,
|
|
avatar_url=call_rs.display_avatar,
|
|
embed=embed,
|
|
wait=True,
|
|
)
|
|
|
|
while True:
|
|
try:
|
|
message_row = await models.Message.create(
|
|
id=message.id,
|
|
call=call,
|
|
channel=channel,
|
|
user=user,
|
|
profile=profile,
|
|
mask=call_rs.mask if call_rs.is_masked else None,
|
|
propagation_id=webhook_message.id if webhook_message else None,
|
|
propagation_channel=other_channel if webhook_message else None,
|
|
)
|
|
break
|
|
|
|
except asyncpg.exceptions.DeadlockDetectedError:
|
|
continue
|
|
|
|
await models.MessageContent.create(
|
|
message=message_row,
|
|
content=cleaned_content,
|
|
was_propagated=not bool(blocked_word),
|
|
)
|
|
|
|
@commands.Cog.listener()
|
|
@commands.guild_only()
|
|
@transactions.atomic()
|
|
async def on_raw_message_delete(
|
|
self, payload: discord.RawMessageDeleteEvent
|
|
) -> None:
|
|
"""Processes a message deletion received.
|
|
|
|
Args:
|
|
payload (discord.RawMessageDeleteEvent): The received payload.
|
|
"""
|
|
|
|
message_id = payload.message_id
|
|
channel_id = payload.channel_id
|
|
|
|
is_on_call = await self.bot.redis.sismember(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id)
|
|
)
|
|
|
|
if not is_on_call:
|
|
return
|
|
|
|
message = await models.Message.get_or_none(id=message_id).select_related(
|
|
"propagation_channel"
|
|
)
|
|
|
|
if message is None:
|
|
return
|
|
|
|
other_channel = message.propagation_channel
|
|
|
|
if other_channel.call_id != message.call_id:
|
|
return
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
webhook = other_channel.webhook(session)
|
|
|
|
try:
|
|
await webhook.delete_message(message.propagation_id)
|
|
|
|
except errors.DiscordException:
|
|
# This message was already deleted.
|
|
pass
|
|
|
|
for replying_message in await models.Message.filter(
|
|
replying_to_id=message_id
|
|
).exclude(propagation_channel_id=channel_id):
|
|
try:
|
|
await webhook.edit_message(
|
|
replying_message.propagation_id, embed=None
|
|
)
|
|
|
|
except errors.DiscordException:
|
|
# This message was deleted.
|
|
pass
|
|
|
|
@commands.Cog.listener()
|
|
@commands.guild_only()
|
|
@transactions.atomic()
|
|
async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent) -> None:
|
|
"""Processes a message edit received.
|
|
|
|
Args:
|
|
payload (discord.RawMessageUpdateEvent): The event payload.
|
|
"""
|
|
|
|
if (
|
|
payload.cached_message is not None
|
|
and (
|
|
payload.cached_message.author.bot or payload.cached_message.is_system()
|
|
)
|
|
or "content" not in payload.data
|
|
):
|
|
return
|
|
|
|
message_id = payload.message_id
|
|
channel_id = payload.channel_id
|
|
content = payload.data["content"]
|
|
|
|
if censorship.moderate(content) is not None:
|
|
return
|
|
|
|
is_on_call = await self.bot.redis.sismember(
|
|
constants.CHANNELS_ONCALL_SET, str(channel_id)
|
|
)
|
|
|
|
if not is_on_call:
|
|
return
|
|
|
|
message = await models.Message.get_or_none(id=message_id).select_related(
|
|
"propagation_channel"
|
|
)
|
|
|
|
if message is None:
|
|
return
|
|
|
|
other_channel = message.propagation_channel
|
|
|
|
if other_channel.call_id != message.call_id:
|
|
return
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
webhook = other_channel.webhook(session)
|
|
|
|
try:
|
|
await webhook.edit_message(message.propagation_id, content=content)
|
|
|
|
except errors.DiscordException:
|
|
# This message was deleted.
|
|
pass
|
|
|
|
# TODO: Edit replies.
|
|
|
|
await models.MessageContent.create(message=message, content=content)
|
|
|
|
|
|
def setup(b: bot.Payphone) -> None:
|
|
"""Load the cog into the bot.
|
|
|
|
Args:
|
|
b (bot.Payphone): The bot instance to load the cog into.
|
|
"""
|
|
|
|
b.add_cog(Calls(b))
|