Files
bancho.py/app/repositories/client_hashes.py
2025-04-04 21:30:31 +09:00

134 lines
4.1 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import TypedDict
from typing import cast
from sqlalchemy import CHAR
from sqlalchemy import Column
from sqlalchemy import DateTime
from sqlalchemy import Integer
from sqlalchemy import func
from sqlalchemy import or_
from sqlalchemy import select
from sqlalchemy.dialects.mysql import Insert as MysqlInsert
from sqlalchemy.dialects.mysql import insert as mysql_insert
from sqlalchemy.sql import ColumnElement
from sqlalchemy.types import Boolean
import app.state.services
from app.repositories import Base
from app.repositories.users import UsersTable
class ClientHashesTable(Base):
__tablename__ = "client_hashes"
userid = Column("userid", Integer, nullable=False, primary_key=True)
osupath = Column("osupath", CHAR(32), nullable=False, primary_key=True)
adapters = Column("adapters", CHAR(32), nullable=False, primary_key=True)
uninstall_id = Column("uninstall_id", CHAR(32), nullable=False, primary_key=True)
disk_serial = Column("disk_serial", CHAR(32), nullable=False, primary_key=True)
latest_time = Column("latest_time", DateTime, nullable=False)
occurrences = Column("occurrences", Integer, nullable=False, server_default="0")
READ_PARAMS = (
ClientHashesTable.userid,
ClientHashesTable.osupath,
ClientHashesTable.adapters,
ClientHashesTable.uninstall_id,
ClientHashesTable.disk_serial,
ClientHashesTable.latest_time,
ClientHashesTable.occurrences,
)
class ClientHash(TypedDict):
userid: int
osupath: str
adapters: str
uninstall_id: str
disk_serial: str
latest_time: datetime
occurrences: int
class ClientHashWithPlayer(ClientHash):
name: str
priv: int
async def create(
userid: int,
osupath: str,
adapters: str,
uninstall_id: str,
disk_serial: str,
) -> ClientHash:
"""Create a new client hash entry in the database."""
insert_stmt: MysqlInsert = (
mysql_insert(ClientHashesTable)
.values(
userid=userid,
osupath=osupath,
adapters=adapters,
uninstall_id=uninstall_id,
disk_serial=disk_serial,
latest_time=func.now(),
occurrences=1,
)
.on_duplicate_key_update(
latest_time=func.now(),
occurrences=ClientHashesTable.occurrences + 1,
)
)
await app.state.services.database.execute(insert_stmt)
select_stmt = (
select(*READ_PARAMS)
.where(ClientHashesTable.userid == userid)
.where(ClientHashesTable.osupath == osupath)
.where(ClientHashesTable.adapters == adapters)
.where(ClientHashesTable.uninstall_id == uninstall_id)
.where(ClientHashesTable.disk_serial == disk_serial)
)
client_hash = await app.state.services.database.fetch_one(select_stmt)
assert client_hash is not None
return cast(ClientHash, client_hash)
async def fetch_any_hardware_matches_for_user(
userid: int,
running_under_wine: bool,
adapters: str,
uninstall_id: str,
disk_serial: str | None = None,
) -> list[ClientHashWithPlayer]:
"""\
Fetch a list of matching hardware addresses where any of
`adapters`, `uninstall_id` or `disk_serial` match other users
from the database.
"""
select_stmt = (
select(*READ_PARAMS, UsersTable.name, UsersTable.priv)
.join(UsersTable, ClientHashesTable.userid == UsersTable.id)
.where(ClientHashesTable.userid != userid)
)
if running_under_wine:
select_stmt = select_stmt.where(ClientHashesTable.uninstall_id == uninstall_id)
else:
# make disk serial optional in the OR
oneof_filters: list[ColumnElement[Boolean]] = []
oneof_filters.append(ClientHashesTable.adapters == adapters)
oneof_filters.append(ClientHashesTable.uninstall_id == uninstall_id)
if disk_serial is not None:
oneof_filters.append(ClientHashesTable.disk_serial == disk_serial)
select_stmt = select_stmt.where(or_(*oneof_filters))
client_hashes = await app.state.services.database.fetch_all(select_stmt)
return cast(list[ClientHashWithPlayer], client_hashes)