feat(bot): ✨ Add servers leaderboard.
This commit is contained in:
+9
-1
@@ -19,6 +19,7 @@ class Payphone(commands.AutoShardedBot):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
|
intents.guilds = True
|
||||||
|
|
||||||
if config.IS_DEBUG:
|
if config.IS_DEBUG:
|
||||||
intents.members = True
|
intents.members = True
|
||||||
@@ -280,7 +281,14 @@ class Payphone(commands.AutoShardedBot):
|
|||||||
prefix = prefix.decode()
|
prefix = prefix.decode()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
server, _ = await models.Server.get_or_create(id=server_id)
|
server, _ = await models.Server.update_or_create(
|
||||||
|
(
|
||||||
|
{"name": message.guild.name}
|
||||||
|
if message is not None and message.guild is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
id=server_id,
|
||||||
|
)
|
||||||
|
|
||||||
await redis.set(key, server.prefix, ex=60 * 60)
|
await redis.set(key, server.prefix, ex=60 * 60)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import discord
|
from datetime import datetime, timezone
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
from tortoise import transactions
|
import discord
|
||||||
|
from discord import errors
|
||||||
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
|
from tortoise import transactions, functions, exceptions
|
||||||
|
|
||||||
from payphone import bot
|
from payphone import bot
|
||||||
from payphone.db import models, utils
|
from payphone.db import models, utils
|
||||||
@@ -12,6 +15,82 @@ class Admin(base.Cog):
|
|||||||
def __init__(self, bot_instance: bot.Payphone):
|
def __init__(self, bot_instance: bot.Payphone):
|
||||||
self.bot = bot_instance
|
self.bot = bot_instance
|
||||||
|
|
||||||
|
self.update_leaderboard.add_exception_type(exceptions.ConfigurationError)
|
||||||
|
self.update_leaderboard.start()
|
||||||
|
|
||||||
|
def cog_unload(self) -> None:
|
||||||
|
self.update_leaderboard.cancel()
|
||||||
|
|
||||||
|
@tasks.loop(minutes=15)
|
||||||
|
async def update_leaderboard(self) -> None:
|
||||||
|
"""Updates the leaderboard."""
|
||||||
|
|
||||||
|
calls = (
|
||||||
|
await models.Call.annotate(messages_count=functions.Count("messages"))
|
||||||
|
.filter(
|
||||||
|
started_at__month=datetime.now(timezone.utc).month,
|
||||||
|
started_at__year=datetime.now(timezone.utc).year,
|
||||||
|
messages_count__gte=25,
|
||||||
|
)
|
||||||
|
.prefetch_related(
|
||||||
|
"call_rss", "call_rss__channel", "call_rss__channel__server"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ID to count
|
||||||
|
servers_count_map: dict[int, int] = {}
|
||||||
|
|
||||||
|
servers_map: dict[int, models.Server] = {
|
||||||
|
rs.channel.server_id: rs.channel.server
|
||||||
|
for call in calls
|
||||||
|
for rs in call.call_rss
|
||||||
|
}
|
||||||
|
|
||||||
|
for call in calls:
|
||||||
|
for rs in call.call_rss:
|
||||||
|
if rs.channel.server_id in servers_count_map:
|
||||||
|
servers_count_map[rs.channel.server.id] += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
servers_count_map[rs.channel.server_id] = 0
|
||||||
|
|
||||||
|
server_counts = list(servers_count_map.items())
|
||||||
|
|
||||||
|
server_counts.sort(key=lambda i: i[1], reverse=True)
|
||||||
|
|
||||||
|
for server_map in server_counts[:10]:
|
||||||
|
if servers_map[server_map[0]].name is None:
|
||||||
|
try:
|
||||||
|
servers_map[server_map[0]].name = (
|
||||||
|
await self.bot.fetch_guild(server_map[0])
|
||||||
|
).name
|
||||||
|
|
||||||
|
except errors.DiscordException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for i, server_map in enumerate(server_counts):
|
||||||
|
servers_map[server_map[0]].leaderboard_position = i + 1
|
||||||
|
servers_map[server_map[0]].leaderboard_calls_count = server_map[1]
|
||||||
|
|
||||||
|
await models.Server.bulk_update(
|
||||||
|
servers_map.values(),
|
||||||
|
["name", "leaderboard_position", "leaderboard_calls_count"],
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_guild_update(
|
||||||
|
self, _before: discord.Guild, after: discord.Guild
|
||||||
|
) -> None:
|
||||||
|
"""Updates a server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
_before (discord.Guild): The server before it being updated.
|
||||||
|
after (discord.Guild): The server after it being updated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
await models.Server.update_or_create({"name": after.name}, id=after.id)
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.has_guild_permissions(manage_guild=True)
|
@commands.has_guild_permissions(manage_guild=True)
|
||||||
|
|||||||
@@ -471,36 +471,62 @@ class Users(base.Cog):
|
|||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
@commands.command(aliases=["l", "leader"])
|
@commands.command(aliases=["l", "leader", "ranking"])
|
||||||
async def leaderboard(self, ctx: commands.Context) -> None:
|
async def leaderboard(self, ctx: commands.Context, board: str = "users") -> None:
|
||||||
"""
|
"""
|
||||||
Shows the leaderboard for the month.
|
Shows the leaderboard for the month.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `[<board>]`: The board to show (users/servers). Defaults to users.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
users_board_options = ["u", "user", "users"]
|
||||||
|
server_board_options = ["s", "server", "servers", "g", "guild", "guilds"]
|
||||||
|
|
||||||
|
if board.lower() not in users_board_options + server_board_options:
|
||||||
|
await ctx.send(
|
||||||
|
f"❌ This leaderboard does not exist. The leaderboards are `servers` and `users`, for example `{ctx.clean_prefix}leaderboard users`."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
table = ""
|
||||||
|
|
||||||
|
if board.lower() in users_board_options:
|
||||||
top_users = (
|
top_users = (
|
||||||
await models.User.filter(leaderboard_position__lte=10)
|
await models.User.filter(leaderboard_position__lte=10)
|
||||||
.prefetch_related("profiles")
|
.prefetch_related("profiles")
|
||||||
.order_by("leaderboard_position")
|
.order_by("leaderboard_position")
|
||||||
.limit(10)
|
.limit(10)
|
||||||
)
|
)
|
||||||
|
|
||||||
user_rows = [
|
user_rows = [
|
||||||
f"**#{pos + 1} - {user.profiles[0].name}**{" (You!)" if user.id == ctx.author.id else ""}\nParticipated in {user.leaderboard_calls_count} calls."
|
f"**#{pos + 1} - {user.profiles[0].name}**{" (You!)" if user.id == ctx.author.id else ""}\nParticipated in {user.leaderboard_calls_count} calls."
|
||||||
for pos, user in enumerate(top_users)
|
for pos, user in enumerate(top_users)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
table = "__**Top Users:**__\n\n" + "\n\n".join(user_rows)
|
||||||
|
|
||||||
|
else:
|
||||||
|
top_servers = (
|
||||||
|
await models.Server.filter(leaderboard_position__lte=10)
|
||||||
|
.order_by("leaderboard_position")
|
||||||
|
.limit(10)
|
||||||
|
)
|
||||||
|
server_rows = [
|
||||||
|
f"**#{pos + 1} - {server.name or "Updating..."}**{" (Here!)" if ctx.guild is not None and server.id == ctx.guild.id else ""}\nParticipated in {server.leaderboard_calls_count} calls."
|
||||||
|
for pos, server in enumerate(top_servers)
|
||||||
|
]
|
||||||
|
|
||||||
|
table = "__**Top Servers:**__\n\n" + "\n\n".join(server_rows)
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
color=constants.EMBED_COLOR,
|
color=constants.EMBED_COLOR,
|
||||||
title=f"Monthly Leaderboard ({datetime.now(timezone.utc).strftime("%B")})",
|
title=f"Monthly Leaderboard ({datetime.now(timezone.utc).strftime("%B")})",
|
||||||
description=(
|
description=(
|
||||||
"Payphone's monthly leaderboard is based on the amount of calls you've "
|
"Payphone's monthly leaderboard is based on the amount of calls you've "
|
||||||
"participated in with more than 25 messages sent (to prevent spam). The stats are "
|
"participated in with more than 25 messages sent (to prevent spam).\n"
|
||||||
"resetted every month.\n"
|
|
||||||
"\n"
|
|
||||||
"__**Top Users:**__\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
)
|
)
|
||||||
+ "\n\n".join(user_rows),
|
+ table,
|
||||||
author=self.bot.embed_author,
|
author=self.bot.embed_author,
|
||||||
footer=discord.EmbedFooter(
|
footer=discord.EmbedFooter(
|
||||||
text="The leaderboard is updated every 15 minutes."
|
text="The leaderboard is updated every 15 minutes."
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from tortoise import BaseDBAsyncClient
|
||||||
|
|
||||||
|
|
||||||
|
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||||
|
return """
|
||||||
|
ALTER TABLE "server" ADD "leaderboard_calls_count" INT NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE "server" ADD "leaderboard_position" INT NOT NULL DEFAULT 0;
|
||||||
|
CREATE INDEX "idx_server_leaderb_47c354" ON "server" ("leaderboard_position");"""
|
||||||
|
|
||||||
|
|
||||||
|
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||||
|
return """
|
||||||
|
DROP INDEX "idx_server_leaderb_47c354";
|
||||||
|
ALTER TABLE "server" DROP COLUMN "leaderboard_calls_count";
|
||||||
|
ALTER TABLE "server" DROP COLUMN "leaderboard_position";"""
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from tortoise import BaseDBAsyncClient
|
||||||
|
|
||||||
|
|
||||||
|
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||||
|
return """
|
||||||
|
ALTER TABLE "server" ADD "name" VARCHAR(100);"""
|
||||||
|
|
||||||
|
|
||||||
|
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||||
|
return """
|
||||||
|
ALTER TABLE "server" DROP COLUMN "name";"""
|
||||||
@@ -4,6 +4,7 @@ import enum
|
|||||||
|
|
||||||
from tortoise import models, fields
|
from tortoise import models, fields
|
||||||
|
|
||||||
|
import discord
|
||||||
from discord import webhook
|
from discord import webhook
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -72,13 +73,26 @@ class Profile(models.Model):
|
|||||||
|
|
||||||
return f"[{self.name}]({self.jump_url})"
|
return f"[{self.name}]({self.jump_url})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embed_author(self) -> discord.EmbedAuthor:
|
||||||
|
"""Returns this profile as an EmbedAuthor object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
discord.EmbedAuthor: The EmbedAuthor object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return discord.EmbedAuthor(name=self.name, icon_url=self.avatar_url)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [("user_id", "server_id")]
|
unique_together = [("user_id", "server_id")]
|
||||||
|
|
||||||
|
|
||||||
class Server(models.Model):
|
class Server(models.Model):
|
||||||
id: int = fields.BigIntField(primary_key=True)
|
id: int = fields.BigIntField(primary_key=True)
|
||||||
|
name: str = fields.CharField(max_length=100, null=True, default=None)
|
||||||
prefix: str = fields.CharField(max_length=5, default="p.")
|
prefix: str = fields.CharField(max_length=5, default="p.")
|
||||||
|
leaderboard_position: int = fields.IntField(default=0, db_index=True)
|
||||||
|
leaderboard_calls_count: int = fields.IntField(default=0)
|
||||||
channels: fields.BackwardFKRelation["Channel"]
|
channels: fields.BackwardFKRelation["Channel"]
|
||||||
profiles: fields.BackwardFKRelation["Profile"]
|
profiles: fields.BackwardFKRelation["Profile"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user