diff --git a/db.py b/db.py index b8ab01b6..742dd22e 100644 --- a/db.py +++ b/db.py @@ -1215,31 +1215,90 @@ mini_list = [Royal, Telegram, Steam, Dota, LeagueOfLegends, Osu, Discord, Overwa Terraria13] -class Matchmaker(Base): - __tablename__ = "matchmakers" +class Match(Base): + __tablename__ = "matches" id = Column(Integer, primary_key=True) + timestamp = Column(DateTime) + creator_id = Column(BigInteger, ForeignKey("telegram.telegram_id")) + creator = relationship("Telegram", backref="matches_created", lazy="joined") - matchmaking_name = Column(String) - matchmaking_desc = Column(Text) - + match_title = Column(String) + match_desc = Column(Text) min_players = Column(Integer) max_players = Column(Integer) + closed = Column(Boolean, default=False) - timestamp = Column(DateTime) - expires_in = Column(DateTime) + message_id = Column(BigInteger) - players = relationship("MatchmakingEntry", lazy="joined") + def active_players_count(self): + count = 0 + for player in self.players: + if player.status == MatchmakingStatus.READY or player.status == MatchmakingStatus.WAIT_FOR_ME: + count += 1 + return count + + def generate_text(self, session): + player_list = session.query(MatchPartecipation).filter_by(match=self).all() + title = f"{self.match_title}" + description = self.match_desc if self.match_desc else "" + if self.min_players: + minimum = f" (minimo {self.min_players})" + else: + minimum = "" + plist = f"Giocatori{minimum}:\n" + ignore_count = 0 + for player in player_list: + if player.status == MatchmakingStatus.WAIT_FOR_ME: + icon = "๐Ÿ•’" + elif player.status == MatchmakingStatus.READY: + icon = "๐Ÿ”ต" + elif player.status == MatchmakingStatus.IGNORED: + ignore_count += 1 + continue + else: + continue + plist += f"{icon} {player.user.royal.username}\n" + if ignore_count: + ignored = f"โŒ {ignore_count} persone non ne hanno voglia.\n" + else: + ignored = "" + if self.max_players: + players = f"[{self.active_players_count()}/{self.max_players}]" + else: + players = f"[{self.active_players_count()}]" + close = f"[matchmaking concluso]\n" if self.closed else "" + message = f"{title} {players}\n" \ + f"{description}\n" \ + f"{plist}\n" \ + f"{ignored}" \ + f"{close}" + return message + + def __repr__(self): + return f"" -class MatchmakingEntry(Base): - __tablename__ = "matchmakingentry" - - royal_id = Column(Integer, ForeignKey("royals.id"), primary_key=True) - royal = relationship("Royal", backref="matchmades", lazy="joined") +class MatchmakingStatus(enum.IntEnum): + WAIT_FOR_ME = 1 + READY = 2 + IGNORED = -1 +class MatchPartecipation(Base): + __tablename__ = "matchpartecipations" + __table_args__ = (PrimaryKeyConstraint("user_id", "match_id"),) + user_id = Column(BigInteger, ForeignKey("telegram.telegram_id")) + user = relationship("Telegram", backref="match_partecipations", lazy="joined") + + match_id = Column(Integer, ForeignKey("matches.id")) + match = relationship("Match", backref="players", lazy="joined") + + status = Column(Integer) + + def __repr__(self): + return f"" # If run as script, create all the tables in the db diff --git a/telegrambot.py b/telegrambot.py index 37d67d3d..787d15d6 100644 --- a/telegrambot.py +++ b/telegrambot.py @@ -64,19 +64,20 @@ def catch_and_report(func: "function"): f"```", parse_mode="Markdown") except Exception: logger.error(f"Double critical error: {sys.exc_info()}") - if not __debug__: - sentry.user_context({ - "id": update.effective_user.id, - "telegram": { - "username": update.effective_user.username, - "first_name": update.effective_user.first_name, - "last_name": update.effective_user.last_name - } - }) - sentry.extra_context({ - "update": update.to_dict() - }) - sentry.captureException() + if __debug__: + raise + sentry.user_context({ + "id": update.effective_user.id, + "telegram": { + "username": update.effective_user.username, + "first_name": update.effective_user.first_name, + "last_name": update.effective_user.last_name + } + }) + sentry.extra_context({ + "update": update.to_dict() + }) + sentry.captureException() return new_func @@ -282,32 +283,69 @@ def cmd_vote(bot: Bot, update: Update): parse_mode="HTML") vote.message_id = message.message_id session.commit() - except Exception: - raise + finally: + session.close() + + +@catch_and_report +def cmd_mm(bot: Bot, update: Update): + session = db.Session() + try: + user = session.query(db.Telegram).filter_by(telegram_id=update.message.from_user.id).one_or_none() + if user is None: + bot.send_message(update.message.chat.id, + "โš  Il tuo account Telegram non รจ registrato a Royalnet!" + " Registrati con `/register@royalgamesbot `.", parse_mode="Markdown") + return + match = re.match(r"/(?:mm|matchmaking)(?:@royalgamesbot)?(?: (?:([0-9]+)-)?([0-9]+))? (?:per )?([A-Za-z0-9!\-_\. ]+)(?:.*\n(.+))?", + update.message.text) + if match is None: + bot.send_message(update.message.chat.id, + "โš  Sintassi del comando errata.\n" + "Sintassi: `/matchmaking@royalgamesbot [minplayers]-[maxplayers] per \\n [descrizione]`") + return + min_players, max_players, match_name, match_desc = match.group(1, 2, 3, 4) + db_match = db.Match(timestamp=datetime.datetime.now(), + match_title=match_name, + match_desc=match_desc, + min_players=min_players, + max_players=max_players, + creator=user) + session.add(db_match) + session.flush() + inline_keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("๐Ÿ”ต Ci sono!", callback_data="match_ready")], + [InlineKeyboardButton("๐Ÿ•’ Sto arrivando, aspettatemi!", callback_data="match_wait_for_me")], + [InlineKeyboardButton("โŒ Non vengo.", callback_data="match_ignore")], + [InlineKeyboardButton("๐Ÿšฉ [termina la ricerca]", callback_data="match_close")]]) + message = bot.send_message(update.message.chat.id, db_match.generate_text(session=session), + parse_mode="HTML", + reply_markup=inline_keyboard) + db_match.message_id = message.message_id + session.commit() finally: session.close() @catch_and_report def on_callback_query(bot: Bot, update: Update): - if update.callback_query.data == "vote_yes": - choice = db.VoteChoices.YES - emoji = "๐Ÿ”ต" - elif update.callback_query.data == "vote_no": - choice = db.VoteChoices.NO - emoji = "๐Ÿ”ด" - elif update.callback_query.data == "vote_abstain": - choice = db.VoteChoices.ABSTAIN - emoji = "โšซ๏ธ" - else: - raise NotImplementedError() if update.callback_query.data.startswith("vote_"): + if update.callback_query.data == "vote_yes": + status = db.VoteChoices.YES + emoji = "๐Ÿ”ต" + elif update.callback_query.data == "vote_no": + status = db.VoteChoices.NO + emoji = "๐Ÿ”ด" + elif update.callback_query.data == "vote_abstain": + status = db.VoteChoices.ABSTAIN + emoji = "โšซ๏ธ" + else: + raise NotImplementedError() session = db.Session() try: user = session.query(db.Telegram).filter_by(telegram_id=update.callback_query.from_user.id).one_or_none() if user is None: bot.answer_callback_query(update.callback_query.id, show_alert=True, - text="โš  Il tuo account Telegram non รจ registrato al RYGdb!" + text="โš  Il tuo account Telegram non รจ registrato a Royalnet!" " Registrati con `/register@royalgamesbot `.", parse_mode="Markdown") return @@ -316,14 +354,14 @@ def on_callback_query(bot: Bot, update: Update): .one() answer = session.query(db.VoteAnswer).filter_by(question=question, user=user).one_or_none() if answer is None: - answer = db.VoteAnswer(question=question, choice=choice, user=user) + answer = db.VoteAnswer(question=question, choice=status, user=user) session.add(answer) bot.answer_callback_query(update.callback_query.id, text=f"Hai votato {emoji}.", cache_time=1) - elif answer.choice == choice: + elif answer.choice == status: session.delete(answer) bot.answer_callback_query(update.callback_query.id, text=f"Hai ritratto il tuo voto.", cache_time=1) else: - answer.choice = choice + answer.choice = status bot.answer_callback_query(update.callback_query.id, text=f"Hai cambiato il tuo voto in {emoji}.", cache_time=1) session.commit() @@ -338,8 +376,79 @@ def on_callback_query(bot: Bot, update: Update): text=question.generate_text(session), reply_markup=inline_keyboard, parse_mode="HTML") - except Exception: - raise + finally: + session.close() + elif update.callback_query.data.startswith("match_"): + session = db.Session() + try: + user = session.query(db.Telegram).filter_by(telegram_id=update.callback_query.from_user.id).one_or_none() + if user is None: + bot.answer_callback_query(update.callback_query.id, show_alert=True, + text="โš  Il tuo account Telegram non รจ registrato a Royalnet!" + " Registrati con `/register@royalgamesbot `.", + parse_mode="Markdown") + return + match = session.query(db.Match).filter_by(message_id=update.callback_query.message.message_id).one() + if update.callback_query.data == "match_ready": + status = db.MatchmakingStatus.READY + text = "๐Ÿ”ต Hai detto che sei pronto per giocare!" + elif update.callback_query.data == "match_wait_for_me": + status = db.MatchmakingStatus.WAIT_FOR_ME + text = "๐Ÿ•’ Hai chiesto agli altri di aspettarti." + elif update.callback_query.data == "match_ignore": + status = db.MatchmakingStatus.IGNORED + text = "โŒ Non ti interessa questa partita." + elif update.callback_query.data == "match_close": + status = None + if match.creator == user: + match.closed = True + text = "๐Ÿšฉ Matchmaking chiuso!" + for player in match.players: + if player.status == db.MatchmakingStatus.READY or player.status == db.MatchmakingStatus.WAIT_FOR_ME: + try: + bot.send_message(player.user.telegram_id, + f"๐Ÿšฉ Sei pronto? {match.match_title} sta iniziando!", + parse_mode="HTML") + except Exception as e: + logger.warning(f"Failed to notify {player.user.username}: {e}") + else: + bot.answer_callback_query(update.callback_query.id, show_alert=True, + text="โš  Non sei il creatore di questo match!") + return + else: + raise NotImplementedError() + if status: + if match.closed: + bot.answer_callback_query(update.callback_query.id, show_alert=True, + text="โš  Il matchmaking รจ terminato!") + return + if match.max_players and match.active_players_count() >= match.max_players: + bot.answer_callback_query(update.callback_query.id, show_alert=True, + text="โš  La partita รจ piena.") + return + player = session.query(db.MatchPartecipation).filter_by(match=match, user=user).one_or_none() + if player is None: + player = db.MatchPartecipation(match=match, status=status.value, user=user) + session.add(player) + else: + player.status = status.value + session.commit() + bot.answer_callback_query(update.callback_query.id, text=text, cache_time=1) + if not match.closed: + inline_keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("๐Ÿ”ต Ci sono!", callback_data="match_ready")], + [InlineKeyboardButton("๐Ÿ•’ Sto arrivando, aspettatemi!", + callback_data="match_wait_for_me")], + [InlineKeyboardButton("โŒ Non vengo.", + callback_data="match_ignore")], + [InlineKeyboardButton("๐Ÿšฉ [termina la ricerca]", + callback_data="match_close")]]) + else: + inline_keyboard = None + bot.edit_message_text(message_id=update.callback_query.message.message_id, + chat_id=update.callback_query.message.chat.id, + text=match.generate_text(session), + reply_markup=inline_keyboard, + parse_mode="HTML") finally: session.close() @@ -601,6 +710,8 @@ def process(arg_discord_connection): u.dispatcher.add_handler(CommandHandler("markov", cmd_markov)) u.dispatcher.add_handler(CommandHandler("roll", cmd_roll)) u.dispatcher.add_handler(CommandHandler("r", cmd_roll)) + u.dispatcher.add_handler(CommandHandler("mm", cmd_mm)) + u.dispatcher.add_handler(CommandHandler("matchmaking", cmd_mm)) u.dispatcher.add_handler(CallbackQueryHandler(on_callback_query)) logger.info("Handlers registered.") u.start_polling()