mirror of
https://github.com/allthingslinux/tux.git
synced 2024-10-02 16:43:12 +00:00
refactor: First starboard prototype
This commit is contained in:
parent
a6c8aaabae
commit
2e2a3b21b4
12 changed files with 494 additions and 51 deletions
13
poetry.lock
generated
13
poetry.lock
generated
|
@ -679,6 +679,17 @@ files = [
|
|||
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "emojis"
|
||||
version = "0.7.0"
|
||||
description = "Emojis for Python"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "emojis-0.7.0-py3-none-any.whl", hash = "sha256:a777926d8ab0bfdd51250e899a3b3524a1e969275ac8e747b4a05578fa597367"},
|
||||
{file = "emojis-0.7.0.tar.gz", hash = "sha256:5f437674da878170239af9a8196e50240b5922d6797124928574008442196b52"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.15.4"
|
||||
|
@ -2425,4 +2436,4 @@ multidict = ">=4.0"
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.12,<4"
|
||||
content-hash = "bbac8f5b09eb56eaa68724e6abfabfd587e57f38d101c5a421ee80c62c2e04dc"
|
||||
content-hash = "ff3627917ae2897246db3ffc167ff5dcb45f550119ee7116f453fd5091bac646"
|
||||
|
|
|
@ -40,14 +40,15 @@ enum CaseType {
|
|||
// Docs: https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-models
|
||||
// Docs: https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-attributes
|
||||
model Guild {
|
||||
guild_id BigInt @id
|
||||
guild_joined_at DateTime? @default(now())
|
||||
cases Case[]
|
||||
snippets Snippet[]
|
||||
notes Note[]
|
||||
reminders Reminder[]
|
||||
guild_config GuildConfig[]
|
||||
Starboard Starboard[]
|
||||
guild_id BigInt @id
|
||||
guild_joined_at DateTime? @default(now())
|
||||
cases Case[]
|
||||
snippets Snippet[]
|
||||
notes Note[]
|
||||
reminders Reminder[]
|
||||
guild_config GuildConfig[]
|
||||
Starboard Starboard[]
|
||||
StarboardMessage StarboardMessage[]
|
||||
|
||||
@@unique([guild_id])
|
||||
@@index([guild_id])
|
||||
|
@ -154,3 +155,19 @@ model Starboard {
|
|||
@@unique([guild_id])
|
||||
@@index([guild_id])
|
||||
}
|
||||
|
||||
model StarboardMessage {
|
||||
message_id BigInt @id
|
||||
message_content String
|
||||
message_created_at DateTime @default(now())
|
||||
message_expires_at DateTime
|
||||
message_channel_id BigInt
|
||||
message_user_id BigInt
|
||||
message_guild_id BigInt
|
||||
star_count Int @default(0)
|
||||
starboard_message_id BigInt
|
||||
guild Guild @relation(fields: [message_guild_id], references: [guild_id])
|
||||
|
||||
@@unique([message_id, message_guild_id])
|
||||
@@index([message_id, message_guild_id])
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ sentry-sdk = {extras = ["httpx", "loguru"], version = "^2.7.0"}
|
|||
types-aiofiles = "^24.1.0.20240626"
|
||||
types-psutil = "^6.0.0.20240621"
|
||||
typing-extensions = "^4.12.2"
|
||||
emojis = "^0.7.0"
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
mkdocs-material = "^9.5.30"
|
||||
|
|
216
tux/cogs/starboard/starboard.py
Normal file
216
tux/cogs/starboard/starboard.py
Normal file
|
@ -0,0 +1,216 @@
|
|||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import discord
|
||||
import emojis
|
||||
from discord.ext import commands
|
||||
from loguru import logger
|
||||
|
||||
from tux.bot import Tux
|
||||
from tux.database.controllers.starboard import StarboardController, StarboardMessageController
|
||||
from tux.utils import checks
|
||||
|
||||
|
||||
class Starboard(commands.Cog):
|
||||
def __init__(self, bot: Tux) -> None:
|
||||
self.bot = bot
|
||||
self.starboard_controller = StarboardController()
|
||||
self.starboard_message_controller = StarboardMessageController()
|
||||
|
||||
@commands.hybrid_group(
|
||||
name="starboard",
|
||||
usage="starboard <subcommand>",
|
||||
description="Configure the starboard for this server",
|
||||
)
|
||||
@commands.guild_only()
|
||||
@checks.has_pl(7) # server owner only
|
||||
async def starboard(self, ctx: commands.Context[Tux]) -> None:
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help("starboard")
|
||||
|
||||
@starboard.command(
|
||||
name="setup",
|
||||
aliases=["s"],
|
||||
usage="starboard setup <channel> <emoji> <threshold>",
|
||||
description="Configure the starboard for this server",
|
||||
)
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def configure_starboard(
|
||||
self,
|
||||
ctx: commands.Context[Tux],
|
||||
channel: discord.TextChannel,
|
||||
emoji: str,
|
||||
threshold: int,
|
||||
) -> None:
|
||||
"""
|
||||
Configure the starboard for this server.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
channel: discord.TextChannel
|
||||
The channel to configure the starboard for
|
||||
emoji: str
|
||||
The emoji to use for the starboard
|
||||
threshold: int
|
||||
The threshold for the starboard
|
||||
"""
|
||||
if not ctx.guild:
|
||||
await ctx.send("This command can only be used in a server.")
|
||||
return
|
||||
|
||||
try:
|
||||
if not emojis.count(emoji, unique=True) or emojis.count(emoji, unique=True) > 1: # type: ignore
|
||||
await ctx.send("Invalid emoji. Please use a single default Discord emoji.")
|
||||
return
|
||||
|
||||
if threshold < 1:
|
||||
await ctx.send("Threshold must be at least 1.")
|
||||
return
|
||||
|
||||
if not channel.permissions_for(ctx.guild.me).send_messages:
|
||||
await ctx.send(f"I don't have permission to send messages in {channel.mention}.")
|
||||
return
|
||||
|
||||
await self.starboard_controller.create_or_update_starboard(ctx.guild.id, channel.id, emoji, threshold)
|
||||
await ctx.send(
|
||||
f"Starboard configured successfully. Channel: {channel.mention}, Emoji: {emoji}, Threshold: {threshold}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error configuring starboard: {e!s}")
|
||||
await ctx.send(f"An error occurred while configuring the starboard: {e!s}")
|
||||
|
||||
@starboard.command(
|
||||
name="remove",
|
||||
aliases=["r"],
|
||||
usage="starboard remove",
|
||||
description="Remove the starboard configuration for this server",
|
||||
)
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def remove_starboard(self, ctx: commands.Context[Tux]) -> None:
|
||||
if not ctx.guild:
|
||||
await ctx.send("This command can only be used in a server.")
|
||||
return
|
||||
|
||||
try:
|
||||
result = await self.starboard_controller.delete_starboard_by_guild_id(ctx.guild.id)
|
||||
if result:
|
||||
await ctx.send("Starboard configuration removed successfully.")
|
||||
else:
|
||||
await ctx.send("No starboard configuration found for this server.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing starboard configuration: {e!s}")
|
||||
await ctx.send(f"An error occurred while removing the starboard configuration: {e!s}")
|
||||
|
||||
@commands.Cog.listener("on_reaction_add")
|
||||
async def starboard_check(self, reaction: discord.Reaction, user: discord.User) -> None:
|
||||
if not reaction.message.guild:
|
||||
return
|
||||
|
||||
try:
|
||||
starboard = await self.starboard_controller.get_starboard_by_guild_id(reaction.message.guild.id)
|
||||
if not starboard:
|
||||
return
|
||||
|
||||
if str(reaction.emoji) != starboard.starboard_emoji:
|
||||
logger.debug(
|
||||
f"Reaction emoji {reaction.emoji} does not match starboard emoji {starboard.starboard_emoji}",
|
||||
)
|
||||
return
|
||||
|
||||
# # Check if the user is not the author of the message
|
||||
# if user.id == reaction.message.author.id:
|
||||
# logger.debug(f"User {user.id} tried to star their own message")
|
||||
# return
|
||||
|
||||
reaction_count = sum(
|
||||
r.count for r in reaction.message.reactions if str(r.emoji) == starboard.starboard_emoji
|
||||
)
|
||||
|
||||
if reaction_count >= starboard.starboard_threshold:
|
||||
starboard_channel = reaction.message.guild.get_channel(starboard.starboard_channel_id)
|
||||
logger.info(f"Starboard channel: {starboard_channel}")
|
||||
|
||||
if not isinstance(starboard_channel, discord.TextChannel):
|
||||
logger.error(
|
||||
f"Starboard channel {starboard.starboard_channel_id} not found or is not a text channel",
|
||||
)
|
||||
return
|
||||
|
||||
await self.create_or_update_starboard_message(starboard_channel, reaction.message, reaction_count)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in starboard_check: {e!s}")
|
||||
|
||||
async def get_existing_starboard_message(
|
||||
self,
|
||||
starboard_channel: discord.TextChannel,
|
||||
original_message: discord.Message,
|
||||
) -> discord.Message | None:
|
||||
assert original_message.guild
|
||||
try:
|
||||
starboard_message = await self.starboard_message_controller.get_starboard_message_by_id(
|
||||
original_message.id,
|
||||
original_message.guild.id,
|
||||
)
|
||||
logger.info(f"Starboard message: {starboard_message}")
|
||||
if starboard_message:
|
||||
return await starboard_channel.fetch_message(starboard_message.starboard_message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error while fetching starboard message: {e!s}")
|
||||
|
||||
return None
|
||||
|
||||
async def create_or_update_starboard_message(
|
||||
self,
|
||||
starboard_channel: discord.TextChannel,
|
||||
original_message: discord.Message,
|
||||
reaction_count: int,
|
||||
) -> None:
|
||||
if not original_message.guild:
|
||||
logger.error("Original message has no guild")
|
||||
return
|
||||
|
||||
try:
|
||||
starboard = await self.starboard_controller.get_starboard_by_guild_id(original_message.guild.id)
|
||||
if not starboard:
|
||||
logger.error(f"No starboard configuration found for guild {original_message.guild.id}")
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
description=original_message.content,
|
||||
color=discord.Color.gold(),
|
||||
timestamp=original_message.created_at,
|
||||
)
|
||||
embed.set_author(
|
||||
name=original_message.author.display_name,
|
||||
icon_url=original_message.author.avatar.url if original_message.author.avatar else None,
|
||||
)
|
||||
embed.add_field(name="Source", value=f"[Jump to message]({original_message.jump_url})")
|
||||
embed.set_footer(text=f"Star count: {reaction_count} {starboard.starboard_emoji}")
|
||||
|
||||
if original_message.attachments:
|
||||
embed.set_image(url=original_message.attachments[0].url)
|
||||
|
||||
starboard_message = await self.get_existing_starboard_message(starboard_channel, original_message)
|
||||
|
||||
if starboard_message:
|
||||
await starboard_message.edit(embed=embed)
|
||||
else:
|
||||
starboard_message = await starboard_channel.send(embed=embed)
|
||||
|
||||
# Create or update the starboard message entry in the database
|
||||
await self.starboard_message_controller.create_or_update_starboard_message(
|
||||
message_id=original_message.id,
|
||||
message_content=original_message.content,
|
||||
message_expires_at=datetime.now(UTC) + timedelta(days=30),
|
||||
message_channel_id=original_message.channel.id,
|
||||
message_user_id=original_message.author.id,
|
||||
message_guild_id=original_message.guild.id,
|
||||
star_count=reaction_count,
|
||||
starboard_message_id=starboard_message.id,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error while creating or updating starboard message: {e!s}")
|
||||
|
||||
|
||||
async def setup(bot: Tux) -> None:
|
||||
await bot.add_cog(Starboard(bot))
|
|
@ -1,40 +0,0 @@
|
|||
from typing import cast
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from tux.bot import Tux
|
||||
from tux.ui.starboard.image_gen import generate_discord_message_image
|
||||
|
||||
|
||||
class Starboard(commands.Cog):
|
||||
def __init__(self, bot: Tux) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="test",
|
||||
usage="test",
|
||||
)
|
||||
async def ping(self, ctx: commands.Context[Tux], *, message: str) -> None:
|
||||
# nickname: str, pfp_url: str, role_color: str, message_content: str, image_attachment_url: str | None = None
|
||||
|
||||
member = cast(discord.Member, ctx.author)
|
||||
|
||||
nickname = member.display_name
|
||||
pfp_url = member.display_avatar.url
|
||||
|
||||
role_color = next(
|
||||
(f"#{role.color.value:06X}" for role in reversed(member.roles) if role.color != discord.Color.default()),
|
||||
"#FFFFFF",
|
||||
)
|
||||
message_content = message
|
||||
image_attachment_url = None
|
||||
|
||||
image = generate_discord_message_image(nickname, pfp_url, role_color, message_content, image_attachment_url)
|
||||
|
||||
image.save("image.png")
|
||||
await ctx.send(file=discord.File("image.png"))
|
||||
|
||||
|
||||
async def setup(bot: Tux) -> None:
|
||||
await bot.add_cog(Starboard(bot))
|
|
@ -4,7 +4,7 @@ from .guild_config import GuildConfigController
|
|||
from .note import NoteController
|
||||
from .reminder import ReminderController
|
||||
from .snippet import SnippetController
|
||||
from .starboard import StarboardController
|
||||
from .starboard import StarboardController, StarboardMessageController
|
||||
|
||||
|
||||
class DatabaseController:
|
||||
|
@ -16,3 +16,4 @@ class DatabaseController:
|
|||
self.guild = GuildController()
|
||||
self.guild_config = GuildConfigController()
|
||||
self.starboard = StarboardController()
|
||||
self.starboard_message = StarboardMessageController()
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from prisma.models import Guild, Starboard
|
||||
from datetime import datetime
|
||||
|
||||
from prisma.models import Guild, Starboard, StarboardMessage
|
||||
from tux.database.client import db
|
||||
|
||||
|
||||
|
@ -47,3 +49,78 @@ class StarboardController:
|
|||
|
||||
async def delete_starboard_by_guild_id(self, guild_id: int) -> Starboard | None:
|
||||
return await self.table.delete(where={"guild_id": guild_id})
|
||||
|
||||
|
||||
class StarboardMessageController:
|
||||
def __init__(self):
|
||||
self.table = db.starboardmessage
|
||||
self.guild_table = db.guild
|
||||
|
||||
async def ensure_guild_exists(self, guild_id: int) -> Guild | None:
|
||||
guild = await self.guild_table.find_unique(where={"guild_id": guild_id})
|
||||
if guild is None:
|
||||
return await self.guild_table.create(data={"guild_id": guild_id})
|
||||
return guild
|
||||
|
||||
async def get_starboard_message(self, message_id: int, guild_id: int) -> StarboardMessage | None:
|
||||
return await self.table.find_unique(
|
||||
where={"message_id_message_guild_id": {"message_id": message_id, "message_guild_id": guild_id}},
|
||||
)
|
||||
|
||||
async def create_or_update_starboard_message(
|
||||
self,
|
||||
message_id: int,
|
||||
message_content: str,
|
||||
message_expires_at: datetime,
|
||||
message_channel_id: int,
|
||||
message_user_id: int,
|
||||
message_guild_id: int,
|
||||
star_count: int,
|
||||
starboard_message_id: int,
|
||||
) -> StarboardMessage:
|
||||
await self.ensure_guild_exists(message_guild_id)
|
||||
|
||||
return await self.table.upsert(
|
||||
where={"message_id_message_guild_id": {"message_id": message_id, "message_guild_id": message_guild_id}},
|
||||
data={
|
||||
"create": {
|
||||
"message_id": message_id,
|
||||
"message_content": message_content,
|
||||
"message_expires_at": message_expires_at,
|
||||
"message_channel_id": message_channel_id,
|
||||
"message_user_id": message_user_id,
|
||||
"message_guild_id": message_guild_id,
|
||||
"star_count": star_count,
|
||||
"starboard_message_id": starboard_message_id,
|
||||
},
|
||||
"update": {
|
||||
"message_content": message_content,
|
||||
"message_expires_at": message_expires_at,
|
||||
"message_channel_id": message_channel_id,
|
||||
"message_user_id": message_user_id,
|
||||
"star_count": star_count,
|
||||
"starboard_message_id": starboard_message_id,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
async def delete_starboard_message(self, message_id: int, guild_id: int) -> StarboardMessage | None:
|
||||
return await self.table.delete(
|
||||
where={"message_id_message_guild_id": {"message_id": message_id, "message_guild_id": guild_id}},
|
||||
)
|
||||
|
||||
async def get_all_starboard_messages(self, guild_id: int) -> list[StarboardMessage]:
|
||||
return await self.table.find_many(where={"message_guild_id": guild_id})
|
||||
|
||||
async def update_star_count(self, message_id: int, guild_id: int, new_star_count: int) -> StarboardMessage | None:
|
||||
return await self.table.update(
|
||||
where={"message_id_message_guild_id": {"message_id": message_id, "message_guild_id": guild_id}},
|
||||
data={"star_count": new_star_count},
|
||||
)
|
||||
|
||||
async def get_starboard_message_by_id(self, original_message_id: int, guild_id: int) -> StarboardMessage | None:
|
||||
"""
|
||||
Get a starboard message by its ID and guild ID.
|
||||
This is the response by the bot, not the original message that was starred.
|
||||
"""
|
||||
return await self.table.find_first(where={"message_id": original_message_id, "message_guild_id": guild_id})
|
||||
|
|
10
typings/emojis/__init__.pyi
Normal file
10
typings/emojis/__init__.pyi
Normal file
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
This type stub file was generated by pyright.
|
||||
"""
|
||||
|
||||
from .emojis import count, decode, encode, get, iter
|
||||
|
||||
'''
|
||||
Emojis for Python 🐍
|
||||
'''
|
||||
__all__ = ['encode', 'decode', 'get', 'count', 'iter']
|
11
typings/emojis/db/__init__.pyi
Normal file
11
typings/emojis/db/__init__.pyi
Normal file
|
@ -0,0 +1,11 @@
|
|||
"""
|
||||
This type stub file was generated by pyright.
|
||||
"""
|
||||
|
||||
from .db import Emoji
|
||||
from .utils import get_categories, get_emoji_aliases, get_emoji_by_alias, get_emoji_by_code, get_emojis_by_category, get_emojis_by_tag, get_tags
|
||||
|
||||
'''
|
||||
Emoji database.
|
||||
'''
|
||||
__all__ = ['Emoji', 'get_emoji_aliases', 'get_emoji_by_code', 'get_emoji_by_alias', 'get_emojis_by_tag', 'get_emojis_by_category', 'get_tags', 'get_categories']
|
6
typings/emojis/db/db.pyi
Normal file
6
typings/emojis/db/db.pyi
Normal file
|
@ -0,0 +1,6 @@
|
|||
"""
|
||||
This type stub file was generated by pyright.
|
||||
"""
|
||||
|
||||
Emoji = ...
|
||||
EMOJI_DB = ...
|
64
typings/emojis/db/utils.pyi
Normal file
64
typings/emojis/db/utils.pyi
Normal file
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
This type stub file was generated by pyright.
|
||||
"""
|
||||
|
||||
def get_emoji_aliases(): # -> dict[Any, Any]:
|
||||
'''
|
||||
Returns all Emojis as a dict (key = alias, value = unicode).
|
||||
|
||||
:rtype: dict
|
||||
'''
|
||||
...
|
||||
|
||||
def get_emoji_by_code(code): # -> None:
|
||||
'''
|
||||
Returns Emoji by Unicode code.
|
||||
|
||||
:param code: Emoji Unicode code.
|
||||
:rtype: emojis.db.Emoji
|
||||
'''
|
||||
...
|
||||
|
||||
def get_emoji_by_alias(alias): # -> Emoji | None:
|
||||
'''
|
||||
Returns Emoji by alias.
|
||||
|
||||
:param alias: Emoji alias.
|
||||
:rtype: emojis.db.Emoji
|
||||
'''
|
||||
...
|
||||
|
||||
def get_emojis_by_tag(tag): # -> filter[Emoji]:
|
||||
'''
|
||||
Returns all Emojis from selected tag.
|
||||
|
||||
:param tag: Tag name to filter (case-insensitive).
|
||||
:rtype: iter
|
||||
'''
|
||||
...
|
||||
|
||||
def get_emojis_by_category(category): # -> filter[Any]:
|
||||
'''
|
||||
Returns all Emojis from selected category.
|
||||
|
||||
:param tag: Category name to filter (case-insensitive).
|
||||
:rtype: iter
|
||||
'''
|
||||
...
|
||||
|
||||
def get_tags(): # -> set[Any]:
|
||||
'''
|
||||
Returns all tags available.
|
||||
|
||||
:rtype: set
|
||||
'''
|
||||
...
|
||||
|
||||
def get_categories(): # -> set[Any]:
|
||||
'''
|
||||
Returns all categories available.
|
||||
|
||||
:rtype: set
|
||||
'''
|
||||
...
|
||||
|
69
typings/emojis/emojis.pyi
Normal file
69
typings/emojis/emojis.pyi
Normal file
|
@ -0,0 +1,69 @@
|
|||
"""
|
||||
This type stub file was generated by pyright.
|
||||
"""
|
||||
|
||||
ALIAS_TO_EMOJI = ...
|
||||
EMOJI_TO_ALIAS = ...
|
||||
EMOJI_TO_ALIAS_SORTED = ...
|
||||
RE_TEXT_TO_EMOJI_GROUP = ...
|
||||
RE_TEXT_TO_EMOJI = ...
|
||||
RE_EMOJI_TO_TEXT_GROUP = ...
|
||||
RE_EMOJI_TO_TEXT = ...
|
||||
def encode(msg): # -> str:
|
||||
'''
|
||||
Encode Emoji aliases into unicode Emoji values.
|
||||
|
||||
:param msg: String to encode.
|
||||
:rtype: str
|
||||
|
||||
Usage::
|
||||
|
||||
>>> import emojis
|
||||
>>> emojis.encode('This is a message with emojis :smile: :snake:')
|
||||
'This is a message with emojis 😄 🐍'
|
||||
'''
|
||||
...
|
||||
|
||||
def decode(msg): # -> str:
|
||||
'''
|
||||
Decode unicode Emoji values into Emoji aliases.
|
||||
|
||||
:param msg: String to decode.
|
||||
:rtype: str
|
||||
|
||||
Usage::
|
||||
|
||||
>>> import emojis
|
||||
>>> emojis.decode('This is a message with emojis 😄 🐍')
|
||||
'This is a message with emojis :smile: :snake:'
|
||||
'''
|
||||
...
|
||||
|
||||
def get(msg): # -> set[str]:
|
||||
'''
|
||||
Returns unique Emojis in the given string.
|
||||
|
||||
:param msg: String to search for Emojis.
|
||||
:rtype: set
|
||||
'''
|
||||
...
|
||||
|
||||
def iter(msg): # -> Generator[str, None, None]:
|
||||
'''
|
||||
Iterates over all Emojis found in the message.
|
||||
|
||||
:param msg: String to search for Emojis.
|
||||
:rtype: iterator
|
||||
'''
|
||||
...
|
||||
|
||||
def count(msg, unique=...): # -> int:
|
||||
'''
|
||||
Returns Emoji count in the given string.
|
||||
|
||||
:param msg: String to search for Emojis.
|
||||
:param unique: (optional) Boolean, return unique values only.
|
||||
:rtype: int
|
||||
'''
|
||||
...
|
||||
|
Loading…
Reference in a new issue