gs_database.py.bak 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. """DWC Network Server Emulator
  2. Copyright (C) 2014 polaris-
  3. Copyright (C) 2014 ToadKing
  4. Copyright (C) 2014 AdmiralCurtiss
  5. Copyright (C) 2015 Sepalani
  6. This program is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU Affero General Public License as
  8. published by the Free Software Foundation, either version 3 of the
  9. License, or (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU Affero General Public License for more details.
  14. You should have received a copy of the GNU Affero General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. import sqlite3
  18. import hashlib
  19. import itertools
  20. import json
  21. import time
  22. import logging
  23. from contextlib import closing
  24. import other.utils as utils
  25. import gamespy.gs_utility as gs_utils
  26. # Logger settings
  27. SQL_LOGLEVEL = logging.DEBUG
  28. logger_output_to_console = True
  29. logger_output_to_file = True
  30. logger_name = "GamespyDatabase"
  31. logger_filename = "gamespy_database.log"
  32. logger = utils.create_logger(logger_name, logger_filename, -1,
  33. logger_output_to_console, logger_output_to_file)
  34. class Transaction(object):
  35. def __init__(self, connection):
  36. self.conn = connection
  37. self.databaseAltered = False
  38. def __enter__(self):
  39. return self
  40. def __exit__(self, type, value, traceback):
  41. if self.databaseAltered:
  42. self.conn.commit()
  43. return
  44. def _executeAndMeasure(self, cursor, statement, parameters):
  45. logTransactionId = utils.generate_random_str(8)
  46. logger.log(SQL_LOGLEVEL, "[%s] STARTING: " % logTransactionId +
  47. statement.replace('?', '%s') % parameters)
  48. timeStart = time.time()
  49. clockStart = time.clock()
  50. cursor.execute(statement, parameters)
  51. clockEnd = time.clock()
  52. timeEnd = time.time()
  53. timeDiff = timeEnd - timeStart
  54. logger.log(SQL_LOGLEVEL,
  55. "[%s] DONE: Took %s real time / %s processor time",
  56. logTransactionId, timeDiff, clockEnd - clockStart)
  57. if timeDiff > 1.0:
  58. logger.log(logging.WARNING,
  59. "[%s] WARNING: SQL Statement took %s seconds!",
  60. logTransactionId, timeDiff)
  61. logger.log(logging.WARNING,
  62. "[%s] " % logTransactionId +
  63. statement.replace('?', '%s') % parameters)
  64. return
  65. def queryall(self, statement, parameters=()):
  66. with closing(self.conn.cursor()) as cursor:
  67. self._executeAndMeasure(cursor, statement, parameters)
  68. rows = cursor.fetchall()
  69. return rows
  70. return []
  71. def queryone(self, statement, parameters=()):
  72. with closing(self.conn.cursor()) as cursor:
  73. self._executeAndMeasure(cursor, statement, parameters)
  74. row = cursor.fetchone()
  75. return row
  76. return []
  77. def nonquery(self, statement, parameters=()):
  78. with closing(self.conn.cursor()) as cursor:
  79. self._executeAndMeasure(cursor, statement, parameters)
  80. self.databaseAltered = True
  81. return
  82. class GamespyDatabase(object):
  83. def __init__(self, filename='gpcm.db'):
  84. self.conn = sqlite3.connect(filename, timeout=10.0)
  85. self.conn.row_factory = sqlite3.Row
  86. # self.initialize_database()
  87. def __del__(self):
  88. self.close()
  89. def close(self):
  90. if self.conn is not None:
  91. self.conn.close()
  92. self.conn = None
  93. def initialize_database(self):
  94. with Transaction(self.conn) as tx:
  95. # I highly doubt having everything in a database be of the type
  96. # TEXT is a good practice, but I'm not good with databases and
  97. # I'm not 100% positive that, for instance, that all user id's
  98. # will be ints, or all passwords will be ints, etc, despite not
  99. # seeing any evidence yet to say otherwise as far as Nintendo
  100. # DS games go.
  101. tx.nonquery("CREATE TABLE IF NOT EXISTS users"
  102. " (profileid INT, userid TEXT, password TEXT,"
  103. " gsbrcd TEXT, email TEXT, uniquenick TEXT,"
  104. " pid TEXT, lon TEXT, lat TEXT, loc TEXT,"
  105. " firstname TEXT, lastname TEXT, stat TEXT,"
  106. " partnerid TEXT, console INT, csnum TEXT,"
  107. " cfc TEXT, bssid TEXT, devname BLOB, birth TEXT,"
  108. " gameid TEXT, enabled INT, zipcode TEXT, aim TEXT)")
  109. tx.nonquery("CREATE TABLE IF NOT EXISTS sessions"
  110. " (session TEXT, profileid INT, loginticket TEXT)")
  111. tx.nonquery("CREATE TABLE IF NOT EXISTS buddies"
  112. " (userProfileId INT, buddyProfileId INT, time INT,"
  113. " status INT, notified INT, gameid TEXT,"
  114. " blocked INT)")
  115. tx.nonquery("CREATE TABLE IF NOT EXISTS pending_messages"
  116. " (sourceid INT, targetid INT, msg TEXT)")
  117. tx.nonquery("CREATE TABLE IF NOT EXISTS gamestat_profile"
  118. " (profileid INT, dindex TEXT, ptype TEXT,"
  119. " data TEXT)")
  120. tx.nonquery("CREATE TABLE IF NOT EXISTS gameinfo"
  121. " (profileid INT, dindex TEXT, ptype TEXT,"
  122. " data TEXT)")
  123. tx.nonquery("CREATE TABLE IF NOT EXISTS nas_logins"
  124. " (userid TEXT, authtoken TEXT, data TEXT)")
  125. tx.nonquery("CREATE TABLE IF NOT EXISTS banned"
  126. " (gameid TEXT, ipaddr TEXT)")
  127. tx.nonquery("CREATE TABLE IF NOT EXISTS pending (macadr TEXT)")
  128. tx.nonquery("CREATE TABLE IF NOT EXISTS registered (macadr TEXT)")
  129. # Create some indexes for performance.
  130. tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS"
  131. " gamestatprofile_triple"
  132. " ON gamestat_profile(profileid,dindex,ptype)")
  133. tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS"
  134. " users_profileid_idx ON users (profileid)")
  135. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  136. " users_userid_idx ON users (userid)")
  137. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  138. " pending_messages_targetid_idx"
  139. " ON pending_messages (targetid)")
  140. tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS"
  141. " sessions_session_idx ON sessions (session)")
  142. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  143. " sessions_loginticket_idx ON sessions (loginticket)")
  144. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  145. " sessions_profileid_idx ON sessions (profileid)")
  146. tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS"
  147. " nas_logins_authtoken_idx ON nas_logins (authtoken)")
  148. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  149. " nas_logins_userid_idx ON nas_logins (userid)")
  150. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  151. " buddies_userProfileId_idx"
  152. " ON buddies (userProfileId)")
  153. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  154. " buddies_buddyProfileId_idx"
  155. " ON buddies (buddyProfileId)")
  156. tx.nonquery("CREATE INDEX IF NOT EXISTS"
  157. " gamestat_profile_profileid_idx"
  158. " ON gamestat_profile (profileid)")
  159. def get_dict(self, row):
  160. if not row:
  161. return None
  162. return dict(itertools.izip(row.keys(), row))
  163. # User functions
  164. def get_next_free_profileid(self):
  165. """TODO: Make profile ids start at 1 for each game?
  166. TODO: This leads to a race condition if two users try to create
  167. accounts at the same time. Instead, it's better to create a new row
  168. and return the sqlite ROWID instead.
  169. """
  170. with Transaction(self.conn) as tx:
  171. row = tx.queryone("SELECT max(profileid) AS m FROM users")
  172. r = self.get_dict(row)
  173. profileid = 1 # Cannot be 0 or else it freezes the game.
  174. if r is not None and r['m'] is not None:
  175. profileid = int(r['m']) + 1
  176. return profileid
  177. def check_user_exists(self, userid, gsbrcd):
  178. with Transaction(self.conn) as tx:
  179. row = tx.queryone(
  180. "SELECT COUNT(*) FROM users WHERE userid = ? AND gsbrcd = ?",
  181. (userid, gsbrcd)
  182. )
  183. count = int(row[0])
  184. return count > 0
  185. def check_user_enabled(self, userid, gsbrcd):
  186. with Transaction(self.conn) as tx:
  187. row = tx.queryone(
  188. "SELECT enabled FROM users WHERE userid = ? AND gsbrcd = ?",
  189. (userid, gsbrcd)
  190. )
  191. enabled = int(row[0])
  192. return enabled > 0
  193. def check_profile_exists(self, profileid):
  194. with Transaction(self.conn) as tx:
  195. row = tx.queryone(
  196. "SELECT COUNT(*) FROM users WHERE profileid = ?",
  197. (profileid,)
  198. )
  199. count = int(row[0])
  200. return count > 0
  201. def get_profile_from_profileid(self, profileid):
  202. profile = {}
  203. if profileid:
  204. with Transaction(self.conn) as tx:
  205. row = tx.queryone(
  206. "SELECT * FROM users WHERE profileid = ?",
  207. (profileid,)
  208. )
  209. profile = self.get_dict(row)
  210. return profile
  211. def perform_login(self, userid, password, gsbrcd):
  212. with Transaction(self.conn) as tx:
  213. row = tx.queryone(
  214. "SELECT * FROM users WHERE userid = ? and gsbrcd = ?",
  215. (userid, gsbrcd)
  216. )
  217. r = self.get_dict(row)
  218. profileid = None # Default, user doesn't exist
  219. if r is not None:
  220. # md5 = hashlib.md5()
  221. # md5.update(password)
  222. # if r['password'] == md5.hexdigest():
  223. # profileid = r['profileid'] # Valid password
  224. if r['enabled'] == 1 and r['gsbrcd'] == gsbrcd:
  225. profileid = r['profileid'] # Valid password
  226. return profileid
  227. def create_user(self, userid, password, email, uniquenick, gsbrcd,
  228. console, csnum, cfc, bssid, devname, birth, gameid,
  229. macadr):
  230. if not self.check_user_exists(userid, gsbrcd):
  231. profileid = self.get_next_free_profileid()
  232. # Always 11??? Is this important? Not to be confused with dwc_pid.
  233. # The three games I found it in (Tetris DS, Advance Wars - Days of
  234. # Ruin, and Animal Crossing: Wild World) all use \pid\11.
  235. pid = "11"
  236. lon = "0.000000" # Always 0.000000?
  237. lat = "0.000000" # Always 0.000000?
  238. loc = ""
  239. firstname = ""
  240. lastname = ""
  241. stat = ""
  242. partnerid = ""
  243. enabled = 1
  244. zipcode = ""
  245. aim = ""
  246. # Hash password before entering it into the database.
  247. # For now I'm using a very simple MD5 hash.
  248. # TODO: Replace with something stronger later, although it's
  249. # overkill for the NDS.
  250. md5 = hashlib.md5()
  251. md5.update(password)
  252. password = md5.hexdigest()
  253. with Transaction(self.conn) as tx:
  254. q = "INSERT INTO users VALUES" \
  255. " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
  256. tx.nonquery(q, (profileid, str(userid), password, gsbrcd,
  257. email, uniquenick, pid, lon, lat, loc,
  258. firstname, lastname, stat, partnerid,
  259. console, csnum, cfc, bssid, devname, birth,
  260. gameid, enabled, zipcode, aim))
  261. return profileid
  262. return None
  263. def import_user(self, profileid, uniquenick, firstname, lastname, email,
  264. gsbrcd, gameid, console):
  265. if not self.check_profile_exists(profileid):
  266. pid = "11"
  267. lon = "0.000000"
  268. lat = "0.000000"
  269. loc = ""
  270. stat = ""
  271. partnerid = ""
  272. password = ""
  273. userid = ""
  274. csnum = ""
  275. cfc = ""
  276. bssid = ""
  277. devname = ""
  278. birth = ""
  279. zipcode = ""
  280. aim = ""
  281. enabled = 1
  282. with Transaction(self.conn) as tx:
  283. q = "INSERT INTO users VALUES" \
  284. " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
  285. tx.nonquery(q, (profileid, str(userid), password, gsbrcd,
  286. email, uniquenick, pid, lon, lat, loc,
  287. firstname, lastname, stat, partnerid,
  288. console, csnum, cfc, bssid, devname, birth,
  289. gameid, enabled, zipcode, aim))
  290. return profileid
  291. def get_user_list(self):
  292. with Transaction(self.conn) as tx:
  293. rows = tx.queryall("SELECT * FROM users")
  294. return [self.get_dict(row) for row in rows]
  295. def save_pending_message(self, sourceid, targetid, msg):
  296. with Transaction(self.conn) as tx:
  297. tx.nonquery("INSERT INTO pending_messages VALUES (?,?,?)",
  298. (sourceid, targetid, msg))
  299. def get_pending_messages(self, profileid):
  300. with Transaction(self.conn) as tx:
  301. rows = tx.queryall(
  302. "SELECT * FROM pending_messages WHERE targetid = ?",
  303. (profileid,)
  304. )
  305. return [self.get_dict(row) for row in rows]
  306. def update_profile(self, profileid, field):
  307. """Found profile id associated with session key.
  308. Start replacing each field one by one.
  309. TODO: Optimize this so it's done all in one update.
  310. TODO: Check if other values than firstname/lastname are set using this
  311. """
  312. if field[0] in ["firstname", "lastname"]:
  313. with Transaction(self.conn) as tx:
  314. q = "UPDATE users SET \"%s\" = ? WHERE profileid = ?"
  315. tx.nonquery(q % field[0], (field[1], profileid))
  316. # Session functions
  317. # TODO: Cache session keys so we don't have to query the database every
  318. # time we get a profile id.
  319. def get_profileid_from_session_key(self, session_key):
  320. with Transaction(self.conn) as tx:
  321. row = tx.queryone(
  322. "SELECT profileid FROM sessions WHERE session = ?",
  323. (session_key,)
  324. )
  325. r = self.get_dict(row)
  326. profileid = -1 # Default, invalid session key
  327. if r is not None:
  328. profileid = r['profileid']
  329. return profileid
  330. def get_profileid_from_loginticket(self, loginticket):
  331. with Transaction(self.conn) as tx:
  332. row = tx.queryone(
  333. "SELECT profileid FROM sessions WHERE loginticket = ?",
  334. (loginticket,)
  335. )
  336. profileid = -1
  337. if row:
  338. profileid = int(row[0])
  339. return profileid
  340. def get_profile_from_session_key(self, session_key):
  341. profileid = self.get_profileid_from_session_key(session_key)
  342. profile = {}
  343. if profileid:
  344. with Transaction(self.conn) as tx:
  345. row = tx.queryone(
  346. "SELECT profileid FROM sessions WHERE session = ?",
  347. (session_key,)
  348. )
  349. profile = self.get_dict(row)
  350. return profile
  351. def generate_session_key(self, min_size):
  352. """Generate session key.
  353. TODO: There's probably a better way to do this.
  354. The point is preventing duplicate session keys.
  355. """
  356. while True:
  357. with Transaction(self.conn) as tx:
  358. session_key = utils.generate_random_number_str(min_size)
  359. row = tx.queryone(
  360. "SELECT COUNT(*) FROM sessions WHERE session = ?",
  361. (session_key,)
  362. )
  363. count = int(row[0])
  364. if not count:
  365. return session_key
  366. def delete_session(self, profileid):
  367. with Transaction(self.conn) as tx:
  368. tx.nonquery(
  369. "DELETE FROM sessions WHERE profileid = ?",
  370. (profileid,)
  371. )
  372. def create_session(self, profileid, loginticket):
  373. if profileid is not None and not self.check_profile_exists(profileid):
  374. return None
  375. # Remove any old sessions associated with this user id
  376. self.delete_session(profileid)
  377. # Create new session
  378. session_key = self.generate_session_key(8)
  379. with Transaction(self.conn) as tx:
  380. tx.nonquery(
  381. "INSERT INTO sessions VALUES (?, ?, ?)",
  382. (session_key, profileid, loginticket)
  383. )
  384. return session_key
  385. def get_session_list(self, profileid=None):
  386. with Transaction(self.conn) as tx:
  387. if profileid is not None:
  388. r = tx.queryall(
  389. "SELECT * FROM sessions WHERE profileid = ?",
  390. (profileid,)
  391. )
  392. else:
  393. r = tx.queryall("SELECT * FROM sessions")
  394. return [self.get_dict(row) for row in r]
  395. # nas server functions
  396. def get_nas_login(self, authtoken):
  397. with Transaction(self.conn) as tx:
  398. row = tx.queryone(
  399. "SELECT data FROM nas_logins WHERE authtoken = ?",
  400. (authtoken,)
  401. )
  402. r = self.get_dict(row)
  403. if r is None:
  404. return None
  405. else:
  406. return json.loads(r["data"])
  407. def get_nas_login_from_userid(self, userid):
  408. with Transaction(self.conn) as tx:
  409. row = tx.queryone(
  410. "SELECT data FROM nas_logins WHERE userid = ?",
  411. (userid,)
  412. )
  413. r = self.get_dict(row)
  414. if r is None:
  415. return None
  416. else:
  417. return json.loads(r["data"])
  418. def is_banned(self, postdata):
  419. with Transaction(self.conn) as tx:
  420. row = tx.queryone(
  421. "SELECT COUNT(*) FROM banned WHERE gameid = ? AND ipaddr = ?",
  422. (postdata['gamecd'][:-1], postdata['ipaddr'])
  423. )
  424. return int(row[0]) > 0
  425. def pending(self, postdata):
  426. with Transaction(self.conn) as tx:
  427. row = tx.queryone(
  428. "SELECT COUNT(*) FROM pending WHERE macadr = ?",
  429. (postdata['macadr'],)
  430. )
  431. return int(row[0]) > 0
  432. def registered(self, postdata):
  433. with Transaction(self.conn) as tx:
  434. row = tx.queryone(
  435. "SELECT COUNT(*) FROM registered WHERE macadr = ?",
  436. (postdata['macadr'],)
  437. )
  438. return int(row[0]) > 0
  439. def get_next_available_userid(self):
  440. with Transaction(self.conn) as tx:
  441. row = tx.queryone("SELECT max(userid) AS maxuser FROM users")
  442. r = self.get_dict(row)
  443. if r is None or r['maxuser'] is None:
  444. # Because all zeroes means Dolphin. Don't wanna get confused
  445. # during debugging later.
  446. return '0000000000002'
  447. else:
  448. userid = str(int(r['maxuser']) + 1)
  449. while len(userid) < 13:
  450. userid = "0" + userid
  451. return userid
  452. def generate_authtoken(self, userid, data):
  453. """Generate authentication token.
  454. Since the auth token passed back to the game will be random, we can
  455. make it small enough that there should never be a crash due to the
  456. size of the token.
  457. ^ real authtoken is 80 + 3 bytes though and I want to figure out
  458. what's causing the 52200 so I'm matching everything as closely as
  459. possible to the real thing.
  460. """
  461. size = 80
  462. # TODO: Another one of those questionable dupe-preventations
  463. while True:
  464. with Transaction(self.conn) as tx:
  465. authtoken = "NDS" + utils.generate_random_str(size)
  466. row = tx.queryone(
  467. "SELECT COUNT(*) FROM nas_logins WHERE authtoken = ?",
  468. (authtoken,)
  469. )
  470. count = int(row[0])
  471. if not count:
  472. break
  473. with Transaction(self.conn) as tx:
  474. row = tx.queryone(
  475. "SELECT * FROM nas_logins WHERE userid = ?",
  476. (userid,)
  477. )
  478. r = self.get_dict(row)
  479. if "devname" in data:
  480. data["devname"] = gs_utils.base64_encode(data["devname"])
  481. if "ingamesn" in data:
  482. data["ingamesn"] = gs_utils.base64_encode(data["ingamesn"])
  483. data = json.dumps(data)
  484. with Transaction(self.conn) as tx:
  485. if r is None: # no row, add it
  486. tx.nonquery(
  487. "INSERT INTO nas_logins VALUES (?, ?, ?)",
  488. (userid, authtoken, data)
  489. )
  490. else:
  491. tx.nonquery(
  492. "UPDATE nas_logins SET authtoken = ?, data = ?"
  493. " WHERE userid = ?",
  494. (authtoken, data, userid)
  495. )
  496. return authtoken
  497. # Buddy functions
  498. def add_buddy(self, userProfileId, buddyProfileId):
  499. now = int(time.time())
  500. # status == 0 -> not authorized
  501. with Transaction(self.conn) as tx:
  502. tx.nonquery(
  503. "INSERT INTO buddies VALUES (?, ?, ?, ?, ?, ?, ?)",
  504. (userProfileId, buddyProfileId, now, 0, 0, "", 0)
  505. )
  506. def auth_buddy(self, userProfileId, buddyProfileId):
  507. # status == 1 -> authorized
  508. with Transaction(self.conn) as tx:
  509. tx.nonquery(
  510. "UPDATE buddies SET status = ?"
  511. " WHERE userProfileId = ? AND buddyProfileId = ?",
  512. (1, userProfileId, buddyProfileId)
  513. )
  514. def block_buddy(self, userProfileId, buddyProfileId):
  515. with Transaction(self.conn) as tx:
  516. tx.nonquery(
  517. "UPDATE buddies SET blocked = ?"
  518. " WHERE userProfileId = ? AND buddyProfileId = ?",
  519. (1, userProfileId, buddyProfileId)
  520. )
  521. def unblock_buddy(self, userProfileId, buddyProfileId):
  522. with Transaction(self.conn) as tx:
  523. tx.nonquery(
  524. "UPDATE buddies SET blocked = ?"
  525. " WHERE userProfileId = ? AND buddyProfileId = ?",
  526. (0, userProfileId, buddyProfileId)
  527. )
  528. def get_buddy(self, userProfileId, buddyProfileId):
  529. if userProfileId and buddyProfileId:
  530. with Transaction(self.conn) as tx:
  531. row = tx.queryone(
  532. "SELECT * FROM buddies"
  533. " WHERE userProfileId = ? AND buddyProfileId = ?",
  534. (userProfileId, buddyProfileId)
  535. )
  536. return self.get_dict(row)
  537. return {}
  538. def delete_buddy(self, userProfileId, buddyProfileId):
  539. with Transaction(self.conn) as tx:
  540. tx.nonquery(
  541. "DELETE FROM buddies"
  542. " WHERE userProfileId = ? AND buddyProfileId = ?",
  543. (userProfileId, buddyProfileId)
  544. )
  545. def get_buddy_list(self, userProfileId):
  546. with Transaction(self.conn) as tx:
  547. rows = tx.queryall(
  548. "SELECT * FROM buddies"
  549. " WHERE userProfileId = ? AND blocked = 0",
  550. (userProfileId,)
  551. )
  552. return [self.get_dict(row) for row in rows]
  553. def get_blocked_list(self, userProfileId):
  554. with Transaction(self.conn) as tx:
  555. rows = tx.queryall(
  556. "SELECT * FROM buddies"
  557. " WHERE userProfileId = ? AND blocked = 1",
  558. (userProfileId,)
  559. )
  560. return [self.get_dict(row) for row in rows]
  561. def get_pending_buddy_requests(self, userProfileId):
  562. with Transaction(self.conn) as tx:
  563. rows = tx.queryall(
  564. "SELECT * FROM buddies"
  565. " WHERE buddyProfileId = ? AND status = 0",
  566. (userProfileId,)
  567. )
  568. return [self.get_dict(row) for row in rows]
  569. def buddy_need_auth_message(self, userProfileId):
  570. with Transaction(self.conn) as tx:
  571. rows = tx.queryall(
  572. "SELECT * FROM buddies"
  573. " WHERE buddyProfileId = ? AND status = 1 AND notified = 0",
  574. (userProfileId,)
  575. )
  576. return [self.get_dict(row) for row in rows]
  577. def buddy_sent_auth_message(self, userProfileId, buddyProfileId):
  578. with Transaction(self.conn) as tx:
  579. tx.nonquery(
  580. "UPDATE buddies SET notified = ?"
  581. " WHERE userProfileId = ? AND buddyProfileId = ?",
  582. (1, userProfileId, buddyProfileId)
  583. )
  584. # Gamestats-related functions
  585. def pd_insert(self, profileid, dindex, ptype, data):
  586. with Transaction(self.conn) as tx:
  587. row = tx.queryone(
  588. "SELECT COUNT(*) FROM gamestat_profile"
  589. " WHERE profileid = ? AND dindex = ? AND ptype = ?",
  590. (profileid, dindex, ptype)
  591. )
  592. count = int(row[0])
  593. if count > 0:
  594. tx.nonquery(
  595. "UPDATE gamestat_profile SET data = ?"
  596. " WHERE profileid = ? AND dindex = ? AND ptype = ?",
  597. (data, profileid, dindex, ptype)
  598. )
  599. else:
  600. tx.nonquery(
  601. "INSERT INTO gamestat_profile"
  602. " (profileid, dindex, ptype, data) VALUES(?,?,?,?)",
  603. (profileid, dindex, ptype, data)
  604. )
  605. def pd_get(self, profileid, dindex, ptype):
  606. with Transaction(self.conn) as tx:
  607. row = tx.queryone(
  608. "SELECT * FROM gamestat_profile"
  609. " WHERE profileid = ? AND dindex = ? AND ptype = ?",
  610. (profileid, dindex, ptype)
  611. )
  612. return self.get_dict(row)