This commit is contained in:
2025-05-25 22:32:36 +02:00
parent 62ee26b20f
commit d5dc438601
25 changed files with 691 additions and 72 deletions

View File

@@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse
from api.app import app from api.app import app
from classes import PycordGuild from classes import PycordGuild
from classes.errors import WalletNotFoundError, GuildNotFoundError from classes.errors import GuildNotFoundError, WalletNotFoundError
from classes.wallet import Wallet from classes.wallet import Wallet
logger: Logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)

View File

@@ -1,5 +1,6 @@
from .guild_rules import GuildRules
from .pycord_guild import PycordGuild from .pycord_guild import PycordGuild
from .pycord_guild_colors import PycordGuildColors
from .pycord_user import PycordUser from .pycord_user import PycordUser
# from .pycord_guild_colors import PycordGuildColors
# from .wallet import Wallet # from .wallet import Wallet

View File

@@ -1,15 +1,108 @@
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any, Dict, Optional
from bson import ObjectId from bson import ObjectId
from libbot.cache.classes import Cache
from classes.base import BaseCacheable
from modules.database import col_custom_channels
@dataclass @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 _id: ObjectId
channel_id: int
owner_id: int owner_id: int
guild_id: int
channel_id: int
allow_comments: bool allow_comments: bool
allow_reactions: bool allow_reactions: bool
created: datetime created: datetime
deleted: datetime | None 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]

View File

@@ -1 +1,2 @@
from .message_events import MessageEvents from .message_events import MessageEvents
from .punishment import Punishment

View File

@@ -0,0 +1,8 @@
from enum import Enum
class Punishment(Enum):
WARNING = 0
MUTE = 1
KICK = 2
BAN = 3

View File

@@ -2,6 +2,6 @@ class GuildNotFoundError(Exception):
"""PycordGuild could not find guild with such an ID in the database""" """PycordGuild could not find guild with such an ID in the database"""
def __init__(self, guild_id: int) -> None: 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") super().__init__(f"Guild with id {self.guild_id} was not found")

39
classes/guild_rules.py Normal file
View 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()

View 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]

View 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]

View File

@@ -1,16 +1,19 @@
import logging import logging
from datetime import datetime from datetime import datetime
from logging import Logger from logging import Logger
from typing import Any, Literal from pathlib import Path
from typing import Any, Dict, Literal
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from aiohttp import ClientSession 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.classes import CacheMemcached, CacheRedis
from libbot.cache.manager import create_cache_client from libbot.cache.manager import create_cache_client
from libbot.i18n import BotLocale
from libbot.pycord.classes import PycordBot as LibPycordBot 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 from modules.database import _update_database_indexes
logger: Logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)
@@ -56,6 +59,60 @@ class PycordBot(LibPycordBot):
key, *args, locale=None if locale is None else locale.split("-")[0] 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: async def find_user(self, user: int | User) -> PycordUser:
"""Find User by its ID or User object. """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) 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: async def start(self, *args: Any, **kwargs: Any) -> None:
await self._schedule_tasks() await self._schedule_tasks()
await _update_database_indexes() await _update_database_indexes()

View File

