async def profile(msg: Message) -> None: userid = utils.to_user_id(msg.arg.strip()) if userid == "": userid = msg.user.userid db = Database.open() with db.get_session() as session: stmt = select(d.Users).filter_by(userid=userid) # TODO: remove annotation userdata: d.Users = session.scalar(stmt) if userdata and userdata.userid and userdata.avatar: stmt = ( select(d.Badges).filter_by(userid=userdata.userid).order_by(d.Badges.id) ) badges: list[d.Badges] = session.execute(stmt).scalars().all() if userdata.avatar[0] == "#": avatar_dir = "trainers-custom" avatar_name = userdata.avatar[1:] else: avatar_dir = "trainers" avatar_name = userdata.avatar html = ProfileHTML( avatar_dir=avatar_dir, avatar_name=avatar_name, username=userdata.username or userdata.userid, badges=badges, description=userdata.description, pokemon_icon=userdata.icon, ) await msg.reply_htmlbox(html.doc)
async def cleanup_table(conn: Connection) -> None: db = Database.open() with db.get_session() as session: stmt = (delete(d.Tokens).where( d.Tokens.expiry < str(datetime.utcnow())).execution_options( synchronize_session=False)) session.execute(stmt)
async def addquote(msg: Message) -> None: if not msg.arg: await msg.reply("Cosa devo salvare?") return db = Database.open() with db.get_session() as session: result = d.Quotes( message=msg.arg, roomid=msg.parametrized_room.roomid, author=msg.user.userid, date=str(date.today()), ) session.add(result) session.commit() try: if result.id: await msg.reply("Quote salvata.") if msg.room is None: await msg.parametrized_room.send_modnote( "QUOTE ADDED", msg.user, msg.arg) return except ObjectDeletedError: pass await msg.reply("Quote già esistente.")
async def join_leave_name(msg: ProtocolMessage) -> None: db = Database.open() with db.get_session() as session: for user in msg.params: stmt = (update(d.TemporaryVoices).filter_by( roomid=msg.room.roomid, userid=utils.to_user_id(user)).values( date=str(datetime.utcnow()))) session.execute(stmt)
async def antonio200509(msg: Message) -> None: db = Database.open("veekun") with db.get_session() as session: stmt = (select(v.PokemonSpeciesNames).filter_by( local_language_id=msg.language_id).order_by(func.random())) # TODO: remove annotation species: v.PokemonSpeciesNames = session.scalar(stmt) if not species: raise SQLAlchemyError("Missing PokemonSpeciesNames data") species_name = species.name numbers = str(random.randint(0, 999999)).zfill(6) await msg.reply(f'Antonio{numbers} guessed "{species_name}"!')
async def encounters(msg: Message) -> None: if len(msg.args) < 1: return location_id = utils.to_id(utils.remove_diacritics(msg.args[0].lower())) language_id = msg.language_id if len(msg.args) >= 2: language_id = utils.get_language_id(msg.args[1], fallback=language_id) db = Database.open("veekun") with db.get_session(language_id) as session: stmt = (select(v.Locations).options( selectinload(v.Locations.location_areas).options( selectinload(v.LocationAreas.location_area_prose), selectinload(v.LocationAreas.encounters).options( selectinload(v.Encounters.version).selectinload( v.Versions.version_names), selectinload(v.Encounters.pokemon).selectinload( v.Pokemon.species).selectinload( v.PokemonSpecies.pokemon_species_names), selectinload(v.Encounters.encounter_slot).selectinload( v.EncounterSlots.encounter_method).selectinload( v.EncounterMethods.encounter_method_prose), selectinload(v.Encounters.encounter_condition_value_map). selectinload(v.EncounterConditionValueMap. encounter_condition_value).selectinload( v.EncounterConditionValues. encounter_condition_value_prose), ), )).filter_by(identifier=location_id)) # TODO: remove annotation location: v.Locations | None = session.scalar(stmt) if location is None: await msg.reply("Location not found.") return results = Encounters() for area in location.location_areas: for encounter in area.encounters: results.add_encounter_slot(area, encounter) html = EncountersHTML(encounters_data=results) if not html: await msg.reply("No data available.") return await msg.reply_htmlbox(html.doc)
async def clearprofile(msg: Message) -> None: db = Database.open() with db.get_session() as session: userid = msg.user.userid session.add(d.Users(userid=userid)) stmt = ( update(d.Users) .filter_by(userid=userid) .values(description="", description_pending="") ) session.execute(stmt) await msg.reply("Frase rimossa")
async def rifiutaprofilo(msg: Message) -> None: db = Database.open() with db.get_session() as session: parts = msg.arg.split(",") stmt = ( update(d.Users) .filter_by(id=parts[0], description_pending=",".join(parts[1:])) .values(description_pending="") ) session.execute(stmt) await msg.user.send_htmlpage("pendingdescriptions", msg.conn.main_room)
async def tempvoice(msg: Message) -> None: if msg.parametrized_room.roombot and msg.user.rank( msg.parametrized_room) == " ": await msg.parametrized_room.send(f"/roomvoice {msg.user.userid}", False) db = Database.open() with db.get_session() as session: session.add( d.TemporaryVoices( roomid=msg.parametrized_room.roomid, userid=msg.user.userid, date=str(datetime.utcnow()), ))
async def removeeightballanswerid(msg: Message) -> None: if len(msg.args) != 2: return db = Database.open() with db.get_session() as session: stmt = select(d.EightBall).filter_by( id=msg.args[0], roomid=msg.parametrized_room.roomid) answer: d.EightBall # TODO: remove annotation if answer := session.scalar(stmt): if msg.room is None: await msg.parametrized_room.send_modnote( "EIGHTBALL ANSWER REMOVED", msg.user, answer.answer) session.delete(answer)
async def eightball(msg: Message) -> None: db = Database.open() with db.get_session() as session: language_name = msg.language if language_name not in DEFAULT_ANSWERS: language_name = "English" answers = DEFAULT_ANSWERS[language_name] if msg.room: stmt = select(d.EightBall.answer).filter_by(roomid=msg.room.roomid) answers.extend(session.execute(stmt).scalars()) await msg.reply(random.choice(answers))
async def removequoteid(msg: Message) -> None: room = msg.parametrized_room if len(msg.args) != 2: return db = Database.open() with db.get_session() as session: stmt = select(d.Quotes).filter_by(id=msg.args[0], roomid=room.roomid) quote: d.Quotes # TODO: remove annotation if quote := session.scalar(stmt): await msg.parametrized_room.send_modnote("QUOTE REMOVED", msg.user, quote.message) session.delete(quote)
async def removeeightballanswer(msg: Message) -> None: if not msg.arg: await msg.reply("Che risposta devo cancellare?") return db = Database.open() with db.get_session() as session: stmt = delete(d.EightBall).filter_by( answer=msg.arg, roomid=msg.parametrized_room.roomid) if session.execute(stmt).rowcount: # type: ignore[attr-defined] await msg.reply("Risposta cancellata.") if msg.room is None: await msg.parametrized_room.send_modnote( "EIGHTBALL ANSWER REMOVED", msg.user, msg.arg) else: await msg.reply("Risposta inesistente.")
async def removequote(msg: Message) -> None: if not msg.arg: await msg.reply("Che quote devo cancellare?") return db = Database.open() with db.get_session() as session: stmt = delete(d.Quotes).filter_by(message=msg.arg, roomid=msg.parametrized_room.roomid) if session.execute(stmt).rowcount: # type: ignore[attr-defined] await msg.reply("Quote cancellata.") if msg.room is None: await msg.parametrized_room.send_modnote( "QUOTE REMOVED", msg.user, msg.arg) else: await msg.reply("Quote inesistente.")
async def randquote(msg: Message) -> None: db = Database.open() with db.get_session() as session: stmt = (select(d.Quotes).filter_by( roomid=msg.parametrized_room.roomid).order_by(func.random())) if msg.arg: # LIKE wildcards are supported and "*" is considered an alias for "%". keyword = msg.arg.replace("*", "%") stmt = stmt.where(d.Quotes.message.ilike(f"%{keyword}%")) quote: d.Quotes = session.scalar(stmt) # TODO: remove annotation if not quote: await msg.reply("Nessuna quote trovata.") return html = e.RawTextNode(to_html_quotebox(quote.message)) await msg.reply_htmlbox(html)
def _get_translations( word: str, languages: tuple[int, int]) -> dict[tuple[str, str], set[str]]: word = utils.to_user_id(utils.remove_diacritics(utils.get_alias(word))) results: dict[tuple[str, str], set[str]] = {} db = Database.open("veekun") with db.get_session() as session: tables: dict[str, tuple[type[v.TranslatableMixin], type[TranslatableTableNames]]] = { "ability": (v.Abilities, v.AbilityNames), "item": (v.Items, v.ItemNames), "move": (v.Moves, v.MoveNames), "nature": (v.Natures, v.NatureNames), } for category_name, t in tables.items(): stmt = (select(t[0], t[1].local_language_id).select_from( t[0]).join(t[1]).where( t[1].local_language_id.in_(languages), t[1].name_normalized == word, )) # TODO: remove annotations row: v.TranslatableMixin language_id: int for row, language_id in session.execute(stmt): translation = row.get_translation( f"{category_name}_names", language_id=list(set(languages) - {language_id})[0], fallback_english=False, ) if translation is not None: res = ( category_name, utils.to_id(utils.remove_diacritics(translation)), ) if res not in results: results[res] = set() results[res].add(translation) return results
async def icon(msg: Message) -> None: if len(msg.args) < 1: await msg.reply( "You can set your Pokémon using " f"``{msg.conn.command_character}icon <pokemon>``, " "or install the userstyle (https://git.io/JuoFg) " "using Stylus (https://add0n.com/stylus.html)." ) return search_query = utils.to_id(msg.args[0]) dex_entry = utils.get_ps_dex_entry(search_query) if dex_entry is None or dex_entry["num"] <= 0: await msg.reply("Pokémon not found.") return db = Database.open() with db.get_session() as session: userid = msg.user.userid session.add(d.Users(userid=userid)) stmt = ( update(d.Users) .filter_by(userid=userid) .values(icon=utils.to_id(dex_entry["dex_name"])) ) session.execute(stmt) # Update the CSV file stmt_csv = ( select(d.Users.userid, d.Users.icon) .where(d.Users.icon.is_not(None)) .order_by(d.Users.userid) ) with utils.get_config_file("userlist_icons.csv").open( "w", encoding="utf-8" ) as f: f.writelines( [f"{userid},{icon}\n" for userid, icon in session.execute(stmt_csv)] ) await msg.reply( "Done. Your Pokémon might take up to 24 hours to appear on the userstyle." )
def load_page(self, page: int) -> None: db = Database.open() with db.get_session() as session: stmt_last_page = self._stmt.with_only_columns(func.count()) last_page = math.ceil(session.scalar(stmt_last_page) / 100) page = min(page, last_page) stmt_rs = self._stmt.limit(100).offset(100 * (page - 1)) rs = session.execute(stmt_rs).all() with self.doc, e.Div(class_="pad"): e.H2(self._title) if not rs: e.TextNode("No results found") return with e.Div(class_="ladder"), e.Table(): with e.Tr(): for field_header, _ in self._fields: e.Th(field_header) e.Th(self._actions_header) for row in rs: with e.Tr(): for _, field in self._fields: e.Td( str(getattr(row[0], field) or "") if isinstance(field, str) else field(row)) with e.Td(style=self._get_css("one_pixel_width")): self._add_action_buttons(row) page_cmd = (f"/pm {self._botname}, {self._cmd_char}changepage " f"{self._command}, {self._room.roomid}, ") for p in range(last_page): with e.Button(p + 1, class_="option") as btn: if page == p + 1: btn.add_class("sel") btn["disabled"] = True else: btn["name"] = "send" btn["value"] = f"{page_cmd}{p + 1}"
def get_required_rank(self, roomid: RoomId | None, is_pm: bool) -> Role: req_rank = self.required_rank if self.required_rank_editable is not False and roomid: command = f".{self.name}" if isinstance(self.required_rank_editable, str): command = self.required_rank_editable db = Database.open() with db.get_session() as session: stmt = select(d.CustomPermissions.required_rank).filter_by( roomid=roomid, command=command ) custom_rank: Role | None = session.scalar(stmt) if custom_rank: req_rank = custom_rank if is_pm and isinstance(self.allow_pm, str): req_rank = self.allow_pm return req_rank
async def setpermission(msg: Message) -> None: room = msg.parametrized_room if len(msg.args) != 3: return command = msg.args[0] if command not in Command.get_rank_editable_commands(): return rank = msg.args[2] if rank not in PERMISSION_ROLES: return rank = cast(Role | Literal["default"], rank) db = Database.open() with db.get_session() as session: if rank == "default": stmt = delete(d.CustomPermissions).filter_by(roomid=room.roomid, command=command) session.execute(stmt) else: session.add( d.CustomPermissions(roomid=room.roomid, command=command, required_rank=rank)) await room.send_modnote("PERMISSIONS", msg.user, f"set the required rank for {command} to {rank}") try: page = int(msg.args[1]) except ValueError: page = 1 await msg.user.send_htmlpage("permissions", room, page, scroll_to_top=False)
async def addeightballanswer(msg: Message) -> None: if not msg.arg: await msg.reply("Cosa devo salvare?") return db = Database.open() with db.get_session() as session: result = d.EightBall(answer=msg.arg, roomid=msg.parametrized_room.roomid) session.add(result) session.commit() try: if result.id: await msg.reply("Risposta salvata.") if msg.room is None: await msg.parametrized_room.send_modnote( "EIGHTBALL ANSWER ADDED", msg.user, msg.arg ) return except ObjectDeletedError: pass await msg.reply("Risposta già esistente.")
async def setprofile(msg: Message) -> None: if not msg.arg: await msg.reply("Specifica una frase da inserire nel tuo profilo") return if len(msg.arg) > 250: await msg.reply("Errore: lunghezza massima 250 caratteri") return # authorized: True if msg.user can approve new descriptions. authorized = msg.user.has_role("driver", msg.conn.main_room) db = Database.open() with db.get_session() as session: userid = msg.user.userid session.add(d.Users(userid=userid)) stmt = update(d.Users).filter_by(userid=userid) if authorized: # Authorized users skip the validation process. stmt = stmt.values(description=msg.arg, description_pending="") else: stmt = stmt.values(description_pending=msg.arg) session.execute(stmt) await msg.reply("Salvato") if not authorized: username = msg.user.username botname = msg.conn.username cmd = f"{msg.conn.command_character}pendingdescriptions" html = f"{username} ha aggiornato la sua frase del profilo." + e.Br() html += ( "Usa " + e.Button(cmd, name="send", value=f"/pm {botname}, {cmd}") + " per approvarla o rifiutarla" ) await msg.conn.main_room.send_rankhtmlbox("%", html)
async def demote_old_temporary_voices(conn: Connection) -> None: await asyncio.sleep(3 * 60 * 60) db = Database.open() while True: with db.get_session() as session: stmt = select(d.TemporaryVoices).filter( d.TemporaryVoices.date < datetime.utcnow() - timedelta(days=30)) user: d.TemporaryVoices | None = session.scalar(stmt) if user: room = Room.get(conn, user.roomid) if room.roombot: await room.send(f"/roomdeauth {user.userid}", False) session.delete(user) # sleep for a minute, then try to deauth another user wait_time = 60 else: # sleep for a day if there are no more users to deauth wait_time = 24 * 60 * 60 await asyncio.sleep(wait_time)
async def csv_to_sqlite(conn: Connection) -> None: latest_veekun_commit = "" try: latest_veekun_commit = (subprocess.run( [ "git", "rev-list", "-1", "HEAD", "--", "data/veekun", "databases/veekun.py", "tasks/veekun.py", ], cwd=join(dirname(__file__), ".."), capture_output=True, check=True, ).stdout.decode().strip()) db = Database.open("veekun") with db.get_session() as session: stmt = select(v.LatestCommit.commit_id) if session.scalar(stmt) == latest_veekun_commit: return # database is already up-to-date, skip rebuild except ( subprocess.SubprocessError, # generic subprocess error FileNotFoundError, # git is not available OperationalError, # table does not exist ): pass # always rebuild on error print("Rebuilding veekun database...") with open(utils.get_config_file("veekun.sqlite"), "wb"): # truncate database pass db = Database.open("veekun") v.Base.metadata.create_all(db.engine) tables_classes = { obj.__tablename__: obj for name, obj in inspect.getmembers(v) if inspect.isclass(obj) and obj.__module__ == v.__name__ and hasattr(obj, "__tablename__") } with db.get_session() as session: if latest_veekun_commit: session.add(v.LatestCommit(commit_id=latest_veekun_commit)) for table in v.Base.metadata.sorted_tables: tname = table.key file_name = utils.get_data_file("veekun", f"{tname}.csv") if isfile(file_name): with open(file_name, encoding="utf-8") as f: csv_data = csv.DictReader(f) csv_keys = csv_data.fieldnames if csv_keys is not None: data = [dict(i) for i in csv_data] if hasattr(table.columns, "name_normalized"): for row in data: row["name_normalized"] = utils.to_user_id( utils.remove_diacritics(row["name"])) if tname == "locations": for row in data: if num := re.search(r"route-(\d+)", row["identifier"]): row["route_number"] = num[1] bulk_insert_stmt = insert(tables_classes[tname]) session.execute(bulk_insert_stmt, data) if "identifier" in csv_keys: bulk_update_stmt = (update( tables_classes[tname]).values( identifier=func.replace( tables_classes[tname].identifier, "-", "")).execution_options( synchronize_session=False)) session.execute(bulk_update_stmt)
async def learnset(msg: Message) -> None: if len(msg.args) < 2: return pokemon_id = utils.to_id(utils.remove_diacritics(msg.args[0].lower())) version_id = utils.to_id(utils.remove_diacritics(msg.args[1].lower())) language_id = msg.language_id if len(msg.args) >= 3: language_id = utils.get_language_id(msg.args[2], fallback=language_id) db = Database.open("veekun") with db.get_session(language_id) as session: stmt = select(v.VersionGroups).filter_by(identifier=version_id) # TODO: remove annotation version_group: v.VersionGroups | None = session.scalar(stmt) if version_group is None: stmt = select(v.Versions).filter_by(identifier=version_id) # TODO: remove annotation version: v.Versions | None = session.scalar(stmt) if version is None: await msg.reply("Game version not found.") return version_group = version.version_group stmt = (select(v.PokemonSpecies).options( selectinload(v.PokemonSpecies.pokemon).selectinload( v.Pokemon.pokemon_moves.and_( v.PokemonMoves.version_group_id == version_group.id)). options( selectinload(v.PokemonMoves.move).options( selectinload(v.Moves.move_names), selectinload( v.Moves.machines.and_( v.Machines.version_group_id == version_group.id)).selectinload( v.Machines.item).selectinload( v.Items.item_names), ), selectinload(v.PokemonMoves.pokemon_move_method).selectinload( v.PokemonMoveMethods.pokemon_move_method_prose), )).filter_by(identifier=pokemon_id)) # TODO: remove annotation pokemon_species: v.PokemonSpecies | None = session.scalar(stmt) if pokemon_species is None: await msg.reply("Pokémon not found.") return results = Learnset() all_forms = set(pokemon_species.pokemon) for pokemon in pokemon_species.pokemon: for pokemon_move in pokemon.pokemon_moves: results.add_move(pokemon, pokemon_move) for method_data in results.methods.values(): for move_data in method_data.moves.values(): if move_data.forms == all_forms: move_data.forms = set() else: method_data.form_column = True html = LearnsetHTML(learnset_data=results) if not html: await msg.reply("No data available.") return await msg.reply_htmlbox(html.doc)
async def create_or_upgrade_database(conn: Connection) -> None: db = Database.open() d.Base.metadata.create_all(db.engine)