forked from Hay1tsme/artemis
allnet: basic playhistory
This commit is contained in:
@ -593,7 +593,6 @@ class BillingServlet:
|
||||
return PlainTextResponse("result=5&linelimit=&message=field is missing or formatting is incorrect\r\n")
|
||||
|
||||
kc_serial_bytes = req.keychipid.encode()
|
||||
|
||||
|
||||
machine = await self.data.arcade.get_machine(req.keychipid)
|
||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||
@ -614,6 +613,32 @@ class BillingServlet:
|
||||
}
|
||||
|
||||
if machine is not None:
|
||||
if self.config.allnet.save_billing:
|
||||
lastcredit = await self.data.arcade.billing_get_credit(machine['id'], req.gameid)
|
||||
if lastcredit is not None:
|
||||
last_playct = lastcredit['playcount']
|
||||
else:
|
||||
last_playct = 0
|
||||
|
||||
# Technically if a cab resets it's playcount and then does more plays then the previous
|
||||
# playcount before a billing checkin occours, we will lose plays equal to the current playcount.
|
||||
if req.playcnt < last_playct: await self.data.arcade.billing_add_playcount(machine['id'], req.gameid, req.playcnt)
|
||||
elif req.playcnt == last_playct: pass # No plays since last checkin, skip update
|
||||
else: await self.data.arcade.billing_add_playcount(machine['id'], req.gameid, req.playcnt - last_playct)
|
||||
|
||||
plays = await self.data.arcade.billing_get_playcount_3mo(machine['id'], req.gameid)
|
||||
if plays is not None and len(plays) > 0:
|
||||
playhist = ""
|
||||
|
||||
for x in range(len(plays), 0, -1): playhist += f"{plays[x]['year']:04d}{plays[x]['month']:02d}/{plays[x]['playct']}:"
|
||||
playhist = playhist[:-1]
|
||||
|
||||
else:
|
||||
playhist = "000000/0:000000/0:000000/0"
|
||||
|
||||
else:
|
||||
playhist = "000000/0:000000/0:000000/0"
|
||||
|
||||
for x in range(1, len(req_dict)):
|
||||
if not req_dict[x]:
|
||||
continue
|
||||
@ -649,6 +674,7 @@ class BillingServlet:
|
||||
if self.config.allnet.save_billing:
|
||||
await self.data.arcade.billing_set_credit(
|
||||
machine['id'],
|
||||
req.gameid,
|
||||
tmp.chute_type.value,
|
||||
tmp.service_type.value,
|
||||
tmp.operation_type.value,
|
||||
@ -708,9 +734,7 @@ class BillingServlet:
|
||||
digest.update(nearfull.to_bytes(4, "little") + kc_serial_bytes)
|
||||
nearfull_sig = signer.sign(digest).hex()
|
||||
|
||||
# TODO: playhistory
|
||||
|
||||
resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig, req.requestno, req.protocolver)
|
||||
resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig, req.requestno, req.protocolver, playhist)
|
||||
|
||||
resp_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\r\n"
|
||||
|
||||
|
@ -0,0 +1,50 @@
|
||||
"""add_billing_playcount
|
||||
|
||||
Revision ID: f6007bbf057d
|
||||
Revises: 27e3434740df
|
||||
Create Date: 2025-04-19 18:20:35.554137
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f6007bbf057d'
|
||||
down_revision = '27e3434740df'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('machine_billing_playcount',
|
||||
sa.Column('id', sa.BIGINT(), nullable=False),
|
||||
sa.Column('machine', sa.Integer(), nullable=False),
|
||||
sa.Column('game_id', sa.CHAR(length=5), nullable=False),
|
||||
sa.Column('year', sa.INTEGER(), nullable=False),
|
||||
sa.Column('month', sa.INTEGER(), nullable=False),
|
||||
sa.Column('playct', sa.BIGINT(), server_default='1', nullable=False),
|
||||
sa.ForeignKeyConstraint(['machine'], ['machine.id'], onupdate='cascade', ondelete='cascade'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('machine'),
|
||||
sa.UniqueConstraint('machine', 'game_id', 'year', 'month', name='machine_billing_playcount_uk'),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
op.add_column('machine_billing_credit', sa.Column('game_id', sa.CHAR(length=5), nullable=False))
|
||||
op.drop_constraint("machine_billing_credit_ibfk_1", "machine_billing_credit", "foreignkey")
|
||||
op.drop_index('machine', table_name='machine_billing_credit')
|
||||
op.create_unique_constraint('machine_billing_credit_uk', 'machine_billing_credit', ['machine', 'game_id'])
|
||||
op.create_foreign_key("machine_billing_credit_ibfk_1", "machine_billing_credit", "machine", ["machine"], ["id"], onupdate='cascade', ondelete='cascade')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint("machine_billing_credit_ibfk_1", "machine_billing_credit", "foreignkey")
|
||||
op.drop_constraint('machine_billing_credit_uk', 'machine_billing_credit', type_='unique')
|
||||
op.create_index('machine', 'machine_billing_credit', ['machine'], unique=True)
|
||||
op.create_foreign_key("machine_billing_credit_ibfk_1", "machine_billing_credit", "machine", ["machine"], ["id"], onupdate='cascade', ondelete='cascade')
|
||||
op.drop_column('machine_billing_credit', 'game_id')
|
||||
op.drop_table('machine_billing_playcount')
|
||||
# ### end Alembic commands ###
|
@ -1,7 +1,8 @@
|
||||
import re
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, Table, and_, or_
|
||||
from sqlalchemy import Column, Table, and_, or_, UniqueConstraint
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.sql import func, select
|
||||
@ -98,8 +99,9 @@ billing_credit: Table = Table(
|
||||
"machine",
|
||||
Integer,
|
||||
ForeignKey("machine.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False, unique=True
|
||||
nullable=False
|
||||
),
|
||||
Column("game_id", CHAR(5), nullable=False),
|
||||
Column("chute_type", INTEGER, nullable=False),
|
||||
Column("service_type", INTEGER, nullable=False),
|
||||
Column("operation_type", INTEGER, nullable=False),
|
||||
@ -115,6 +117,25 @@ billing_credit: Table = Table(
|
||||
Column("coin_count_slot5", INTEGER, nullable=False),
|
||||
Column("coin_count_slot6", INTEGER, nullable=False),
|
||||
Column("coin_count_slot7", INTEGER, nullable=False),
|
||||
UniqueConstraint("machine", "game_id", name="machine_billing_credit_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
billing_playct: Table = Table(
|
||||
"machine_billing_playcount",
|
||||
metadata,
|
||||
Column("id", BIGINT, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"machine",
|
||||
Integer,
|
||||
ForeignKey("machine.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False, unique=True
|
||||
),
|
||||
Column("game_id", CHAR(5), nullable=False),
|
||||
Column("year", INTEGER, nullable=False),
|
||||
Column("month", INTEGER, nullable=False),
|
||||
Column("playct", BIGINT, nullable=False, server_default="1"),
|
||||
UniqueConstraint("machine", "game_id", "year", "month", name="machine_billing_playcount_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
@ -413,12 +434,13 @@ class ArcadeData(BaseData):
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
async def billing_set_credit(self, machine_id: int, chute_type: int, service_type: int, op_mode: int, coin_rate0: int, coin_rate1: int,
|
||||
async def billing_set_credit(self, machine_id: int, game_id: str, chute_type: int, service_type: int, op_mode: int, coin_rate0: int, coin_rate1: int,
|
||||
bonus_adder: int, coin_to_credit_rate: int, coin_count_slot0: int, coin_count_slot1: int, coin_count_slot2: int, coin_count_slot3: int,
|
||||
coin_count_slot4: int, coin_count_slot5: int, coin_count_slot6: int, coin_count_slot7: int) -> Optional[int]:
|
||||
|
||||
sql = insert(billing_credit).values(
|
||||
machine=machine_id,
|
||||
game_id=game_id,
|
||||
chute_type=chute_type,
|
||||
service_type=service_type,
|
||||
operation_type=op_mode,
|
||||
@ -460,6 +482,36 @@ class ArcadeData(BaseData):
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
async def billing_get_credit(self, machine_id: int, game_id: str) -> Optional[Row]:
|
||||
result = await self.execute(billing_credit.select(billing_credit.c.machine == machine_id))
|
||||
if result:
|
||||
return result.fetchone()
|
||||
|
||||
async def billing_add_playcount(self, machine_id: int, game_id: str, playct: int = 1) -> None:
|
||||
now = datetime.now()
|
||||
sql = insert(billing_playct).values(
|
||||
machine=machine_id,
|
||||
game_id=game_id,
|
||||
year=now.year,
|
||||
month=now.month,
|
||||
playct=playct
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(playct=billing_playct.c.playct + playct)
|
||||
result = await self.execute(conflict)
|
||||
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to add playcount for machine {machine_id} running {game_id}")
|
||||
|
||||
async def billing_get_playcount_3mo(self, machine_id: int, game_id: str) -> Optional[List[Row]]:
|
||||
result = await self.execute(billing_playct.select(and_(
|
||||
billing_playct.c.machine == machine_id,
|
||||
billing_playct.c.game_id == game_id
|
||||
)).order_by(billing_playct.c.year.desc(), billing_playct.c.month.desc()).limit(3))
|
||||
|
||||
if result is not None:
|
||||
return result.fetchall()
|
||||
|
||||
def format_serial(
|
||||
self, platform_code: str, platform_rev: int, serial_letter: str, serial_num: int, append: int, dash: bool = False
|
||||
) -> str:
|
||||
|
Reference in New Issue
Block a user