Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v5.0.3 #112

Merged
merged 15 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion core/bot_classes/axobot.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,20 @@ async def get_config(self, guild_id: discord.Guild | int, option: str):
options_list = await self.get_options_list()
return options_list.get(option, {"default": None})["default"]

async def get_guilds_with_config(self, option_name: str, option_value: str):
async def get_guilds_with_value(self, option_name: str, option_value: str):
"""Get a list of guilds with a specific config option set to a specific value"""
cog = self.get_cog("ServerConfig")
if cog and self.database_online:
return await cog.db_get_guilds_with_value(option_name, option_value)
return []

async def db_get_guilds_with_option(self, option_name: str):
"""Get a list of guilds with a specific config option set to anything but None"""
cog = self.get_cog("ServerConfig")
if cog and self.database_online:
return await cog.db_get_guilds_with_option(option_name)
return {}

async def get_recipient(self, channel: discord.DMChannel) -> discord.User | None:
"""Get the recipient of the given DM channel

Expand Down
2 changes: 1 addition & 1 deletion core/database/query/db_abstract_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def __aexit__(self, exc_type, value, traceback):

async def _format_query(self):
"Create a formatted query string from the query and its arguments."
await format_query(self.cursor, self.query, self.args)
return await format_query(self.cursor, self.query, self.args)

async def _save_execution_time(self, start_time: float):
await save_execution_time(self.bot, start_time)
1 change: 1 addition & 0 deletions core/emojis_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,5 +1012,6 @@ def get_emoji(self, name: str) -> discord.Emoji | None:
"minecraft": 958305433439834152,
"github": 1130174138267480244,
"readthedocs": 484841075001786368,
"bluesky": 1312561135794393232,
}
return self.bot.get_emoji(ids[name])
1 change: 1 addition & 0 deletions core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class RankCardsFlag(_BaseFlagClass):
1 << 14: "christmas23",
1 << 15: "april24",
1 << 16: "halloween24",
1 << 17: "christmas24",
}

class UserFlag(_BaseFlagClass):
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
# The short X.Y version
version = "5.0"
# The full version, including alpha/beta/rc tags
release = "5.0.2"
release = "5.0.3"

_documentation_name = "Axobot Documentation"

Expand Down
6 changes: 3 additions & 3 deletions docs/rss.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ To manage this plugin (add, edit or remove feeds), you will need at least the Ma
See the last post
-----------------

**Syntax:** :code:`last-post <name|link> [youtube|twitch|deviant|web]`
**Syntax:** :code:`last-post <name|link> [bluesky|deviantart|twitch|youtube|web]`

This command allows you to see the last post of a youtube channel, a user on Twitch or DeviantArt, or from any valid RSS feed. If you provide a full URL, the bot will automatically detect the type of feed. If you only provide the name of the channel, you will have to specify the type of feed.
This command allows you to see the last post of a YouTube channel, a user on Twitch/DeviantArt/Bluesky, or from any valid RSS feed. If you provide a full URL, the bot will automatically detect the type of feed. If you only provide the name of the channel, you will have to specify the type of feed.

.. note:: No specific permission is required for this command. Remember to allow the use of external emojis to get a prettier look.

Expand All @@ -27,7 +27,7 @@ Follow a feed

If you want to automatically track an rss feed, this command should be used. You can only track a maximum feeds, which will be reloaded every 20 minutes. Note that Minecraft server tracing also counts as an rss feed, and therefore will cost you a slot (which are currently limited to 10 per server).

For YouTube channels, simply give the link of the channel, so that the bot automatically detects the type and name of the channel. If no type is recognized, the 'web' type will be selected.
For YouTube, Twitch or Bluesky channels, simply give the link of the channel, so that the bot automatically detects the type and name of the channel. If no type is recognized, the 'web' type will be selected.

.. note:: To post a message, the bot does not need any specific permission. But if it's a Minecraft server feed (see the `corresponding section <minecraft.html>`__), don't forget the "`Read message history <perms.html#read-message-history>`__" permission!

Expand Down
46 changes: 46 additions & 0 deletions events-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -331,5 +331,51 @@
],
"probability": 0.05
}
},
"christmas-2024": {
"begin": "2024-12-01",
"end": "2024-12-31",
"type": "christmas",
"icon": "https://zrunner.me/cdn/christmas_2024.png",
"color": 4886759,
"objectives": [
{
"points": 300,
"reward_type": "role",
"role_id": 1312417584456532090
},
{
"points": 600,
"reward_type": "rankcard",
"rank_card": "christmas24",
"min_date": "2023-12-25"
}
],
"emojis": {
"reactions_list": [],
"triggers": [
"christmas",
"xmas",
"present",
"noël",
"santa",
"gift",
"cadeau",
"snow",
"neige",
"ice",
"glace",
"ho ho ho",
"reinder",
"sleight",
"traîneau",
"candy cane",
"sucre d'orge",
"elf ",
"lutin",
"cookie"
],
"probability": 0.1
}
}
}
6 changes: 4 additions & 2 deletions lang/bot_events/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
},
"soon": "Událost se blíží! Sledujte informace, protože začne %{date}",
"tictactoe": {
"reward-title": "Tic-tac-toe vítezství!",
"reward-desc": "Tím, že jsi vyhrál tuto hru, jsi získal ** body%{points} události**!"
"won": {
"title": "Tic-tac-toe vítezství!",
"desc": "Tím, že jsi vyhrál tuto hru, jsi získal ** body%{points} události**!"
}
},
"tip-title": "Náhodný tip",
"unclassed": "nezařazené",
Expand Down
10 changes: 8 additions & 2 deletions lang/bot_events/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@
},
"soon": "An event is coming soon! Watch for information as it will begin on %{date}",
"tictactoe": {
"reward-title": "Tic-tac-toe victory!",
"reward-desc": "By winning this game, you earned **%{points} event points**!"
"lost": {
"title": "Tic-tac-toe defeat",
"desc": "By losing this game, you lost **%{points} event points**..."
},
"won": {
"title": "Tic-tac-toe victory!",
"desc": "By winning this game, you earned **%{points} event points**!"
}
},
"tip-title": "Random tip",
"unclassed": "unclassed",
Expand Down
10 changes: 8 additions & 2 deletions lang/bot_events/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@
},
"soon": "Un événement arrive bientôt ! Surveillez les informations, car il débutera le %{date}",
"tictactoe": {
"reward-title": "Victoire au morpion !",
"reward-desc": "En remportant ce match, tu as gagné **%{points} points d'événement** !"
"lost": {
"title": "Défaite au morpion",
"desc": "En perdant ce match, tu as perdu **%{points} points d'événement**..."
},
"won": {
"title": "Victoire au morpion !",
"desc": "En remportant ce match, tu as gagné **%{points} points d'événement** !"
}
},
"tip-title": "Astuce aléatoire",
"unclassed": "Non classé",
Expand Down
6 changes: 4 additions & 2 deletions lang/bot_events/lolcat.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
},
"soon": "Hey hey hey! ur too soon, try checking on %{date}! :hype: ",
"tictactoe": {
"reward-title": "WOW, u won against the most supremely smartest tic-tac-toe AI ever!",
"reward-desc": "So uh, as a reward, I've gave u **%{points} event points**, check 'em with `/event profile`!"
"won": {
"title": "WOW, u won against the most supremely smartest tic-tac-toe AI ever!",
"desc": "So uh, as a reward, I've gave u **%{points} event points**, check 'em with `/event profile`!"
}
},
"tip-title": "Cool tip",
"unclassed": "unclassd",
Expand Down
3 changes: 3 additions & 0 deletions lang/rss/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"many": "Type the roles to mention for these feeds (either names, IDs or mentions), separated by spaces."
},
"ask-roles-hint-example": "For example:\n> Members \"Super VIP\" @Ping roles",
"bluesky": "Bluesky",
"bluesky-default-flow": "{logo} | New post of {author}! ({date})\n\n{title}\n\nLink: {link}\n\n{mentions}",
"bluesky-form-last": "{logo} | Here is the last post of {author}:\nWritten on {date}\n\n{title}\n\nLink: {url}",
"change-txt": {
"title": "Edit your feed text",
"label": "Message template",
Expand Down
3 changes: 3 additions & 0 deletions lang/rss/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"many": "Entrez les rôles à mentionner pour ces flux (au choix leurs noms, IDs ou mentions), séparés par des espaces."
},
"ask-roles-hint-example": "Par exemple:\n> Membres \"Super VIP\" @Notifs",
"bluesky": "Bluesky",
"bluesky-default-flow": "{logo} | Nouveau post de {author} ! ({date})\n\n{title}\n\nLien : {link}\n\n{mentions}",
"bluesky-form-last": "{logo} | Voici le dernier post de {author}:\nÉcrit le {date}\n\n{title}\n\nLien : {url}",
"change-txt": {
"title": "Modifiez le texte de votre flux",
"label": "Modèle de message",
Expand Down
85 changes: 79 additions & 6 deletions modules/bot_events/bot_events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import json
import logging
import time
from collections import defaultdict
from typing import AsyncGenerator, Literal

import discord
Expand All @@ -12,7 +14,7 @@
from core.formatutils import FormatUtils

from .data import EventData, EventRewardRole, EventType
from .subcogs import AbstractSubcog, RandomCollectSubcog
from .subcogs import AbstractSubcog, ChristmasSubcog


class BotEvents(commands.Cog):
Expand All @@ -32,15 +34,17 @@ def __init__(self, bot: Axobot):
self.coming_event_id: str | None = None
self.update_current_event()

self._subcog: AbstractSubcog = RandomCollectSubcog(
self._subcog: AbstractSubcog = ChristmasSubcog(
self.bot, self.current_event, self.current_event_data, self.current_event_id)
self.min_delay_between_ttt_wins = 30 # seconds between 2 tictactoe wins
self.last_ttt_win: dict[int, int] = defaultdict(int) # map of user_id -> timestamp

@property
def subcog(self) -> AbstractSubcog:
"Return the subcog populated with the current event data"
if self._subcog.current_event != self.current_event or self._subcog.current_event_data != self.current_event_data:
self.log.debug("Updating subcog with new data")
self._subcog = RandomCollectSubcog(self.bot, self.current_event, self.current_event_data, self.current_event_id)
self._subcog = ChristmasSubcog(self.bot, self.current_event, self.current_event_data, self.current_event_id)
return self._subcog

async def cog_load(self):
Expand Down Expand Up @@ -130,6 +134,62 @@ async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
if self.current_event:
await self.subcog.on_raw_reaction_add(payload)

@commands.Cog.listener()
async def on_tictactoe_win(self, interaction: discord.Interaction):
"Grant points to the user if they won a game of tictactoe"
if not self.current_event:
return
now = time.time()
# limit to 1 win per minute
if self.last_ttt_win[interaction.user.id] + self.min_delay_between_ttt_wins > now:
return
self.last_ttt_win[interaction.user.id] = now
points = 7
user = interaction.user
# send win reward embed
emb = discord.Embed(
title=await self.bot._(interaction, "bot_events.tictactoe.won.title"),
description=await self.bot._(interaction, "bot_events.tictactoe.won.desc", points=points),
color=self.current_event_data["color"],
)
emb.set_author(name=user.global_name, icon_url=user.display_avatar)
await interaction.followup.send(embed=emb)
# send card unlocked notif
await self.check_and_send_card_unlocked_notif(interaction, user)
# give points
await self.db_add_user_points(user.id, points)

@commands.Cog.listener()
async def on_tictactoe_lose(self, interaction: discord.Interaction):
"Grant points to the user if they lost a game of tictactoe"
if not self.current_event:
return
user = interaction.user
# check if user points is high enough to add some difficulty (ie. remove points on loss)
if not self.current_event_data["objectives"]:
return
user_points = await self.db_get_user_points(user.id)
first_cap = self.current_event_data["objectives"][0]["points"]
last_cap = self.current_event_data["objectives"][-1]["points"]
if user_points < first_cap * 1.1:
return
if first_cap == last_cap or user_points < last_cap * 1.1:
# remove 3 points if user has more than 110% of the first objective
points = -3
else:
# remove 5 points if user has more than 110% of the max objective
points = -5
# send loss reward embed
emb = discord.Embed(
title=await self.bot._(interaction, "bot_events.tictactoe.lost.title"),
description=await self.bot._(interaction, "bot_events.tictactoe.lost.desc", points=-points),
color=self.current_event_data["color"],
)
emb.set_author(name=user.global_name, icon_url=user.display_avatar)
await interaction.followup.send(embed=emb)
# remove points
await self.db_add_user_points(user.id, points)

events_main = app_commands.Group(
name="event",
description="Participate in bot special events!",
Expand Down Expand Up @@ -217,7 +277,7 @@ async def event_profile(self, interaction: discord.Interaction, user: discord.Us
await self.subcog.profile_cmd(interaction, user)

@events_main.command(name="collect")
@app_commands.checks.cooldown(1, 60)
@app_commands.checks.cooldown(2, 60)
@app_commands.check(database_connected)
async def event_collect(self, interaction: discord.Interaction):
"Get some event points every hour"
Expand All @@ -240,8 +300,11 @@ async def get_user_unlockable_rankcards(self, user: discord.User, points: int |
continue
yield reward["rank_card"]

async def check_and_send_card_unlocked_notif(self,
interaction: discord.Interaction | discord.TextChannel, user: discord.User | int):
async def check_and_send_card_unlocked_notif(
self,
interaction: discord.Interaction | discord.TextChannel,
user: discord.User | int
):
"Check if the user meets the requirements to unlock the event rank card, and send a notification if so"
if isinstance(user, int):
user = self.bot.get_user(user)
Expand Down Expand Up @@ -313,6 +376,16 @@ def _check_reward_date(self, reward_date: str | None):
parsed_date = datetime.datetime.strptime(reward_date, "%Y-%m-%d").replace(tzinfo=datetime.UTC)
return self.bot.utcnow() < parsed_date

async def db_get_user_points(self, user_id: int) -> int | None:
"Get the user's event points"
if not self.bot.database_online or self.bot.current_event is None:
return None
query = "SELECT `points` FROM `event_points` WHERE `user_id` = %s AND `beta` = %s;"
async with self.bot.db_main.read(query, (user_id, self.bot.beta), fetchone=True) as query_result:
if query_result:
return query_result["points"]
return None

async def db_add_user_points(self, user_id: int, points: int):
"Add some 'other' events points to a user"
try:
Expand Down
Loading
Loading