|
3 | 3 | """ |
4 | 4 |
|
5 | 5 | import random |
| 6 | +from itertools import islice |
6 | 7 |
|
7 | 8 | import discord |
8 | 9 | from discord import app_commands |
@@ -63,3 +64,184 @@ async def christmas(interact: discord.Interaction): |
63 | 64 | color=scratch_orange, |
64 | 65 | ) |
65 | 66 | ) |
| 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