Initial commit
This commit is contained in:
30
classes/callbacks.py
Normal file
30
classes/callbacks.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyrogram.types import CallbackQuery
|
||||
|
||||
|
||||
@dataclass
|
||||
class CallbackLanguage:
|
||||
__slots__ = ("language",)
|
||||
|
||||
language: str
|
||||
|
||||
@classmethod
|
||||
def from_callback(cls, callback: CallbackQuery):
|
||||
"""Parse callback query and extract language data from it.
|
||||
|
||||
### Args:
|
||||
* callback (`CallbackQuery`): Callback query got from user interaction.
|
||||
|
||||
### Raises:
|
||||
* `ValueError`: Raised when callback provided is not a language one.
|
||||
|
||||
### Returns:
|
||||
* `CallbackLanguage`: Parsed callback query.
|
||||
"""
|
||||
action, language = str(callback.data).split(":")
|
||||
|
||||
if action.lower() != "language":
|
||||
raise ValueError("Callback provided is not a language callback")
|
||||
|
||||
return cls(language)
|
1
classes/enums/__init__.py
Normal file
1
classes/enums/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .garbage_type import GarbageType
|
10
classes/enums/garbage_type.py
Normal file
10
classes/enums/garbage_type.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class GarbageType(Enum):
|
||||
BIO = 0
|
||||
PLASTIC = 1
|
||||
PAPER = 2
|
||||
GENERAL = 3
|
||||
GLASS = 4
|
||||
UNSPECIFIED = 5
|
73
classes/garbage_entry.py
Normal file
73
classes/garbage_entry.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Mapping, Union
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
from classes.enums.garbage_type import GarbageType
|
||||
from classes.location import Location
|
||||
|
||||
|
||||
@dataclass
|
||||
class GarbageEntry:
|
||||
__slots__ = (
|
||||
"_id",
|
||||
"locations",
|
||||
"garbage_type",
|
||||
"date",
|
||||
)
|
||||
|
||||
_id: Union[ObjectId, None]
|
||||
locations: List[Location]
|
||||
garbage_type: GarbageType
|
||||
date: datetime
|
||||
|
||||
@classmethod
|
||||
async def from_dict(cls, data: Mapping[str, Any]):
|
||||
"""Generate GarbageEntry object from the mapping provided
|
||||
|
||||
### Args:
|
||||
* data (`Mapping[str, Any]`): Entry
|
||||
|
||||
### Raises:
|
||||
* `KeyError`: Key is missing.
|
||||
* `TypeError`: Key of a wrong type provided.
|
||||
* `ValueError`: "date" is not a valid ISO string.
|
||||
|
||||
### Returns:
|
||||
* `GarbageEntry`: Valid GarbageEntry object.
|
||||
"""
|
||||
for key in ("locations", "garbage_type", "date"):
|
||||
if key not in data:
|
||||
raise KeyError
|
||||
if key == "locations" and not isinstance(data[key], list):
|
||||
raise TypeError
|
||||
if key == "garbage_type" and not isinstance(data[key], int):
|
||||
raise TypeError
|
||||
if key == "date":
|
||||
datetime.fromisoformat(str(data[key]))
|
||||
|
||||
locations = [
|
||||
await Location.get(location_id) for location_id in data["locations"]
|
||||
]
|
||||
garbage_type = GarbageType(data["garbage_type"])
|
||||
|
||||
return cls(
|
||||
None,
|
||||
locations,
|
||||
garbage_type,
|
||||
data["date"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def from_record(cls, data: Mapping[str, Any]):
|
||||
locations = [
|
||||
await Location.get(location_id) for location_id in data["locations"]
|
||||
]
|
||||
garbage_type = GarbageType(data["garbage_type"])
|
||||
return cls(
|
||||
data["_id"],
|
||||
locations,
|
||||
garbage_type,
|
||||
data["date"],
|
||||
)
|
20
classes/geobase/geobase.py
Normal file
20
classes/geobase/geobase.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
from classes.location import Location
|
||||
|
||||
|
||||
class GeoBase(ABC):
|
||||
@abstractmethod
|
||||
async def get_location(self) -> Location:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def find_location(self) -> List[Location]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def nearby_location(self) -> List[Location]:
|
||||
pass
|
23
classes/geobase/geobase_api.py
Normal file
23
classes/geobase/geobase_api.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import List
|
||||
|
||||
from aiohttp import ClientSession
|
||||
|
||||
from classes.geobase.geobase import GeoBase
|
||||
from classes.location import Location
|
||||
|
||||
# from urllib.parse import urlencode
|
||||
|
||||
|
||||
class GeoBaseAPI(GeoBase):
|
||||
async def get_location(self, session: ClientSession, location_id: int) -> Location:
|
||||
# query = {"geoNameId": location_id, "style": "MEDIUM"}
|
||||
# response = await session.get(f"http://api.geonames.org/get?{urlencode(query)}")
|
||||
pass
|
||||
|
||||
async def find_location(self, session: ClientSession, name: str) -> List[Location]:
|
||||
pass
|
||||
|
||||
async def nearby_location(
|
||||
self, session: ClientSession, lat: float, lon: float, radius: int
|
||||
) -> List[Location]:
|
||||
pass
|
58
classes/location.py
Normal file
58
classes/location.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
from classes.point import Point
|
||||
from modules.database import col_locations
|
||||
|
||||
|
||||
@dataclass
|
||||
class Location:
|
||||
__slots__ = (
|
||||
"_id",
|
||||
"id",
|
||||
"name",
|
||||
"location",
|
||||
"country",
|
||||
"timezone",
|
||||
)
|
||||
|
||||
_id: ObjectId
|
||||
id: int
|
||||
name: str
|
||||
location: Point
|
||||
country: int
|
||||
timezone: str
|
||||
|
||||
@classmethod
|
||||
async def get(cls, id: int):
|
||||
db_entry = await col_locations.find_one({"id": id})
|
||||
|
||||
if db_entry is None:
|
||||
raise ValueError(f"No location with ID {id} found.")
|
||||
|
||||
db_entry["location"] = Point(*db_entry["location"]) # type: ignore
|
||||
|
||||
return cls(**db_entry)
|
||||
|
||||
@classmethod
|
||||
async def find(cls, name: str):
|
||||
db_entry = await col_locations.find_one({"name": {"$regex": name}})
|
||||
|
||||
if db_entry is None:
|
||||
raise ValueError(f"No location with name {name} found.")
|
||||
|
||||
db_entry["location"] = Point(*db_entry["location"]) # type: ignore
|
||||
|
||||
return cls(**db_entry)
|
||||
|
||||
@classmethod
|
||||
async def nearby(cls, lat: float, lon: float):
|
||||
db_entry = await col_locations.find_one({"location": {"$near": [lon, lat]}})
|
||||
|
||||
if db_entry is None:
|
||||
raise ValueError(f"No location near {lat}, {lon} found.")
|
||||
|
||||
db_entry["location"] = Point(*db_entry["location"]) # type: ignore
|
||||
|
||||
return cls(**db_entry)
|
12
classes/point.py
Normal file
12
classes/point.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
__slots__ = (
|
||||
"lon",
|
||||
"lat",
|
||||
)
|
||||
|
||||
lon: float
|
||||
lat: float
|
68
classes/pyroclient.py
Normal file
68
classes/pyroclient.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from typing import List, Union
|
||||
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from libbot.pyrogram.classes import PyroClient as LibPyroClient
|
||||
from pymongo import ASCENDING, GEO2D
|
||||
from pyrogram.types import User
|
||||
|
||||
from classes.location import Location
|
||||
from classes.pyrouser import PyroUser
|
||||
from modules.database import col_locations
|
||||
from modules.reminder import remind
|
||||
|
||||
|
||||
class PyroClient(LibPyroClient):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if self.scheduler is not None:
|
||||
self.scheduler.add_job(
|
||||
remind, CronTrigger.from_crontab("* * * * *"), args=(self,)
|
||||
)
|
||||
|
||||
async def start(self, **kwargs):
|
||||
await col_locations.create_index(
|
||||
[("id", ASCENDING)], name="location_id", unique=True
|
||||
)
|
||||
await col_locations.create_index(
|
||||
[("location", GEO2D)],
|
||||
name="location_location",
|
||||
)
|
||||
return await super().start(**kwargs)
|
||||
|
||||
async def find_user(self, user: Union[int, User]) -> PyroUser:
|
||||
"""Find User by it's ID or User object.
|
||||
|
||||
### Args:
|
||||
* user (`Union[int, User]`): ID or User object to extract ID from.
|
||||
|
||||
### Returns:
|
||||
* `PyroUser`: User in database representation.
|
||||
"""
|
||||
|
||||
return (
|
||||
await PyroUser.find(user)
|
||||
if isinstance(user, int)
|
||||
else await PyroUser.find(user.id, locale=user.language_code)
|
||||
)
|
||||
|
||||
async def get_location(self, id: int) -> Location:
|
||||
"""Get Location by it's ID.
|
||||
|
||||
### Args:
|
||||
* id (`int`): Location's ID. Defaults to `None`.
|
||||
|
||||
### Returns:
|
||||
* `Location`: Location from database as an object.
|
||||
"""
|
||||
|
||||
return await Location.get(id)
|
||||
|
||||
async def list_locations(self) -> List[Location]:
|
||||
"""Get all locations stored in database.
|
||||
|
||||
### Returns:
|
||||
* `List[Location]`: List of `Location` objects.
|
||||
"""
|
||||
return [
|
||||
await Location.get(record["id"]) async for record in col_locations.find({})
|
||||
]
|
118
classes/pyrouser.py
Normal file
118
classes/pyrouser.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Union
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
from classes.location import Location
|
||||
from modules.database import col_users
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PyroUser:
|
||||
"""Dataclass of DB entry of a user"""
|
||||
|
||||
__slots__ = (
|
||||
"_id",
|
||||
"id",
|
||||
"locale",
|
||||
"enabled",
|
||||
"location",
|
||||
"offset",
|
||||
"time_hour",
|
||||
"time_minute",
|
||||
)
|
||||
|
||||
_id: ObjectId
|
||||
id: int
|
||||
locale: Union[str, None]
|
||||
enabled: bool
|
||||
location: Union[Location, None]
|
||||
offset: int
|
||||
time_hour: int
|
||||
time_minute: int
|
||||
|
||||
@classmethod
|
||||
async def find(
|
||||
cls,
|
||||
id: int,
|
||||
locale: Union[str, None] = None,
|
||||
enabled: bool = True,
|
||||
location_id: int = 0,
|
||||
offset: int = 1,
|
||||
time_hour: int = 18,
|
||||
time_minute: int = 0,
|
||||
):
|
||||
db_entry = await col_users.find_one({"id": id})
|
||||
|
||||
if db_entry is None:
|
||||
inserted = await col_users.insert_one(
|
||||
{
|
||||
"id": id,
|
||||
"locale": locale,
|
||||
"enabled": enabled,
|
||||
"location": location_id,
|
||||
"offset": offset,
|
||||
"time_hour": time_hour,
|
||||
"time_minute": time_minute,
|
||||
}
|
||||
)
|
||||
db_entry = await col_users.find_one({"_id": inserted.inserted_id})
|
||||
|
||||
if db_entry is None:
|
||||
raise RuntimeError("Could not find inserted user entry.")
|
||||
|
||||
try:
|
||||
db_entry["location"] = await Location.get(db_entry["location"]) # type: ignore
|
||||
except ValueError:
|
||||
db_entry["location"] = None # type: ignore
|
||||
|
||||
return cls(**db_entry)
|
||||
|
||||
async def update_locale(self, locale: Union[str, None]) -> None:
|
||||
"""Change user's locale stored in the database.
|
||||
|
||||
### Args:
|
||||
* locale (`Union[str, None]`): New locale to be set.
|
||||
"""
|
||||
logger.debug("%s's locale has been set to %s", self.id, locale)
|
||||
await col_users.update_one({"_id": self._id}, {"$set": {"locale": locale}})
|
||||
|
||||
async def update_state(self, enabled: bool = False) -> None:
|
||||
logger.debug("%s's state has been set to %s", self.id, enabled)
|
||||
await col_users.update_one({"_id": self._id}, {"$set": {"enabled": enabled}})
|
||||
|
||||
async def update_location(self, location_id: int = 0) -> None:
|
||||
logger.debug("%s's location has been set to %s", self.id, location_id)
|
||||
await col_users.update_one(
|
||||
{"_id": self._id}, {"$set": {"location": location_id}}
|
||||
)
|
||||
|
||||
async def update_offset(self, offset: int = 1) -> None:
|
||||
logger.debug("%s's offset has been set to %s", self.id, offset)
|
||||
await col_users.update_one({"_id": self._id}, {"$set": {"offset": offset}})
|
||||
|
||||
async def update_time(self, hour: int = 18, minute: int = 0) -> None:
|
||||
logger.debug("%s's time has been set to %s h. %s m.", self.id, hour, minute)
|
||||
await col_users.update_one(
|
||||
{"_id": self._id}, {"$set": {"time_hour": hour, "time_minute": minute}}
|
||||
)
|
||||
|
||||
async def delete(self) -> None:
|
||||
logger.debug("%s's data has been deleted", self.id)
|
||||
await col_users.delete_one({"_id": self._id})
|
||||
|
||||
async def checkout(self) -> Any:
|
||||
logger.debug("%s's data has been checked out", self.id)
|
||||
db_entry = await col_users.find_one({"_id": self._id})
|
||||
|
||||
if db_entry is None:
|
||||
raise KeyError(
|
||||
f"DB record with id {self._id} of user {self.id} is not found"
|
||||
)
|
||||
|
||||
del db_entry["_id"] # type: ignore
|
||||
|
||||
return db_entry
|
Reference in New Issue
Block a user