@@ -1,36 +1,74 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Any, Optional from typing import Any, Dict, Optional
from bson import ObjectId from bson import ObjectId
from libbot.cache.classes import Cache 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 @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: ObjectId
id: int id: int
locale: str
def __init__(self) -> None: rules: GuildRules
raise NotImplementedError()
@classmethod @classmethod
async def from_id( async def from_id(
cls, guild_id: int, allow_creation: bool = True, cache: Optional[Cache] = None cls, guild_id: int, allow_creation: bool = True, cache: Optional[Cache] = None
) -> "PycordGuild": ) -> "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: Args:
guild_id (int): User's Discord ID guild_id (int): ID of the guild to look up.
allow_creation (:obj:`bool`, optional): Create new guild record if none found in the database allow_creation (:obj:`bool`, optional): Create a new record if none found in the database.
cache (:obj:`Cache`, optional): Cache engine to get the cache from cache (:obj:`Cache`, optional): Cache engine that will be used to fetch and update the cache.
Returns: Returns:
PycordGuild: User object PycordGuild: Object of the found or newly created guild.
Raises: 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]: def to_dict(self, json_compatible: bool = False) -> Dict[str, Any]:
"""Convert PycordGuild object to a JSON representation. """Convert PycordGuild object to a JSON representation.
@@ -44,4 +82,64 @@ class PycordGuild:
return { return {
"_id": self._id if not json_compatible else str(self._id), "_id": self._id if not json_compatible else str(self._id),
"id": 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)

View File

@@ -10,6 +10,7 @@ from pymongo.results import InsertOneResult
from classes.base import BaseCacheable from classes.base import BaseCacheable
from classes.errors.pycord_user import UserNotFoundError from classes.errors.pycord_user import UserNotFoundError
from classes.wallet import Wallet from classes.wallet import Wallet
from modules.database import col_users
from modules.utils import restore_from_cache from modules.utils import restore_from_cache
logger: Logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)
@@ -20,6 +21,8 @@ class PycordUser(BaseCacheable):
"""Dataclass of DB entry of a user""" """Dataclass of DB entry of a user"""
__slots__ = ("_id", "id", "guild_id") __slots__ = ("_id", "id", "guild_id")
__short_name__ = "user"
__collection__ = col_users
_id: ObjectId _id: ObjectId
id: int id: int

View File

@@ -9,9 +9,9 @@ from pymongo.results import InsertOneResult
from classes.errors.wallet import ( from classes.errors.wallet import (
WalletBalanceLimitExceeded, WalletBalanceLimitExceeded,
WalletInsufficientFunds,
WalletNotFoundError, WalletNotFoundError,
WalletOverdraftLimitExceeded, WalletOverdraftLimitExceeded,
WalletInsufficientFunds,
) )
from modules.database import col_wallets from modules.database import col_wallets

29
cogs/cog_admin.py Normal file
View 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))

View 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
View 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
View 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))

View File

@@ -1,7 +1,7 @@
import logging import logging
from logging import Logger from logging import Logger
from discord import ApplicationContext, SlashCommandGroup, option, User from discord import ApplicationContext, SlashCommandGroup, User, option
from discord.ext import commands from discord.ext import commands
from classes.errors import WalletInsufficientFunds from classes.errors import WalletInsufficientFunds

View File

