@@ -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__)
|
||||||
|
@@ -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
|
||||||
|
@@ -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]
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
from .message_events import MessageEvents
|
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"""
|
"""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
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
|
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()
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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
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
|
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
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"locale": "en",
|
"locale": "en-US",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"bot": {
|
"bot": {
|
||||||
"owners": [
|
"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": {
|
"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
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": {
|
"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 (посилка)",
|
||||||
|
8
main.py
8
main.py
@@ -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())
|
||||||
|
@@ -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
|
||||||
|
)
|
||||||
|
@@ -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 (
|
||||||
|
Reference in New Issue
Block a user