mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
Add matchmaking service
This commit is contained in:
parent
ab88d70465
commit
d4cc3c9398
2 changed files with 215 additions and 45 deletions
85
db.py
85
db.py
|
@ -1215,31 +1215,90 @@ mini_list = [Royal, Telegram, Steam, Dota, LeagueOfLegends, Osu, Discord, Overwa
|
||||||
Terraria13]
|
Terraria13]
|
||||||
|
|
||||||
|
|
||||||
class Matchmaker(Base):
|
class Match(Base):
|
||||||
__tablename__ = "matchmakers"
|
__tablename__ = "matches"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
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)
|
match_title = Column(String)
|
||||||
matchmaking_desc = Column(Text)
|
match_desc = Column(Text)
|
||||||
|
|
||||||
min_players = Column(Integer)
|
min_players = Column(Integer)
|
||||||
max_players = Column(Integer)
|
max_players = Column(Integer)
|
||||||
|
closed = Column(Boolean, default=False)
|
||||||
|
|
||||||
timestamp = Column(DateTime)
|
message_id = Column(BigInteger)
|
||||||
expires_in = Column(DateTime)
|
|
||||||
|
|
||||||
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"<b>{self.match_title}</b>"
|
||||||
|
description = self.match_desc if self.match_desc else ""
|
||||||
|
if self.min_players:
|
||||||
|
minimum = f" <i>(minimo {self.min_players})</i>"
|
||||||
|
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"❌ <i>{ignore_count} persone non ne hanno voglia.</i>\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"<Match {self.match_title}>"
|
||||||
|
|
||||||
|
|
||||||
class MatchmakingEntry(Base):
|
class MatchmakingStatus(enum.IntEnum):
|
||||||
__tablename__ = "matchmakingentry"
|
WAIT_FOR_ME = 1
|
||||||
|
READY = 2
|
||||||
royal_id = Column(Integer, ForeignKey("royals.id"), primary_key=True)
|
IGNORED = -1
|
||||||
royal = relationship("Royal", backref="matchmades", lazy="joined")
|
|
||||||
|
|
||||||
|
|
||||||
|
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"<MatchPartecipation {self.user.username} in {self.match.match_title}>"
|
||||||
|
|
||||||
|
|
||||||
# If run as script, create all the tables in the db
|
# If run as script, create all the tables in the db
|
||||||
|
|
137
telegrambot.py
137
telegrambot.py
|
@ -64,7 +64,8 @@ def catch_and_report(func: "function"):
|
||||||
f"```", parse_mode="Markdown")
|
f"```", parse_mode="Markdown")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error(f"Double critical error: {sys.exc_info()}")
|
logger.error(f"Double critical error: {sys.exc_info()}")
|
||||||
if not __debug__:
|
if __debug__:
|
||||||
|
raise
|
||||||
sentry.user_context({
|
sentry.user_context({
|
||||||
"id": update.effective_user.id,
|
"id": update.effective_user.id,
|
||||||
"telegram": {
|
"telegram": {
|
||||||
|
@ -282,32 +283,69 @@ def cmd_vote(bot: Bot, update: Update):
|
||||||
parse_mode="HTML")
|
parse_mode="HTML")
|
||||||
vote.message_id = message.message_id
|
vote.message_id = message.message_id
|
||||||
session.commit()
|
session.commit()
|
||||||
except Exception:
|
finally:
|
||||||
raise
|
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 <nomeutenteryg>`.", 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 <gamename> \\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:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
@catch_and_report
|
@catch_and_report
|
||||||
def on_callback_query(bot: Bot, update: Update):
|
def on_callback_query(bot: Bot, update: Update):
|
||||||
|
if update.callback_query.data.startswith("vote_"):
|
||||||
if update.callback_query.data == "vote_yes":
|
if update.callback_query.data == "vote_yes":
|
||||||
choice = db.VoteChoices.YES
|
status = db.VoteChoices.YES
|
||||||
emoji = "🔵"
|
emoji = "🔵"
|
||||||
elif update.callback_query.data == "vote_no":
|
elif update.callback_query.data == "vote_no":
|
||||||
choice = db.VoteChoices.NO
|
status = db.VoteChoices.NO
|
||||||
emoji = "🔴"
|
emoji = "🔴"
|
||||||
elif update.callback_query.data == "vote_abstain":
|
elif update.callback_query.data == "vote_abstain":
|
||||||
choice = db.VoteChoices.ABSTAIN
|
status = db.VoteChoices.ABSTAIN
|
||||||
emoji = "⚫️"
|
emoji = "⚫️"
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
if update.callback_query.data.startswith("vote_"):
|
|
||||||
session = db.Session()
|
session = db.Session()
|
||||||
try:
|
try:
|
||||||
user = session.query(db.Telegram).filter_by(telegram_id=update.callback_query.from_user.id).one_or_none()
|
user = session.query(db.Telegram).filter_by(telegram_id=update.callback_query.from_user.id).one_or_none()
|
||||||
if user is None:
|
if user is None:
|
||||||
bot.answer_callback_query(update.callback_query.id, show_alert=True,
|
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 <nomeutenteryg>`.",
|
" Registrati con `/register@royalgamesbot <nomeutenteryg>`.",
|
||||||
parse_mode="Markdown")
|
parse_mode="Markdown")
|
||||||
return
|
return
|
||||||
|
@ -316,14 +354,14 @@ def on_callback_query(bot: Bot, update: Update):
|
||||||
.one()
|
.one()
|
||||||
answer = session.query(db.VoteAnswer).filter_by(question=question, user=user).one_or_none()
|
answer = session.query(db.VoteAnswer).filter_by(question=question, user=user).one_or_none()
|
||||||
if answer is 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)
|
session.add(answer)
|
||||||
bot.answer_callback_query(update.callback_query.id, text=f"Hai votato {emoji}.", cache_time=1)
|
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)
|
session.delete(answer)
|
||||||
bot.answer_callback_query(update.callback_query.id, text=f"Hai ritratto il tuo voto.", cache_time=1)
|
bot.answer_callback_query(update.callback_query.id, text=f"Hai ritratto il tuo voto.", cache_time=1)
|
||||||
else:
|
else:
|
||||||
answer.choice = choice
|
answer.choice = status
|
||||||
bot.answer_callback_query(update.callback_query.id, text=f"Hai cambiato il tuo voto in {emoji}.",
|
bot.answer_callback_query(update.callback_query.id, text=f"Hai cambiato il tuo voto in {emoji}.",
|
||||||
cache_time=1)
|
cache_time=1)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
@ -338,8 +376,79 @@ def on_callback_query(bot: Bot, update: Update):
|
||||||
text=question.generate_text(session),
|
text=question.generate_text(session),
|
||||||
reply_markup=inline_keyboard,
|
reply_markup=inline_keyboard,
|
||||||
parse_mode="HTML")
|
parse_mode="HTML")
|
||||||
except Exception:
|
finally:
|
||||||
raise
|
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 <nomeutenteryg>`.",
|
||||||
|
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? <b>{match.match_title}</b> 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:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
@ -601,6 +710,8 @@ def process(arg_discord_connection):
|
||||||
u.dispatcher.add_handler(CommandHandler("markov", cmd_markov))
|
u.dispatcher.add_handler(CommandHandler("markov", cmd_markov))
|
||||||
u.dispatcher.add_handler(CommandHandler("roll", cmd_roll))
|
u.dispatcher.add_handler(CommandHandler("roll", cmd_roll))
|
||||||
u.dispatcher.add_handler(CommandHandler("r", 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))
|
u.dispatcher.add_handler(CallbackQueryHandler(on_callback_query))
|
||||||
logger.info("Handlers registered.")
|
logger.info("Handlers registered.")
|
||||||
u.start_polling()
|
u.start_polling()
|
||||||
|
|
Loading…
Reference in a new issue