mirror of
https://github.com/rangermix/TwitchDropsMiner.git
synced 2026-05-26 23:19:39 +00:00
151 lines
4.0 KiB
Python
151 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
import random
|
|
import string
|
|
import asyncio
|
|
import logging
|
|
from functools import wraps
|
|
from contextlib import suppress
|
|
from datetime import datetime, timezone
|
|
from collections import abc, OrderedDict
|
|
from typing import Any, Literal, Generic, TypeVar, cast, TYPE_CHECKING
|
|
|
|
from constants import JsonType
|
|
|
|
if TYPE_CHECKING:
|
|
from typing_extensions import ParamSpec
|
|
else:
|
|
# stub it
|
|
class ParamSpec:
|
|
def __init__(*args, **kwargs):
|
|
pass
|
|
|
|
|
|
_T = TypeVar("_T") # type
|
|
_D = TypeVar("_D") # default
|
|
_P = ParamSpec("_P") # params
|
|
logger = logging.getLogger("TwitchDrops")
|
|
NONCE_CHARS = string.ascii_letters + string.digits
|
|
|
|
|
|
def timestamp(string: str) -> datetime:
|
|
return datetime.strptime(string, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
|
|
|
|
|
|
def create_nonce(length: int = 30) -> str:
|
|
return ''.join(random.choices(NONCE_CHARS, k=length))
|
|
|
|
|
|
def deduplicate(iterable: abc.Iterable[_T]) -> list[_T]:
|
|
return list(OrderedDict.fromkeys(iterable).keys())
|
|
|
|
|
|
def task_wrapper(
|
|
afunc: abc.Callable[_P, abc.Coroutine[Any, Any, _T]]
|
|
) -> abc.Callable[_P, abc.Coroutine[Any, Any, _T]]:
|
|
@wraps(afunc)
|
|
async def wrapper(*args: _P.args, **kwargs: _P.kwargs):
|
|
try:
|
|
await afunc(*args, **kwargs)
|
|
except Exception:
|
|
logger.exception("Exception in task")
|
|
raise # raise up to the wrapping task
|
|
return wrapper
|
|
|
|
|
|
def invalidate_cache(instance, *attrnames):
|
|
"""
|
|
To be used to invalidate `functools.cached_property`.
|
|
"""
|
|
for name in attrnames:
|
|
with suppress(AttributeError):
|
|
delattr(instance, name)
|
|
|
|
|
|
class OrderedSet(abc.MutableSet[_T]):
|
|
"""
|
|
Implementation of a set that preserves insertion order,
|
|
based on OrderedDict with values set to None.
|
|
"""
|
|
def __init__(self, iterable: abc.Iterable[_T] = [], /):
|
|
self._items: OrderedDict[_T, None] = OrderedDict((item, None) for item in iterable)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self.__class__.__name__}([{', '.join(map(repr, self._items))}])"
|
|
|
|
def __contains__(self, item: object, /) -> bool:
|
|
return item in self._items
|
|
|
|
def __iter__(self) -> abc.Iterator[_T]:
|
|
return iter(self._items)
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._items)
|
|
|
|
def add(self, item: _T, /) -> None:
|
|
self._items[item] = None
|
|
|
|
def discard(self, item: _T, /) -> None:
|
|
with suppress(KeyError):
|
|
del self._items[item]
|
|
|
|
def update(self, *others: abc.Iterable[_T]) -> None:
|
|
for it in others:
|
|
for item in it:
|
|
if item not in self._items:
|
|
self._items[item] = None
|
|
|
|
def difference_update(self, *others: abc.Iterable[_T]) -> None:
|
|
for it in others:
|
|
for item in it:
|
|
if item in self._items:
|
|
del self._items[item]
|
|
|
|
|
|
class AwaitableValue(Generic[_T]):
|
|
def __init__(self):
|
|
self._value: _T
|
|
self._event = asyncio.Event()
|
|
|
|
def has_value(self) -> bool:
|
|
return self._event.is_set()
|
|
|
|
def wait(self) -> abc.Coroutine[Any, Any, Literal[True]]:
|
|
return cast(abc.Coroutine[Any, Any, Literal[True]], self._event.wait())
|
|
|
|
def get_with_default(self, default: _D) -> _T | _D:
|
|
if self._event.is_set():
|
|
return self._value
|
|
return default
|
|
|
|
async def get(self) -> _T:
|
|
await self._event.wait()
|
|
return self._value
|
|
|
|
def set(self, value: _T) -> None:
|
|
self._value = value
|
|
self._event.set()
|
|
|
|
def clear(self) -> None:
|
|
self._event.clear()
|
|
|
|
|
|
class Game:
|
|
def __init__(self, data: JsonType):
|
|
self.id: int = int(data["id"])
|
|
self.name: str = data["name"]
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def __repr__(self) -> str:
|
|
return f"Game({self.id}, {self.name})"
|
|
|
|
def __eq__(self, other: object):
|
|
if isinstance(other, self.__class__):
|
|
return self.id == other.id
|
|
return NotImplemented
|
|
|
|
def __hash__(self) -> int:
|
|
return hash((self.__class__.__name__, self.id))
|