From 1cf6228b435861d6330c52d738f1b5da37bf75ec Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Fri, 3 Jul 2026 18:03:37 +0200 Subject: [PATCH 1/5] Drop broken billing playcount unique constraint --- ...rop_billing_playcount_unique_constraint.py | 24 +++++++++++++++++++ core/data/schema/arcade.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py diff --git a/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py b/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py new file mode 100644 index 0000000..fda1a6f --- /dev/null +++ b/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py @@ -0,0 +1,24 @@ +"""drop billing playcount unique constraint + +Revision ID: 63f1fc2e334c +Revises: ada3e2d02483 +Create Date: 2026-07-03 18:01:52.160242 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '63f1fc2e334c' +down_revision = 'ada3e2d02483' +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_constraint('machine', 'machine_billing_playcount', type_='unique') + + +def downgrade(): + op.create_unique_constraint('machine', 'machine_billing_playcount', ['machine']) diff --git a/core/data/schema/arcade.py b/core/data/schema/arcade.py index d587f71..f10f4cc 100644 --- a/core/data/schema/arcade.py +++ b/core/data/schema/arcade.py @@ -142,7 +142,7 @@ billing_playct: 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("year", INTEGER, nullable=False), From 24f854e61d6e17ec92cc21713d8e17ee6ffca56b Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Thu, 9 Jul 2026 17:35:26 +0200 Subject: [PATCH 2/5] allnet: improve billing playlimit to include history --- core/allnet.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/core/allnet.py b/core/allnet.py index 821c5de..276f440 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -711,6 +711,7 @@ class BillingServlet: "messages": [] } playhist = "000000/0:000000/0:000000/0" + total_playcount = req.playcnt if machine is not None: if self.config.allnet.save_billing: @@ -722,16 +723,21 @@ class BillingServlet: # 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) + 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) - 1, -1, -1): playhist += f"{plays[x]['year']:04d}{plays[x]['month']:02d}/{plays[x]['playct']}:" - playhist = playhist[:-1] + if plays: + total_playcount = sum(p['playct'] for p in plays) + + playhist = ":".join( + f"{p['year']:04d}{p['month']:02d}/{p['playct']}" + for p in reversed(plays) + ) for x in range(1, len(req_dict)): if not req_dict[x]: @@ -814,10 +820,10 @@ class BillingServlet: return PlainTextResponse("result=6&waittime=0&linelimit=20\r\n") playlimit = req.playlimit - while req.playcnt > playlimit: + while total_playcount > playlimit: playlimit += 1024 - nearfull = req.nearfull + (req.billingtype.value * 0x00010000) + nearfull = req.nearfull | (req.billingtype.value << 16) digest.update(playlimit.to_bytes(4, "little") + kc_serial_bytes) playlimit_sig = signer.sign(digest).hex() From db442ed8fa70a9ef00453ad7f26eb24d86ae3a07 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Thu, 9 Jul 2026 21:12:42 +0200 Subject: [PATCH 3/5] update alembic revision --- .../63f1fc2e334c_drop_billing_playcount_unique_constraint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py b/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py index fda1a6f..cba9f02 100644 --- a/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py +++ b/core/data/alembic/versions/63f1fc2e334c_drop_billing_playcount_unique_constraint.py @@ -1,7 +1,7 @@ """drop billing playcount unique constraint Revision ID: 63f1fc2e334c -Revises: ada3e2d02483 +Revises: 1b78db5898f4 Create Date: 2026-07-03 18:01:52.160242 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '63f1fc2e334c' -down_revision = 'ada3e2d02483' +down_revision = '1b78db5898f4' branch_labels = None depends_on = None From 297abf4e545e8c0950169700515318ea5928d72b Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Fri, 10 Jul 2026 21:01:32 +0200 Subject: [PATCH 4/5] change campaign table definition from TEXT to String --- .../versions/1b78db5898f4_add_campaigns.py | 71 +++++++++++-------- core/data/schema/base.py | 4 +- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/core/data/alembic/versions/1b78db5898f4_add_campaigns.py b/core/data/alembic/versions/1b78db5898f4_add_campaigns.py index 847cd68..1435384 100644 --- a/core/data/alembic/versions/1b78db5898f4_add_campaigns.py +++ b/core/data/alembic/versions/1b78db5898f4_add_campaigns.py @@ -18,39 +18,48 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('campaign', - sa.Column('id', sa.INTEGER(), nullable=False), - sa.Column('name', sa.TEXT(length=127), nullable=False), - sa.Column('announce_date', sa.TIMESTAMP(), nullable=True), - sa.Column('start_date', sa.TIMESTAMP(), nullable=True), - sa.Column('end_date', sa.TIMESTAMP(), nullable=True), - sa.Column('distrib_start_date', sa.TIMESTAMP(), nullable=True), - sa.Column('distrib_end_date', sa.TIMESTAMP(), nullable=True), - sa.Column('info0', sa.INTEGER(), nullable=True), - sa.Column('info1', sa.INTEGER(), nullable=True), - sa.Column('info2', sa.INTEGER(), nullable=True), - sa.Column('info3', sa.INTEGER(), nullable=True), - sa.PrimaryKeyConstraint('id'), - mysql_charset='utf8mb4' + op.create_table( + 'campaign', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('name', sa.String(127), nullable=False), + sa.Column('announce_date', sa.TIMESTAMP(), nullable=True), + sa.Column('start_date', sa.TIMESTAMP(), nullable=True), + sa.Column('end_date', sa.TIMESTAMP(), nullable=True), + sa.Column('distrib_start_date', sa.TIMESTAMP(), nullable=True), + sa.Column('distrib_end_date', sa.TIMESTAMP(), nullable=True), + sa.Column('info0', sa.INTEGER(), nullable=True), + sa.Column('info1', sa.INTEGER(), nullable=True), + sa.Column('info2', sa.INTEGER(), nullable=True), + sa.Column('info3', sa.INTEGER(), nullable=True), + sa.PrimaryKeyConstraint('id'), + mysql_charset='utf8mb4', ) - op.create_table('campaign_game', - sa.Column('campaign_id', sa.INTEGER(), nullable=False), - sa.Column('game_id', sa.TEXT(length=5), nullable=False), - sa.ForeignKeyConstraint(['campaign_id'], ['campaign.id'], onupdate='cascade', ondelete='cascade'), - sa.UniqueConstraint('campaign_id', 'game_id', name='campaign_game_uk'), - mysql_charset='utf8mb4' + op.create_table( + 'campaign_game', + sa.Column('campaign_id', sa.INTEGER(), nullable=False), + sa.Column('game_id', sa.String(5), nullable=False), + sa.ForeignKeyConstraint( + ['campaign_id'], ['campaign.id'], onupdate='cascade', ondelete='cascade' + ), + sa.UniqueConstraint('campaign_id', 'game_id', name='campaign_game_uk'), + mysql_charset='utf8mb4', ) - op.create_table('campaign_progress', - sa.Column('id', sa.BIGINT(), autoincrement=True, nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('campaign_id', sa.INTEGER(), nullable=False), - sa.Column('is_participating', sa.INTEGER(), server_default='0', nullable=False), - sa.Column('progress', sa.INTEGER(), server_default='0', nullable=False), - sa.ForeignKeyConstraint(['campaign_id'], ['campaign.id'], onupdate='cascade', ondelete='cascade'), - sa.ForeignKeyConstraint(['user_id'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('campaign_id', 'user_id', name='campaign_progress_uk'), - mysql_charset='utf8mb4' + op.create_table( + 'campaign_progress', + sa.Column('id', sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('campaign_id', sa.INTEGER(), nullable=False), + sa.Column('is_participating', sa.INTEGER(), server_default='0', nullable=False), + sa.Column('progress', sa.INTEGER(), server_default='0', nullable=False), + sa.ForeignKeyConstraint( + ['campaign_id'], ['campaign.id'], onupdate='cascade', ondelete='cascade' + ), + sa.ForeignKeyConstraint( + ['user_id'], ['aime_user.id'], onupdate='cascade', ondelete='cascade' + ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('campaign_id', 'user_id', name='campaign_progress_uk'), + mysql_charset='utf8mb4', ) # ### end Alembic commands ### diff --git a/core/data/schema/base.py b/core/data/schema/base.py index 7934cd0..d8be455 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -43,7 +43,7 @@ campaign: Table = Table( "campaign", metadata, Column("id", INTEGER, primary_key=True, nullable=False), - Column("name", TEXT(127), nullable=False), + Column("name", String(127), nullable=False), Column("announce_date", TIMESTAMP), # None = start announcing now Column("start_date", TIMESTAMP), # None = start now Column("end_date", TIMESTAMP), # None = never end @@ -62,7 +62,7 @@ campaign_game: Table = Table( "campaign_game", metadata, Column("campaign_id", INTEGER, ForeignKey("campaign.id", ondelete="cascade", onupdate="cascade"), nullable=False), - Column("game_id", TEXT(5), nullable=False), + Column("game_id", String(5), nullable=False), UniqueConstraint("campaign_id", "game_id", name="campaign_game_uk"), mysql_charset="utf8mb4", ) From c90f5115de343139fdd4fd69f03171f746fa8ad6 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Fri, 10 Jul 2026 21:43:05 +0200 Subject: [PATCH 5/5] billing: improve playlimit calculation --- core/allnet.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/allnet.py b/core/allnet.py index 276f440..25581fd 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -711,7 +711,6 @@ class BillingServlet: "messages": [] } playhist = "000000/0:000000/0:000000/0" - total_playcount = req.playcnt if machine is not None: if self.config.allnet.save_billing: @@ -732,8 +731,6 @@ class BillingServlet: plays = await self.data.arcade.billing_get_playcount_3mo(machine['id'], req.gameid) if plays: - total_playcount = sum(p['playct'] for p in plays) - playhist = ":".join( f"{p['year']:04d}{p['month']:02d}/{p['playct']}" for p in reversed(plays) @@ -820,7 +817,7 @@ class BillingServlet: return PlainTextResponse("result=6&waittime=0&linelimit=20\r\n") playlimit = req.playlimit - while total_playcount > playlimit: + while req.playcnt >= playlimit - req.nearfull: playlimit += 1024 nearfull = req.nearfull | (req.billingtype.value << 16)