Skip to content

Commit 3fb2536

Browse files
committed
games.py: Add top games to game search autocomplete, optimize search
1 parent ed7fce7 commit 3fb2536

File tree

1 file changed

+45
-24
lines changed

1 file changed

+45
-24
lines changed

modules/games.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,9 @@ def __init__(self, bot):
6666
# Ensure indices exist
6767
self.db.create_index([("deku_id", pymongo.ASCENDING)], unique=True)
6868

69-
# Generate the pipeline
70-
self.pipeline = [
71-
{'$project': {'deku_id': 1, 'name': 1, 'release_date': 1}}, # Filter to only stuff we want
72-
]
73-
self.aggregatePipeline = list(self.db.aggregate(self.pipeline))
69+
self.gameNamesCache = None
70+
self.topGames = None
71+
self.recalculate_cache()
7472

7573
if AUTO_SYNC:
7674
self.sync_db.start()
@@ -131,27 +129,27 @@ async def sync_db(self) -> Tuple[int, str]:
131129
logging.info(f'[Games] Finished syncing {count} games')
132130

133131
self.last_sync = {'at': sync_time, 'running': False}
134-
self.aggregatePipeline = list(self.db.aggregate(self.pipeline))
135-
132+
self.recalculate_cache()
136133
return count
137134

138-
def search(self, query: str) -> Optional[dict]:
139-
match = {'deku_id': None, 'score': 0, 'name': None}
140-
for game in self.aggregatePipeline:
141-
# If the game we are comparing is really short (<=3 chars), do not allow a match if our search is longer.
142-
# This prevents things like 'a' being the best match for 'realMyst' and not 'realMyst: Masterpiece Edition'
143-
if len(game['name']) <= 5 and len(query) > 5:
144-
continue
135+
def recalculate_cache(self):
136+
self.gameNamesCache = list(self.db.aggregate([{'$project': {'deku_id': 1, 'name': 1}}]))
145137

146-
score = rapidfuzz.fuzz.WRatio(query.lower(), game['name'], processor=rapidfuzz.utils.default_process)
138+
# Cache the 10 most popular games
139+
users = mclient.bowser.users.find({"favgames": {'$exists': True, '$not': {'$size': 0}}})
140+
games = {}
141+
for user in users:
142+
for game_id in user['favgames']:
143+
if game_id not in games:
144+
games[game_id] = 1
147145

148-
if not match['score'] or (score > match['score']):
149-
match = {'deku_id': game['deku_id'], 'score': score, 'name': game['name']}
146+
else:
147+
games[game_id] += 1
150148

151-
if match['score'] < SEARCH_RATIO_THRESHOLD:
152-
return None
149+
top_10 = dict(sorted(games.items(), key=lambda kv: kv[1], reverse=True)[0:10])
150+
games = self.db.find({"deku_id": {"$in": list(top_10.keys())}}, projection={'deku_id': 1, 'name': 1})
153151

154-
return match
152+
self.topGames = list(games)
155153

156154
async def get_image(self, deku_id: str, as_url: bool = False):
157155
game = self.db.find_one({'deku_id': deku_id}, projection={'image': 1})
@@ -174,16 +172,39 @@ def get_name(self, deku_id: str):
174172
document = self.db.find_one({'deku_id': deku_id}, projection={'name': 1})
175173
return document['name'] if document else None
176174

175+
def search(self, query: str, multiResult=False) -> Optional[dict]:
176+
gameList = self.gameNamesCache
177+
178+
# If the game we are comparing is really short (<=3 chars), do not allow a match if our search is longer.
179+
# This prevents things like 'a' being the best match for 'realMyst' and not 'realMyst: Masterpiece Edition'
180+
if len(query) > 5:
181+
gameList = [g for g in gameList if len(g['name']) > 5]
182+
183+
results = rapidfuzz.process.extract(
184+
query,
185+
[g['name'] for g in gameList],
186+
scorer=rapidfuzz.fuzz.WRatio,
187+
limit=10 if multiResult else 1,
188+
processor=rapidfuzz.utils.default_process,
189+
score_cutoff=SEARCH_RATIO_THRESHOLD,
190+
)
191+
192+
if not results:
193+
return None
194+
195+
ret = [{'deku_id': gameList[i]['deku_id'], 'score': score, 'name': name} for name, score, i in results]
196+
return ret if multiResult else ret[0]
197+
177198
async def _games_search_autocomplete(self, interaction: discord.Interaction, current: str):
178199
if current:
179-
game = self.search(current)
200+
games = self.search(current, True)
180201

181202
else:
182203
# Current textbox is empty
183-
return []
204+
return [app_commands.Choice(name=game['name'], value=game['deku_id']) for game in self.topGames]
184205

185-
if game:
186-
return [app_commands.Choice(name=game['name'], value=game['deku_id'])]
206+
if games:
207+
return [app_commands.Choice(name=game['name'], value=game['deku_id']) for game in games]
187208
else:
188209
return []
189210

0 commit comments

Comments
 (0)