@@ -1,5 +1,5 @@
{ {
"locale": "en", "locale": "en-US",
"debug": false, "debug": false,
"bot": { "bot": {
"owners": [ "owners": [

View File

@@ -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": { "actions": {
"bite": { "bite": {
"name": "Вкусити", "name": "Вкусити",
@@ -33,46 +57,25 @@
"text": "**{user_name}** підморгує **{target_name}**" "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": { "tracking": {
"dhl": { "dhl": {
"statuses": { "statuses": {
"delivered": "Доставлено", "AA": "Departed from the hub",
"transit": "Транзит", "AE": "Pickup successful",
"pre-transit": "Пре-транзит", "AN": "Pickup not successful",
"failure": "Невдача" "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": { "messages": {
"DHL PAKET (parcel)": "DHL PAKET (посилка)", "DHL PAKET (parcel)": "DHL PAKET (посилка)",

103
locale/en-US.json Normal file
View 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"
}
}
}
}

View File

@@ -38,6 +38,11 @@
] ]
} }
}, },
"commands": {
"reload": {
"description": "Перезавантажити конфігурацію бота"
}
},
"actions": { "actions": {
"bite": { "bite": {
"name": "Вкусити", "name": "Вкусити",
@@ -75,10 +80,22 @@
"tracking": { "tracking": {
"dhl": { "dhl": {
"statuses": { "statuses": {
"delivered": "Доставлено", "AA": "Departed from the hub",
"transit": "Транзит", "AE": "Pickup successful",
"pre-transit": "Пре-транзит", "AN": "Pickup not successful",
"failure": "Невдача" "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": { "messages": {
"DHL PAKET (parcel)": "DHL PAKET (посилка)", "DHL PAKET (parcel)": "DHL PAKET (посилка)",

View File

@@ -1,9 +1,11 @@
import asyncio import asyncio
import contextlib import contextlib
import logging.config import logging.config
from asyncio import get_event_loop
from logging import Logger from logging import Logger
from os import getpid, makedirs from os import getpid, makedirs
from pathlib import Path from pathlib import Path
from sys import exit
from discord import LoginFailure from discord import LoginFailure
from libbot.utils import config_get from libbot.utils import config_get
@@ -13,7 +15,7 @@ from api.app import app # noqa
from classes.pycord_bot import PycordBot from classes.pycord_bot import PycordBot
from modules.extensions_loader import dynamic_import_from_src from modules.extensions_loader import dynamic_import_from_src
from modules.scheduler import scheduler 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) makedirs(Path("logs/"), exist_ok=True)
@@ -50,3 +52,7 @@ async def main():
asyncio.create_task(main()) asyncio.create_task(main())
if __name__ == "__main__":
event_loop = get_event_loop()
event_loop.run_until_complete(main())

View File

@@ -29,6 +29,9 @@ db: AsyncDatabase = db_client.get_database(name=db_config["name"])
col_users: AsyncCollection = db.get_collection("users") col_users: AsyncCollection = db.get_collection("users")
col_guilds: AsyncCollection = db.get_collection("guilds") col_guilds: AsyncCollection = db.get_collection("guilds")
col_wallets: AsyncCollection = db.get_collection("wallets") col_wallets: AsyncCollection = db.get_collection("wallets")
col_custom_channels: AsyncCollection = db.get_collection("custom_channels")
# col_messages: AsyncCollection = db.get_collection("messages") # col_messages: AsyncCollection = db.get_collection("messages")
# col_warnings: AsyncCollection = db.get_collection("warnings") # col_warnings: AsyncCollection = db.get_collection("warnings")
# col_checkouts: AsyncCollection = db.get_collection("checkouts") # col_checkouts: AsyncCollection = db.get_collection("checkouts")
@@ -44,3 +47,6 @@ async def _update_database_indexes() -> None:
await col_wallets.create_index( await col_wallets.create_index(
["owner_id", "guild_id"], name="owner_id-guild_id", unique=True ["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
)

View File

@@ -1,16 +1,16 @@
from datetime import datetime
import logging import logging
from datetime import datetime
from typing import Any, Dict, List from typing import Any, Dict, List
from database import col_messages
from discord import Embed from discord import Embed
from pymongo import DESCENDING from pymongo import DESCENDING
from pytz import timezone from pytz import timezone
from ujson import loads
from classes.enums import MessageEvents from classes.enums import MessageEvents
from classes.pycordbot import PycordBot from classes.pycordbot import PycordBot
from ujson import loads
from modules.utils import hex_to_int from modules.utils import hex_to_int
from database import col_messages
from modules.weather.parser import parse_weather from modules.weather.parser import parse_weather
# Example guild key # Example guild key
@@ -71,9 +71,9 @@ async def report_weather(
# Results must be parsed and added as embeds to the embeds lits. # Results must be parsed and added as embeds to the embeds lits.
for location in locations: for location in locations:
location_timezone_offset = ":".join( location_timezone_offset = ":".join(
str( str(timezone(bot.config["bot"]["timezone"]).utcoffset(datetime.utcnow())).split(
timezone(bot.config["bot"]["timezone"]).utcoffset(datetime.utcnow()) ":"
).split(":")[:2] )[:2]
) )
api_response = await ( api_response = await (