@@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse
|
||||
|
||||
from api.app import app
|
||||
from classes import PycordGuild
|
||||
from classes.errors import WalletNotFoundError, GuildNotFoundError
|
||||
from classes.errors import GuildNotFoundError, WalletNotFoundError
|
||||
from classes.wallet import Wallet
|
||||
|
||||
logger: Logger = logging.getLogger(__name__)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from .guild_rules import GuildRules
|
||||
from .pycord_guild import PycordGuild
|
||||
from .pycord_guild_colors import PycordGuildColors
|
||||
from .pycord_user import PycordUser
|
||||
|
||||
# from .pycord_guild_colors import PycordGuildColors
|
||||
# from .wallet import Wallet
|
||||
|
@@ -1,15 +1,108 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from bson import ObjectId
|
||||
from libbot.cache.classes import Cache
|
||||
|
||||
from classes.base import BaseCacheable
|
||||
from modules.database import col_custom_channels
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomChannel:
|
||||
class CustomChannel(BaseCacheable):
|
||||
"""Dataclass of DB entry of a custom channel"""
|
||||
|
||||
__slots__ = (
|
||||
"_id",
|
||||
"owner_id",
|
||||
"guild_id",
|
||||
"channel_id",
|
||||
"allow_comments",
|
||||
"allow_reactions",
|
||||
"created",
|
||||
"deleted",
|
||||
)
|
||||
__short_name__ = "channel"
|
||||
__collection__ = col_custom_channels
|
||||
|
||||
_id: ObjectId
|
||||
channel_id: int
|
||||
owner_id: int
|
||||
guild_id: int
|
||||
channel_id: int
|
||||
allow_comments: bool
|
||||
allow_reactions: bool
|
||||
created: datetime
|
||||
deleted: datetime | None
|
||||
|
||||
@classmethod
|
||||
async def from_id(
|
||||
cls,
|
||||
user_id: int,
|
||||
guild_id: int,
|
||||
channel_id: Optional[int] = None,
|
||||
cache: Optional[Cache] = None,
|
||||
) -> "CustomChannel":
|
||||
raise NotImplementedError()
|
||||
|
||||
def to_dict(self, json_compatible: bool = False) -> Dict[str, Any]:
|
||||
"""Convert PycordGuild object to a JSON representation.
|
||||
|
||||
Args:
|
||||
json_compatible (bool): Whether the JSON-incompatible objects like ObjectId need to be converted
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: JSON representation of PycordGuild
|
||||
"""
|
||||
return {
|
||||
"_id": self._id if not json_compatible else str(self._id),
|
||||
"id": self.id,
|
||||
"locale": self.locale,
|
||||
}
|
||||
|
||||
async def _set(self, cache: Optional[Cache] = None, **kwargs: Any) -> None:
|
||||
await super()._set(cache, **kwargs)
|
||||
|
||||
async def _remove(self, *args: str, cache: Optional[Cache] = None) -> None:
|
||||
await super()._remove(*args, cache=cache)
|
||||
|
||||
def _get_cache_key(self) -> str:
|
||||
return f"{self.__short_name__}_{self.id}"
|
||||
|
||||
def _update_cache(self, cache: Optional[Cache] = None) -> None:
|
||||
super()._update_cache(cache)
|
||||
|
||||
def _delete_cache(self, cache: Optional[Cache] = None) -> None:
|
||||
super()._delete_cache(cache)
|
||||
|
||||
@staticmethod
|
||||
def _entry_to_cache(db_entry: Dict[str, Any]) -> Dict[str, Any]:
|
||||
cache_entry: Dict[str, Any] = db_entry.copy()
|
||||
|
||||
cache_entry["_id"] = str(cache_entry["_id"])
|
||||
|
||||
return cache_entry
|
||||
|
||||
@staticmethod
|
||||
def _entry_from_cache(cache_entry: Dict[str, Any]) -> Dict[str, Any]:
|
||||
db_entry: Dict[str, Any] = cache_entry.copy()
|
||||
|
||||
db_entry["_id"] = ObjectId(db_entry["_id"])
|
||||
|
||||
return db_entry
|
||||
|
||||
# TODO Add documentation
|
||||
@staticmethod
|
||||
def get_defaults(guild_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": guild_id,
|
||||
"locale": None,
|
||||
}
|
||||
|
||||
# TODO Add documentation
|
||||
@staticmethod
|
||||
def get_default_value(key: str) -> Any:
|
||||
if key not in CustomChannel.get_defaults():
|
||||
raise KeyError(f"There's no default value for key '{key}' in CustomChannel")
|
||||
|
||||
return CustomChannel.get_defaults()[key]
|
||||
|
@@ -1 +1,2 @@
|
||||
from .message_events import MessageEvents
|
||||
from .punishment import Punishment
|
||||
|
8
classes/enums/punishment.py
Normal file
8
classes/enums/punishment.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Punishment(Enum):
|
||||
WARNING = 0
|
||||
MUTE = 1
|
||||
KICK = 2
|
||||
BAN = 3
|
@@ -2,6 +2,6 @@ class GuildNotFoundError(Exception):
|
||||
"""PycordGuild could not find guild with such an ID in the database"""
|
||||
|
||||
def __init__(self, guild_id: int) -> None:
|
||||
self.guild_id = guild_id
|
||||
self.guild_id: int = guild_id
|
||||
|
||||
super().__init__(f"Guild with id {self.guild_id} was not found")
|
||||
|
39
classes/guild_rules.py
Normal file
39
classes/guild_rules.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from classes.guild_rules_section import GuildRulesSection
|
||||
|
||||
# Example JSON
|
||||
# {
|
||||
# "header": "These are our rules",
|
||||
# "sections": [
|
||||
# {
|
||||
# "title": "1. First section",
|
||||
# "description": "This sections contains some rules",
|
||||
# "rules": [
|
||||
# {
|
||||
# "title": "Example rule",
|
||||
# "content": "Do not wear sandals while in socks!",
|
||||
# "punishment": 0,
|
||||
# }
|
||||
# ],
|
||||
# }
|
||||
# ],
|
||||
# }
|
||||
|
||||
|
||||
@dataclass
|
||||
class GuildRules:
|
||||
__slots__ = ("header", "sections")
|
||||
|
||||
header: str
|
||||
sections: List[GuildRulesSection]
|
||||
|
||||
# TODO Implement this method
|
||||
@classmethod
|
||||
def from_json(cls, db_entry: Dict[str, Any]) -> "GuildRules":
|
||||
raise NotImplementedError()
|
||||
|
||||
# TODO Implement this method
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
raise NotImplementedError()
|
13
classes/guild_rules_rule.py
Normal file
13
classes/guild_rules_rule.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from classes.enums import Punishment
|
||||
|
||||
|
||||
@dataclass
|
||||
class GuildRulesRule:
|
||||
__slots__ = ("title", "content", "punishment")
|
||||
|
||||
title: str
|
||||
content: str
|
||||
punishment: Literal[Punishment.WARNING, Punishment.MUTE, Punishment.KICK, Punishment.BAN]
|
13
classes/guild_rules_section.py
Normal file
13
classes/guild_rules_section.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from classes.guild_rules_rule import GuildRulesRule
|
||||
|
||||
|
||||
@dataclass
|
||||
class GuildRulesSection:
|
||||
__slots__ = ("title", "description", "rules")
|
||||
|
||||
title: str
|
||||
description: str
|
||||
rules: List[GuildRulesRule]
|
@@ -1,16 +1,19 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from logging import Logger
|
||||
from typing import Any, Literal
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Literal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from discord import User
|
||||
from discord import Activity, ActivityType, Guild, User
|
||||
from libbot.cache.classes import CacheMemcached, CacheRedis
|
||||
from libbot.cache.manager import create_cache_client
|
||||
from libbot.i18n import BotLocale
|
||||
from libbot.pycord.classes import PycordBot as LibPycordBot
|
||||
from libbot.utils import json_read
|
||||
|
||||
from classes import PycordUser
|
||||
from classes import PycordGuild, PycordUser
|
||||
from modules.database import _update_database_indexes
|
||||
|
||||
logger: Logger = logging.getLogger(__name__)
|
||||
@@ -56,6 +59,60 @@ class PycordBot(LibPycordBot):
|
||||
key, *args, locale=None if locale is None else locale.split("-")[0]
|
||||
)
|
||||
|
||||
# TODO Add rollback mechanism for recovery from broken config
|
||||
# TODO Add documentation
|
||||
def reload(self) -> None:
|
||||
config_old: Dict[str, Any] = self.config.copy()
|
||||
|
||||
try:
|
||||
self.config = json_read(Path("config.json"))
|
||||
|
||||
self.bot_locale = BotLocale(
|
||||
default_locale=self.config["locale"],
|
||||
)
|
||||
self.default_locale = self.bot_locale.default
|
||||
self.locales = self.bot_locale.locales
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"Could not reload the configuration, restoring old in-memory values due to: %s",
|
||||
exc,
|
||||
exc_info=exc,
|
||||
)
|
||||
|
||||
self.config = config_old
|
||||
|
||||
raise exc
|
||||
|
||||
async def set_status(self) -> None:
|
||||
activity_enabled: bool = self.config["bot"]["status"]["enabled"]
|
||||
activity_id: int = self.config["bot"]["status"]["activity_type"]
|
||||
activity_message: str = self.config["bot"]["status"]["activity_text"]
|
||||
|
||||
if not activity_enabled:
|
||||
logger.info("Activity is disabled")
|
||||
return
|
||||
|
||||
try:
|
||||
activity_type: ActivityType = ActivityType(activity_id)
|
||||
except Exception as exc:
|
||||
logger.debug(
|
||||
"Could not activity with ID %s to ActivityType due to: %s",
|
||||
activity_id,
|
||||
exc,
|
||||
exc_info=exc,
|
||||
)
|
||||
logger.error("Activity type with ID %s is not supported", activity_id)
|
||||
return
|
||||
|
||||
await self.change_presence(activity=Activity(type=activity_type, name=activity_message))
|
||||
|
||||
logger.info(
|
||||
"Set activity type to %s (%s) with message '%s'",
|
||||
activity_id,
|
||||
activity_type.name,
|
||||
activity_message,
|
||||
)
|
||||
|
||||
async def find_user(self, user: int | User) -> PycordUser:
|
||||
"""Find User by its ID or User object.
|
||||
|
||||
@@ -74,6 +131,24 @@ class PycordBot(LibPycordBot):
|
||||
else await PycordUser.from_id(user.id, cache=self.cache)
|
||||
)
|
||||
|
||||
async def find_guild(self, guild: int | Guild) -> PycordGuild:
|
||||
"""Find Guild by its ID or Guild object.
|
||||
|
||||
Args:
|
||||
guild (int | Guild): ID or User object to extract ID from
|
||||
|
||||
Returns:
|
||||
PycordGuild: Guild object
|
||||
|
||||
Raises:
|
||||
GuildNotFoundException: Guild was not found and creation was not allowed
|
||||
"""
|
||||
return (
|
||||
await PycordGuild.from_id(guild, cache=self.cache)
|
||||
if isinstance(guild, int)
|
||||
else await PycordGuild.from_id(guild.id, cache=self.cache)
|
||||
)
|
||||
|
||||
async def start(self, *args: Any, **kwargs: Any) -> None:
|
||||
await self._schedule_tasks()
|
||||
await _update_database_indexes()
|
||||
|
@@ -1,36 +1,74 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from bson import ObjectId
|
||||
from libbot.cache.classes import Cache
|
||||
from pymongo.results import InsertOneResult
|
||||
|
||||
from classes import GuildRules
|
||||
from classes.base import BaseCacheable
|
||||
from classes.errors import GuildNotFoundError
|
||||
from modules.database import col_guilds
|
||||
from modules.utils import restore_from_cache
|
||||
|
||||
|
||||
@dataclass
|
||||
class PycordGuild:
|
||||
class PycordGuild(BaseCacheable):
|
||||
"""Dataclass of DB entry of a guild"""
|
||||
|
||||
__slots__ = ("_id", "id", "locale", "rules")
|
||||
__short_name__ = "guild"
|
||||
__collection__ = col_guilds
|
||||
|
||||
_id: ObjectId
|
||||
id: int
|
||||
|
||||
def __init__(self) -> None:
|
||||
raise NotImplementedError()
|
||||
locale: str
|
||||
rules: GuildRules
|
||||
|
||||
@classmethod
|
||||
async def from_id(
|
||||
cls, guild_id: int, allow_creation: bool = True, cache: Optional[Cache] = None
|
||||
) -> "PycordGuild":
|
||||
"""Find guild in database and create new record if guild does not exist.
|
||||
"""Find the guild by its ID and construct PycordEventStage from database entry.
|
||||
|
||||
Args:
|
||||
guild_id (int): User's Discord ID
|
||||
allow_creation (:obj:`bool`, optional): Create new guild record if none found in the database
|
||||
cache (:obj:`Cache`, optional): Cache engine to get the cache from
|
||||
guild_id (int): ID of the guild to look up.
|
||||
allow_creation (:obj:`bool`, optional): Create a new record if none found in the database.
|
||||
cache (:obj:`Cache`, optional): Cache engine that will be used to fetch and update the cache.
|
||||
|
||||
Returns:
|
||||
PycordGuild: User object
|
||||
PycordGuild: Object of the found or newly created guild.
|
||||
|
||||
Raises:
|
||||
GuildNotFoundError: User was not found and creation was not allowed
|
||||
GuildNotFoundError: Guild with such ID does not exist and creation was not allowed.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
cached_entry: Dict[str, Any] | None = restore_from_cache(
|
||||
cls.__short_name__, guild_id, cache=cache
|
||||
)
|
||||
|
||||
if cached_entry is not None:
|
||||
return cls(**cls._entry_from_cache(cached_entry))
|
||||
|
||||
db_entry: Dict[str, Any] | None = await cls.__collection__.find_one({"id": guild_id})
|
||||
|
||||
if db_entry is None:
|
||||
if not allow_creation:
|
||||
raise GuildNotFoundError(guild_id)
|
||||
|
||||
db_entry = PycordGuild.get_defaults(guild_id)
|
||||
|
||||
insert_result: InsertOneResult = await cls.__collection__.insert_one(db_entry)
|
||||
|
||||
db_entry["_id"] = insert_result.inserted_id
|
||||
|
||||
if cache is not None:
|
||||
cache.set_json(f"{cls.__short_name__}_{guild_id}", cls._entry_to_cache(db_entry))
|
||||
|
||||
db_entry["rules"] = (
|
||||
None if db_entry["rules"] is None else GuildRules.from_json(db_entry["rules"])
|
||||
)
|
||||
|
||||
return cls(**db_entry)
|
||||
|
||||
def to_dict(self, json_compatible: bool = False) -> Dict[str, Any]:
|
||||
"""Convert PycordGuild object to a JSON representation.
|
||||
@@ -44,4 +82,64 @@ class PycordGuild:
|
||||
return {
|
||||
"_id": self._id if not json_compatible else str(self._id),
|
||||
"id": self.id,
|
||||
"locale": self.locale,
|
||||
"rules": None if self.rules is None else self.rules.to_dict(),
|
||||
}
|
||||
|
||||
async def _set(self, cache: Optional[Cache] = None, **kwargs: Any) -> None:
|
||||
await super()._set(cache, **kwargs)
|
||||
|
||||
async def _remove(self, *args: str, cache: Optional[Cache] = None) -> None:
|
||||
await super()._remove(*args, cache=cache)
|
||||
|
||||
def _get_cache_key(self) -> str:
|
||||
return f"{self.__short_name__}_{self.id}"
|
||||
|
||||
def _update_cache(self, cache: Optional[Cache] = None) -> None:
|
||||
super()._update_cache(cache)
|
||||
|
||||
def _delete_cache(self, cache: Optional[Cache] = None) -> None:
|
||||
super()._delete_cache(cache)
|
||||
|
||||
@staticmethod
|
||||
def _entry_to_cache(db_entry: Dict[str, Any]) -> Dict[str, Any]:
|
||||
cache_entry: Dict[str, Any] = db_entry.copy()
|
||||
|
||||
cache_entry["_id"] = str(cache_entry["_id"])
|
||||
|
||||
if cache_entry["rules"] is not None and isinstance(cache_entry["rules"], GuildRules):
|
||||
cache_entry["rules"] = cache_entry["rules"].to_json()
|
||||
|
||||
return cache_entry
|
||||
|
||||
@staticmethod
|
||||
def _entry_from_cache(cache_entry: Dict[str, Any]) -> Dict[str, Any]:
|
||||
db_entry: Dict[str, Any] = cache_entry.copy()
|
||||
|
||||
db_entry["_id"] = ObjectId(db_entry["_id"])
|
||||
db_entry["rules"] = (
|
||||
None if db_entry["rules"] is None else GuildRules.from_json(db_entry["rules"])
|
||||
)
|
||||
|
||||
return db_entry
|
||||
|
||||
# TODO Add documentation
|
||||
@staticmethod
|
||||
def get_defaults(guild_id: Optional[int] = None) -> Dict[str, Any]:
|
||||
return {"id": guild_id, "locale": None, "rules": None}
|
||||
|
||||
# TODO Add documentation
|
||||
@staticmethod
|
||||
def get_default_value(key: str) -> Any:
|
||||
if key not in PycordGuild.get_defaults():
|
||||
raise KeyError(f"There's no default value for key '{key}' in PycordGuild")
|
||||
|
||||
return PycordGuild.get_defaults()[key]
|
||||
|
||||
# TODO Add documentation
|
||||
async def set_rules(self, rules: GuildRules, cache: Optional[Cache] = None) -> None:
|
||||
await self.update(cache=cache, rules=rules.to_dict())
|
||||
|
||||
# TODO Add documentation
|
||||
async def clear_rules(self, cache: Optional[Cache] = None) -> None:
|
||||
await self.update(cache=cache, rules=None)
|
||||
|
@@ -10,6 +10,7 @@ from pymongo.results import InsertOneResult
|
||||
from classes.base import BaseCacheable
|
||||
from classes.errors.pycord_user import UserNotFoundError
|
||||
from classes.wallet import Wallet
|
||||
from modules.database import col_users
|
||||
from modules.utils import restore_from_cache
|
||||
|
||||
logger: Logger = logging.getLogger(__name__)
|
||||
@@ -20,6 +21,8 @@ class PycordUser(BaseCacheable):
|
||||
"""Dataclass of DB entry of a user"""
|
||||
|
||||
__slots__ = ("_id", "id", "guild_id")
|
||||
__short_name__ = "user"
|
||||
__collection__ = col_users
|
||||
|
||||
_id: ObjectId
|
||||
id: int
|
||||
|
@@ -9,9 +9,9 @@ from pymongo.results import InsertOneResult
|
||||
|
||||
from classes.errors.wallet import (
|
||||
WalletBalanceLimitExceeded,
|
||||
WalletInsufficientFunds,
|
||||
WalletNotFoundError,
|
||||
WalletOverdraftLimitExceeded,
|
||||
WalletInsufficientFunds,
|
||||
)
|
||||
from modules.database import col_wallets
|
||||
|
||||
|
29
cogs/cog_admin.py
Normal file
29
cogs/cog_admin.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from discord import ApplicationContext, Cog, slash_command
|
||||
from libbot.i18n import _, in_every_locale
|
||||
|
||||
from classes.pycord_bot import PycordBot
|
||||
|
||||
|
||||
class CogAdmin(Cog):
|
||||
"""Cog with the guessing command."""
|
||||
|
||||
def __init__(self, bot: PycordBot):
|
||||
self.bot: PycordBot = bot
|
||||
|
||||
@slash_command(
|
||||
name="reload",
|
||||
description=_("description", "commands", "reload"),
|
||||
description_localizations=in_every_locale("description", "commands", "reload"),
|
||||
)
|
||||
async def command_guess(self, ctx: ApplicationContext) -> None:
|
||||
try:
|
||||
self.bot.reload()
|
||||
await self.bot.set_status()
|
||||
|
||||
await ctx.respond("Okay.")
|
||||
except Exception as exc:
|
||||
await ctx.respond(f"Not okay:\n```\n{exc}\n```", ephemeral=True)
|
||||
|
||||
|
||||
def setup(bot: PycordBot) -> None:
|
||||
bot.add_cog(CogAdmin(bot))
|
14
cogs/cog_organizational.py
Normal file
14
cogs/cog_organizational.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from discord import Cog
|
||||
|
||||
from classes.pycord_bot import PycordBot
|
||||
|
||||
|
||||
class CogOrganizational(Cog):
|
||||
"""Cog with the guessing command."""
|
||||
|
||||
def __init__(self, bot: PycordBot):
|
||||
self.bot: PycordBot = bot
|
||||
|
||||
|
||||
def setup(bot: PycordBot) -> None:
|
||||
bot.add_cog(CogOrganizational(bot))
|
24
cogs/cog_utility.py
Normal file
24
cogs/cog_utility.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from logging import Logger
|
||||
|
||||
from discord import Cog
|
||||
|
||||
from classes.pycord_bot import PycordBot
|
||||
from modules.utils import get_logger
|
||||
|
||||
logger: Logger = get_logger(__name__)
|
||||
|
||||
|
||||
class CogUtility(Cog):
|
||||
def __init__(self, bot: PycordBot):
|
||||
self.bot: PycordBot = bot
|
||||
|
||||
@Cog.listener()
|
||||
async def on_ready(self) -> None:
|
||||
"""Listener for the event when bot connects to Discord and becomes "ready"."""
|
||||
logger.info("Logged in as %s", self.bot.user)
|
||||
|
||||
await self.bot.set_status()
|
||||
|
||||
|
||||
def setup(bot: PycordBot) -> None:
|
||||
bot.add_cog(CogUtility(bot))
|
73
cogs/cog_wallet.py
Normal file
73
cogs/cog_wallet.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import logging
|
||||
from logging import Logger
|
||||
|
||||
from discord import ApplicationContext, SlashCommandGroup, User, option
|
||||
from discord.ext import commands
|
||||
|
||||
from classes.errors import WalletInsufficientFunds
|
||||
from classes.pycord_bot import PycordBot
|
||||
from classes.wallet import Wallet
|
||||
|
||||
logger: Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CogWallet(commands.Cog):
|
||||
def __init__(self, client: PycordBot):
|
||||
self.client: PycordBot = client
|
||||
|
||||
command_group: SlashCommandGroup = SlashCommandGroup("wallet", "Wallet management")
|
||||
|
||||
@command_group.command(
|
||||
name="balance",
|
||||
description="View wallet's balance",
|
||||
)
|
||||
@option("user", description="User whose balance to check (if not your own)", required=False)
|
||||
async def command_wallet_balance(self, ctx: ApplicationContext, user: User = None) -> None:
|
||||
wallet: Wallet = await Wallet.from_id(
|
||||
ctx.user.id if not user else user.id, ctx.guild_id
|
||||
)
|
||||
|
||||
await ctx.respond(
|
||||
self.client._("balance_own", "messages", "wallet", locale=ctx.locale).format(
|
||||
balance=wallet.balance
|
||||
)
|
||||
if user is None
|
||||
else self.client._("balance_user", "messages", "wallet", locale=ctx.locale).format(
|
||||
balance=wallet.balance, user=user.display_name
|
||||
)
|
||||
)
|
||||
|
||||
@command_group.command(
|
||||
name="transfer",
|
||||
description="View wallet's balance",
|
||||
)
|
||||
@option("user", description="Recipient")
|
||||
@option("amount", description="Amount", min_value=0.01)
|
||||
async def command_wallet_transfer(
|
||||
self, ctx: ApplicationContext, user: User, amount: float
|
||||
) -> None:
|
||||
amount = round(amount, 2)
|
||||
|
||||
# Guild will be needed for overdraft options
|
||||
# guild: PycordGuild = await PycordGuild.from_id(ctx.guild_id)
|
||||
wallet: Wallet = await Wallet.from_id(ctx.user.id, ctx.guild_id)
|
||||
|
||||
try:
|
||||
await wallet.transfer(user.id, ctx.guild_id, amount)
|
||||
except WalletInsufficientFunds:
|
||||
await ctx.respond(
|
||||
self.client._(
|
||||
"transfer_insufficient_funds", "messages", "wallet", locale=ctx.locale
|
||||
).format(amount=round(abs(wallet.balance - amount), 2))
|
||||
)
|
||||
return
|
||||
|
||||
await ctx.respond(
|
||||
self.client._("transfer_success", "messages", "wallet", locale=ctx.locale).format(
|
||||
amount=amount, recipient=user.display_name
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def setup(client: PycordBot) -> None:
|
||||
client.add_cog(CogWallet(client))
|
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
from logging import Logger
|
||||
|
||||
from discord import ApplicationContext, SlashCommandGroup, option, User
|
||||
from discord import ApplicationContext, SlashCommandGroup, User, option
|
||||
from discord.ext import commands
|
||||
|
||||
from classes.errors import WalletInsufficientFunds
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"locale": "en",
|
||||
"locale": "en-US",
|
||||
"debug": false,
|
||||
"bot": {
|
||||
"owners": [
|
||||
|
@@ -1,4 +1,28 @@
|
||||
{
|
||||
"messages": {
|
||||
"welcome": {
|
||||
"morning": [
|
||||
"{0} Добрий ранок та ласкаво просимо! {1}"
|
||||
],
|
||||
"midday": [
|
||||
"{0} Добрий день! Ласкаво просимо! {1}"
|
||||
],
|
||||
"evening": [
|
||||
"{0} Добрий вечір! Ласкаво просимо! {1}"
|
||||
],
|
||||
"night": [
|
||||
"{0} Доброї ночі! Ласкаво просимо! {1}"
|
||||
],
|
||||
"unknown": [
|
||||
"{0} Вітаннячко! Ласкаво просимо! {1}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"reload": {
|
||||
"description": "Reload bot's configuration"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"bite": {
|
||||
"name": "Вкусити",
|
||||
@@ -33,46 +57,25 @@
|
||||
"text": "**{user_name}** підморгує **{target_name}**"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"welcome": {
|
||||
"morning": [
|
||||
"{0} Добрий ранок та ласкаво просимо! {1}",
|
||||
"{0} Доброго ранку та ласкаво просимо! {1}",
|
||||
"{0} Вітаннячко! Ласкаво просимо! {1}",
|
||||
"{0} Доброго ранку! Ласкаво просимо! {1}"
|
||||
],
|
||||
"midday": [
|
||||
"{0} Добрий день! Ласкаво просимо! {1}",
|
||||
"{0} Добридень! Ласкаво просимо! {1}",
|
||||
"{0} День добрий! Ласкаво просимо! {1}",
|
||||
"{0} Мої вітання! Ласкаво просимо! {1}",
|
||||
"{0} Здоровенькі були! Ласкаво просимо! {1}",
|
||||
"{0} Раді вітати вас! Ласкаво просимо! {1}",
|
||||
"{0} Доброго здоров’ячка! Ласкаво просимо! {1}"
|
||||
],
|
||||
"evening": [
|
||||
"{0} Добрий вечір! Ласкаво просимо! {1}",
|
||||
"{0} Доброго вечора! Ласкаво просимо! {1}",
|
||||
"{0} Добривечір! Ласкаво просимо! {1}",
|
||||
"{0} Доброго вечора та ласкаво просимо! {1}",
|
||||
"{0} Добрий вечір та ласкаво просимо! {1}"
|
||||
],
|
||||
"night": [
|
||||
"{0} Доброї ночі! Ласкаво просимо! {1}",
|
||||
"{0} Здоровенькі були! Ласкаво просимо! {1}"
|
||||
],
|
||||
"unknown": [
|
||||
"{0} Вітаннячко! Ласкаво просимо! {1}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tracking": {
|
||||
"dhl": {
|
||||
"statuses": {
|
||||
"delivered": "Доставлено",
|
||||
"transit": "Транзит",
|
||||
"pre-transit": "Пре-транзит",
|
||||
"failure": "Невдача"
|
||||
"AA": "Departed from the hub",
|
||||
"AE": "Pickup successful",
|
||||
"AN": "Pickup not successful",
|
||||
"BV": "Exception occurred",
|
||||
"DD": "Data service",
|
||||
"EE": "Arrived at the hub",
|
||||
"ES": "First processed by DHL",
|
||||
"GT": "Money transfer",
|
||||
"LA": "In storage",
|
||||
"NB": "Processing during transit",
|
||||
"PO": "In delivery",
|
||||
"VA": "Electronic pre-advise",
|
||||
"ZF": "Delivery",
|
||||
"ZN": "Delivery not successful",
|
||||
"ZO": "Customs clearance",
|
||||
"ZU": "Delivery successful"
|
||||
},
|
||||
"messages": {
|
||||
"DHL PAKET (parcel)": "DHL PAKET (посилка)",
|
||||
|
103
locale/en-US.json
Normal file
103
locale/en-US.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"messages": {
|
||||
"wallet": {
|
||||
"balance_own": "Your balance is `{balance}`.",
|
||||
"balance_user": "**{user}**'s balance is `{balance}`.",
|
||||
"transfer_success": "You have transferred `{amount}` to **{recipient}**.",
|
||||
"transfer_insufficient_funds": "Insufficient funds. `{amount}` more is needed for this transaction."
|
||||
},
|
||||
"welcome": {
|
||||
"morning": [
|
||||
"{0} Good morning and welcome! {1}"
|
||||
],
|
||||
"midday": [
|
||||
"{0} Good day and welcome! {1}"
|
||||
],
|
||||
"evening": [
|
||||
"{0} Good evening and welcome! {1}"
|
||||
],
|
||||
"night": [
|
||||
"{0} Good night and welcome! {1}"
|
||||
],
|
||||
"unknown": [
|
||||
"{0} Hello and welcome! {1}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"reload": {
|
||||
"description": "Reload bot's configuration"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"bite": {
|
||||
"name": "Bite",
|
||||
"text": "**{user_name}** bites **{target_name}**"
|
||||
},
|
||||
"hug": {
|
||||
"name": "Hug",
|
||||
"text": "**{user_name}** hugs **{target_name}**"
|
||||
},
|
||||
"kiss": {
|
||||
"name": "Kiss",
|
||||
"text": "**{user_name}** kisses **{target_name}**"
|
||||
},
|
||||
"lick": {
|
||||
"name": "Lick",
|
||||
"text": "**{user_name}** licks **{target_name}**"
|
||||
},
|
||||
"pat": {
|
||||
"name": "Pat",
|
||||
"text": "**{user_name}** pats **{target_name}**"
|
||||
},
|
||||
"poke": {
|
||||
"name": "Poke",
|
||||
"text": "**{user_name}** pokes **{target_name}**"
|
||||
},
|
||||
"wave": {
|
||||
"name": "Wave",
|
||||
"text": "**{user_name}** waves **{target_name}**"
|
||||
},
|
||||
"wink": {
|
||||
"name": "Wink",
|
||||
"text": "**{user_name}** winks **{target_name}**"
|
||||
}
|
||||
},
|
||||
"tracking": {
|
||||
"dhl": {
|
||||
"statuses": {
|
||||
"AA": "Departed from the hub",
|
||||
"AE": "Pickup successful",
|
||||
"AN": "Pickup not successful",
|
||||
"BV": "Exception occurred",
|
||||
"DD": "Data service",
|
||||
"EE": "Arrived at the hub",
|
||||
"ES": "First processed by DHL",
|
||||
"GT": "Money transfer",
|
||||
"LA": "In storage",
|
||||
"NB": "Processing during transit",
|
||||
"PO": "In delivery",
|
||||
"VA": "Electronic pre-advise",
|
||||
"ZF": "Delivery",
|
||||
"ZN": "Delivery not successful",
|
||||
"ZO": "Customs clearance",
|
||||
"ZU": "Delivery successful"
|
||||
},
|
||||
"messages": {
|
||||
"DHL PAKET (parcel)": "DHL PAKET (parcel)",
|
||||
"The shipment was prepared for onward transport.": "The shipment was prepared for onward transport.",
|
||||
"Warenpost (Merchandise Shipment)": "Warenpost (Merchandise Shipment)",
|
||||
"The shipment has been processed in the parcel center": "The shipment has been processed in the parcel center",
|
||||
"Unfortunately, the shipment could not be delivered today due to a strike action.": "Unfortunately, the shipment could not be delivered today due to a strike action.",
|
||||
"Die Sendung wurde von DHL abgeholt.": "The shipment has been picked up by DHL.",
|
||||
"The shipment has been successfully delivered": "The shipment has been successfully delivered",
|
||||
"The shipment could not be delivered, and the recipient has been notified": "The shipment could not be delivered, and the recipient has been notified",
|
||||
"The shipment has been loaded onto the delivery vehicle": "The shipment has been loaded onto the delivery vehicle",
|
||||
"The shipment arrived in the region of recipient and will be transported to the delivery base in the next step.": "The shipment arrived in the region of recipient and will be transported to the delivery base in the next step.",
|
||||
"The shipment has been processed in the parcel center of origin": "The shipment has been processed in the parcel center of origin",
|
||||
"The shipment has been posted by the sender at the retail outlet": "The shipment has been posted by the sender at the retail outlet",
|
||||
"The instruction data for this shipment have been provided by the sender to DHL electronically": "The instruction data for this shipment have been provided by the sender to DHL electronically"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -38,6 +38,11 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"reload": {
|
||||
"description": "Перезавантажити конфігурацію бота"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"bite": {
|
||||
"name": "Вкусити",
|
||||
@@ -75,10 +80,22 @@
|
||||
"tracking": {
|
||||
"dhl": {
|
||||
"statuses": {
|
||||
"delivered": "Доставлено",
|
||||
"transit": "Транзит",
|
||||
"pre-transit": "Пре-транзит",
|
||||
"failure": "Невдача"
|
||||
"AA": "Departed from the hub",
|
||||
"AE": "Pickup successful",
|
||||
"AN": "Pickup not successful",
|
||||
"BV": "Exception occurred",
|
||||
"DD": "Data service",
|
||||
"EE": "Arrived at the hub",
|
||||
"ES": "First processed by DHL",
|
||||
"GT": "Money transfer",
|
||||
"LA": "In storage",
|
||||
"NB": "Processing during transit",
|
||||
"PO": "In delivery",
|
||||
"VA": "Electronic pre-advise",
|
||||
"ZF": "Delivery",
|
||||
"ZN": "Delivery not successful",
|
||||
"ZO": "Customs clearance",
|
||||
"ZU": "Delivery successful"
|
||||
},
|
||||
"messages": {
|
||||
"DHL PAKET (parcel)": "DHL PAKET (посилка)",
|
||||
|
8
main.py
8
main.py
@@ -1,9 +1,11 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging.config
|
||||
from asyncio import get_event_loop
|
||||
from logging import Logger
|
||||
from os import getpid, makedirs
|
||||
from pathlib import Path
|
||||
from sys import exit
|
||||
|
||||
from discord import LoginFailure
|
||||
from libbot.utils import config_get
|
||||
@@ -13,7 +15,7 @@ from api.app import app # noqa
|
||||
from classes.pycord_bot import PycordBot
|
||||
from modules.extensions_loader import dynamic_import_from_src
|
||||
from modules.scheduler import scheduler
|
||||
from modules.utils import get_logging_config, get_logger
|
||||
from modules.utils import get_logger, get_logging_config
|
||||
|
||||
makedirs(Path("logs/"), exist_ok=True)
|
||||
|
||||
@@ -50,3 +52,7 @@ async def main():
|
||||
|
||||
|
||||
asyncio.create_task(main())
|
||||
|
||||
if __name__ == "__main__":
|
||||
event_loop = get_event_loop()
|
||||
event_loop.run_until_complete(main())
|
||||
|
@@ -29,6 +29,9 @@ db: AsyncDatabase = db_client.get_database(name=db_config["name"])
|
||||
col_users: AsyncCollection = db.get_collection("users")
|
||||
col_guilds: AsyncCollection = db.get_collection("guilds")
|
||||
col_wallets: AsyncCollection = db.get_collection("wallets")
|
||||
col_custom_channels: AsyncCollection = db.get_collection("custom_channels")
|
||||
|
||||
|
||||
# col_messages: AsyncCollection = db.get_collection("messages")
|
||||
# col_warnings: AsyncCollection = db.get_collection("warnings")
|
||||
# col_checkouts: AsyncCollection = db.get_collection("checkouts")
|
||||
@@ -44,3 +47,6 @@ async def _update_database_indexes() -> None:
|
||||
await col_wallets.create_index(
|
||||
["owner_id", "guild_id"], name="owner_id-guild_id", unique=True
|
||||
)
|
||||
await col_custom_channels.create_index(
|
||||
["owner_id", "guild_id", "channel_id"], name="owner_id-guild_id-channel_id", unique=True
|
||||
)
|
||||
|
@@ -1,16 +1,16 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from database import col_messages
|
||||
from discord import Embed
|
||||
from pymongo import DESCENDING
|
||||
from pytz import timezone
|
||||
from ujson import loads
|
||||
|
||||
from classes.enums import MessageEvents
|
||||
from classes.pycordbot import PycordBot
|
||||
from ujson import loads
|
||||
from modules.utils import hex_to_int
|
||||
from database import col_messages
|
||||
|
||||
from modules.weather.parser import parse_weather
|
||||
|
||||
# Example guild key
|
||||
@@ -71,9 +71,9 @@ async def report_weather(
|
||||
# Results must be parsed and added as embeds to the embeds lits.
|
||||
for location in locations:
|
||||
location_timezone_offset = ":".join(
|
||||
str(
|
||||
timezone(bot.config["bot"]["timezone"]).utcoffset(datetime.utcnow())
|
||||
).split(":")[:2]
|
||||
str(timezone(bot.config["bot"]["timezone"]).utcoffset(datetime.utcnow())).split(
|
||||
":"
|
||||
)[:2]
|
||||
)
|
||||
|
||||
api_response = await (
|
||||
|
Reference in New Issue
Block a user