11import asyncio
22import logging
33import os
4+ import psycopg
5+ import random
46import shutil
57import uvicorn
68
79from core import Plugin , DEFAULT_TAG
8- from datetime import datetime
10+ from datetime import datetime , timedelta , timezone
911from fastapi import FastAPI , APIRouter , Form
1012from psycopg .rows import dict_row
1113from services .bot import DCSServerBot
1517app : Optional [FastAPI ] = None
1618
1719
20+ # Bit field constants
21+ BIT_USER_LINKED = 1
22+ BIT_LINK_IN_PROGRESS = 2
23+ BIT_FORCE_OPERATION = 4
24+
25+
1826class RestAPI (Plugin ):
1927
2028 def __init__ (self , bot : DCSServerBot ):
@@ -30,6 +38,7 @@ def __init__(self, bot: DCSServerBot):
3038 self .router .add_api_route (prefix + "/getuser" , self .getuser , methods = ["POST" ])
3139 self .router .add_api_route (prefix + "/missilepk" , self .missilepk , methods = ["POST" ])
3240 self .router .add_api_route (prefix + "/stats" , self .stats , methods = ["POST" ])
41+ self .router .add_api_route (prefix + "/linkme" , self .linkme , methods = ["POST" ])
3342 self .app = app
3443 self .config = Config (app = self .app , host = cfg ['listen' ], port = cfg ['port' ], log_level = logging .ERROR ,
3544 use_colors = False )
@@ -182,6 +191,74 @@ async def stats(self, nick: str = Form(default=None), date: str = Form(default=N
182191 data ['kdrByModule' ] = await cursor .fetchall ()
183192 return data
184193
194+ async def linkme (self ,
195+ discord_id : str = Form (..., description = "Discord user ID (snowflake)" , example = "123456789012345678" ),
196+ force : bool = Form (False , description = "Force the operation" , example = True )):
197+
198+ async def create_token () -> str :
199+ while True :
200+ try :
201+ token = str (random .randint (1000 , 9999 ))
202+ cursor .execute ("""
203+ INSERT INTO players (ucid, discord_id, last_seen)
204+ VALUES (%s, %s, NOW() AT TIME ZONE 'utc')
205+ """ , (token , discord_id ))
206+ return token
207+ except psycopg .errors .UniqueViolation :
208+ pass
209+
210+ self .log .debug (f'Calling /link with discord_id="{ discord_id } ", force="{ force } "' )
211+ async with self .apool .connection () as conn :
212+ async with conn .cursor (row_factory = dict_row ) as cursor :
213+
214+ # Check if discord_id exists
215+ await cursor .execute ("SELECT ucid, last_seen FROM players WHERE discord_id = %s" , (discord_id ,))
216+ result = await cursor .fetchone ()
217+
218+ rc = 0
219+ token = None
220+ now = datetime .now (tz = timezone .utc )
221+
222+ if result :
223+ ucid , last_seen = result
224+
225+ if len (ucid ) == 4 : # UCID is stored as a 4-character string
226+ # Linking already in progress
227+ token = ucid
228+ rc |= BIT_LINK_IN_PROGRESS
229+ if force :
230+ rc |= BIT_FORCE_OPERATION
231+ cursor .execute ("""
232+ UPDATE players
233+ SET last_seen = (NOW() AT TIME ZONE 'utc')
234+ WHERE discord_id = %s
235+ """ , (discord_id , ))
236+ expiry_timestamp = (now + timedelta (hours = 48 )).isoformat ()
237+ else :
238+ expiry_timestamp = (last_seen + timedelta (hours = 48 )).isoformat ()
239+ else :
240+ # User already linked
241+ rc |= BIT_USER_LINKED
242+ if force :
243+ rc |= BIT_FORCE_OPERATION
244+ token = await create_token ()
245+ expiry_timestamp = (now + timedelta (hours = 48 )).isoformat ()
246+ else :
247+ expiry_timestamp = None
248+ else :
249+ token = await create_token ()
250+ expiry_timestamp = (datetime .now () + timedelta (hours = 48 )).isoformat ()
251+ # Set bit_field for new user
252+ rc = 0 # Default bit_field for new user
253+ if force :
254+ rc |= BIT_FORCE_OPERATION # Set force operation flag
255+
256+ return {
257+ "token" : token ,
258+ "timestamp" : expiry_timestamp ,
259+ "rc" : rc
260+ }
261+
185262
186263async def setup (bot : DCSServerBot ):
187264 global app
0 commit comments