Skip to content

Commit 671a2ad

Browse files
CopilotkRxZykRxZy
andauthored
Add /recommend command for profile-based recommendations (#19)
* Initial plan * Add /recommend command for personalized recommendations Co-authored-by: kRxZykRxZy <192328467+kRxZykRxZy@users.noreply.github.com> * Address code review feedback: use specific exceptions and itertools.islice Co-authored-by: kRxZykRxZy <192328467+kRxZykRxZy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kRxZykRxZy <192328467+kRxZykRxZy@users.noreply.github.com>
1 parent 73c066f commit 671a2ad

1 file changed

Lines changed: 182 additions & 0 deletions

File tree

commands/search_commands.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import random
6+
from itertools import islice
67

78
import discord
89
from discord import app_commands
@@ -63,3 +64,184 @@ async def christmas(interact: discord.Interaction):
6364
color=scratch_orange,
6465
)
6566
)
67+
68+
69+
@bot.tree.command(
70+
name="recommend",
71+
description="Get personalized recommendations for projects, studios, or users based on a profile!",
72+
)
73+
@app_commands.choices(
74+
recommendation_type=[
75+
app_commands.Choice(name="Projects", value="projects"),
76+
app_commands.Choice(name="Users", value="users"),
77+
app_commands.Choice(name="Studios", value="studios"),
78+
]
79+
)
80+
async def recommend(
81+
interact: discord.Interaction, username: str, recommendation_type: str
82+
):
83+
await interact.response.defer()
84+
85+
try:
86+
user = scratch.get_user(username)
87+
except scratch.utils.exceptions.UserNotFound:
88+
await interact.followup.send(
89+
embed=discord.Embed(
90+
title="Error:",
91+
description="This user doesn't exist! <:giga404:1330551323610976339>",
92+
color=discord.Color.red(),
93+
)
94+
)
95+
return
96+
97+
msg = discord.Embed(color=scratch_orange)
98+
99+
if recommendation_type == "projects":
100+
# Get user's loved and favorited projects to find similar ones
101+
loved_projects = list(islice(user.loved_projects(limit=10), 10))
102+
103+
if not loved_projects:
104+
msg.title = f"No recommendations found for {username}"
105+
msg.description = "This user hasn't loved any projects yet!"
106+
await interact.followup.send(embed=msg)
107+
return
108+
109+
# Extract common tags/themes from loved projects
110+
recommendations = []
111+
seen_ids = set()
112+
113+
# For each loved project, get its author's other popular projects
114+
for project in loved_projects[:3]: # Limit to avoid too many API calls
115+
try:
116+
author = project.author()
117+
author_projects = list(author.projects(limit=5))
118+
for proj in author_projects:
119+
if proj.id not in seen_ids and proj.id != project.id:
120+
recommendations.append(proj)
121+
seen_ids.add(proj.id)
122+
if len(recommendations) >= 5:
123+
break
124+
except Exception:
125+
continue
126+
127+
if len(recommendations) >= 5:
128+
break
129+
130+
if recommendations:
131+
msg.title = f"📚 Project recommendations for {username}"
132+
description = "Based on projects you loved, you might enjoy:\n"
133+
for proj in recommendations[:5]:
134+
description += (
135+
f"\n**[{proj.title}](<https://scratch.mit.edu/projects/{proj.id}>)**\n"
136+
f"-# by [{proj.author_name}](https://scratch.mit.edu/users/{proj.author_name}) "
137+
f"• ❤️ {proj.loves} • ⭐ {proj.favorites}\n"
138+
)
139+
msg.description = description
140+
else:
141+
msg.title = f"No recommendations found for {username}"
142+
msg.description = "Could not find similar projects at this time."
143+
144+
elif recommendation_type == "users":
145+
# Get users that the target user follows
146+
following = list(islice(user.following_names(limit=20), 20))
147+
148+
if not following:
149+
msg.title = f"No recommendations found for {username}"
150+
msg.description = "This user isn't following anyone yet!"
151+
await interact.followup.send(embed=msg)
152+
return
153+
154+
# Find users followed by the people this user follows
155+
recommendations = []
156+
seen_users = set(following + [username])
157+
158+
for followed_username in following[:5]: # Limit API calls
159+
try:
160+
followed_user = scratch.get_user(followed_username)
161+
their_following = list(followed_user.following_names(limit=10))
162+
163+
for potential_rec in their_following:
164+
if potential_rec not in seen_users:
165+
try:
166+
rec_user = scratch.get_user(potential_rec)
167+
recommendations.append(rec_user)
168+
seen_users.add(potential_rec)
169+
if len(recommendations) >= 5:
170+
break
171+
except Exception:
172+
continue
173+
except Exception:
174+
continue
175+
176+
if len(recommendations) >= 5:
177+
break
178+
179+
if recommendations:
180+
msg.title = f"👥 User recommendations for {username}"
181+
description = "Based on who you follow, you might like:\n"
182+
for rec_user in recommendations[:5]:
183+
description += (
184+
f"\n**[{rec_user.username}](https://scratch.mit.edu/users/{rec_user.username})**\n"
185+
f"-# {rec_user.follower_count()} followers • {rec_user.following_count()} following\n"
186+
)
187+
msg.description = description
188+
else:
189+
msg.title = f"No recommendations found for {username}"
190+
msg.description = "Could not find similar users at this time."
191+
192+
elif recommendation_type == "studios":
193+
# Get studios the user is curating
194+
curating = list(islice(user.studios_curating(limit=20), 20))
195+
196+
if not curating:
197+
msg.title = f"No recommendations found for {username}"
198+
msg.description = "This user isn't curating any studios yet!"
199+
await interact.followup.send(embed=msg)
200+
return
201+
202+
# Get related studios from the ones they're already in
203+
recommendations = []
204+
seen_ids = set([studio.id for studio in curating])
205+
206+
for studio in curating[:5]: # Limit API calls
207+
try:
208+
# Get curators of this studio
209+
curators = list(studio.curator_names(limit=10))
210+
211+
# Find other studios these curators are in
212+
for curator_name in curators[:3]:
213+
try:
214+
curator = scratch.get_user(curator_name)
215+
curator_studios = list(curator.studios_curating(limit=5))
216+
217+
for potential_studio in curator_studios:
218+
if potential_studio.id not in seen_ids:
219+
recommendations.append(potential_studio)
220+
seen_ids.add(potential_studio.id)
221+
if len(recommendations) >= 5:
222+
break
223+
except Exception:
224+
continue
225+
226+
if len(recommendations) >= 5:
227+
break
228+
except Exception:
229+
continue
230+
231+
if len(recommendations) >= 5:
232+
break
233+
234+
if recommendations:
235+
msg.title = f"🎨 Studio recommendations for {username}"
236+
description = "Based on studios you're in, you might like:\n"
237+
for studio in recommendations[:5]:
238+
description += (
239+
f"\n**[{studio.title}](<https://scratch.mit.edu/studios/{studio.id}>)**\n"
240+
f"-# {studio.project_count} projects • {studio.follower_count} followers\n"
241+
)
242+
msg.description = description
243+
else:
244+
msg.title = f"No recommendations found for {username}"
245+
msg.description = "Could not find similar studios at this time."
246+
247+
await interact.followup.send(embed=msg)

0 commit comments

Comments
 (0)