feat: Add Russian Roulette feature betwwen two players
Some checks failed
CD / Deploy to VPS (push) Has been cancelled
Some checks failed
CD / Deploy to VPS (push) Has been cancelled
This commit is contained in:
34
cogs/cs.py
34
cogs/cs.py
@@ -1,15 +1,18 @@
|
||||
import discord, datetime, os, re, json
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.firefox.service import Service
|
||||
from bs4 import BeautifulSoup
|
||||
from discord.ext import commands, tasks, bridge
|
||||
from setup.logger import LOGGER
|
||||
from setup.config import Config
|
||||
from models.cs_models import *
|
||||
|
||||
BASE_URL = "https://www.hltv.org"
|
||||
|
||||
PAGE_FILE = os.path.join("data","hltv.html")
|
||||
PAGE_FILE = os.path.join(Config.CONFIG_PATH,"hltv.html")
|
||||
|
||||
MATCHES_FILE = os.path.join("data","matches.json")
|
||||
MATCHES_FILE = os.path.join(Config.CONFIG_PATH,"matches.json")
|
||||
|
||||
# Cache duration in seconds (6 hours)
|
||||
DATA_CACHE_DURATION = 3600 * 6
|
||||
@@ -43,14 +46,27 @@ class CSCog(commands.Cog):
|
||||
return False
|
||||
|
||||
def get_page(self) -> None:
|
||||
"""Get the page from https://www.hltv.org/matches/ using Firefox webdriver and save to PAGE_FILE."""
|
||||
driver = webdriver.Firefox()
|
||||
driver.get("https://www.hltv.org/matches/")
|
||||
html = driver.page_source
|
||||
driver.close()
|
||||
"""Get the page from https://www.hltv.org/matches/ using a headless Firefox webdriver and save to PAGE_FILE."""
|
||||
options = Options()
|
||||
options.add_argument("--headless=new")
|
||||
options.add_argument("--no-sandbox")
|
||||
options.add_argument("--disable-dev-shm-usage")
|
||||
options.add_argument("--window-size=1920,1080")
|
||||
|
||||
with open(PAGE_FILE, 'w') as f:
|
||||
f.write(html)
|
||||
geckodriver_path = os.getenv("GECKODRIVER_PATH")
|
||||
service = Service(executable_path=geckodriver_path) if geckodriver_path else None
|
||||
|
||||
driver = None
|
||||
try:
|
||||
driver = webdriver.Firefox(service=service, options=options)
|
||||
driver.get("https://www.hltv.org/matches/")
|
||||
html = driver.page_source
|
||||
|
||||
with open(PAGE_FILE, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
finally:
|
||||
if driver is not None:
|
||||
driver.quit()
|
||||
|
||||
def save(self) -> None:
|
||||
"""Save self.matches to MATCHES_FILE."""
|
||||
|
||||
@@ -2,12 +2,13 @@ import discord, json, os, random, re
|
||||
import collections
|
||||
from discord.ext import commands
|
||||
from setup.logger import LOGGER
|
||||
from setup.config import Config
|
||||
|
||||
class InsultsCog(commands.Cog):
|
||||
def __init__(self, bot:discord.Bot):
|
||||
self.bot = bot
|
||||
|
||||
with open(os.path.join("data", "insults.txt")) as f:
|
||||
with open(os.path.join(Config.CONFIG_PATH, "insults.txt")) as f:
|
||||
self.insults = [l.strip() for l in f.readlines()]
|
||||
|
||||
@commands.Cog.listener()
|
||||
|
||||
212
cogs/roulette.py
Normal file
212
cogs/roulette.py
Normal file
@@ -0,0 +1,212 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import random
|
||||
|
||||
import discord
|
||||
from discord import option
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
class RouletteCog(commands.Cog):
|
||||
def __init__(self, bot: discord.Bot):
|
||||
self.bot = bot
|
||||
self.cooldown = 60 * 3
|
||||
self.active_games = {}
|
||||
|
||||
def _build_view(self, game: dict) -> discord.ui.View:
|
||||
view = discord.ui.View(timeout=None)
|
||||
|
||||
if game["status"] == "pending":
|
||||
async def accept_callback(interaction: discord.Interaction):
|
||||
if interaction.user.id != game["target"].id:
|
||||
await interaction.response.send_message(
|
||||
"Only the challenged member can accept this duel.",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
game["status"] = "ready"
|
||||
game["current_player"] = game["author"]
|
||||
|
||||
if game.get("message"):
|
||||
await game["message"].edit(
|
||||
content=(
|
||||
f"{game['author'].mention} challenged {game['target'].mention} to a Russian roulette duel.\n"
|
||||
f"✅ {game['target'].mention} accepted. {game['current_player'].mention}, click Fire!"
|
||||
),
|
||||
view=self._build_view(game),
|
||||
)
|
||||
|
||||
await interaction.response.defer()
|
||||
|
||||
async def decline_callback(interaction: discord.Interaction):
|
||||
if interaction.user.id != game["target"].id:
|
||||
await interaction.response.send_message(
|
||||
"Only the challenged member can decline this duel.",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
game["status"] = "declined"
|
||||
self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None)
|
||||
|
||||
if game.get("message"):
|
||||
await game["message"].edit(
|
||||
content=f"{game['target'].mention} declined the challenge.",
|
||||
view=None,
|
||||
)
|
||||
|
||||
await interaction.response.defer()
|
||||
|
||||
accept_button = discord.ui.Button(label="Accept", style=discord.ButtonStyle.green)
|
||||
accept_button.callback = accept_callback
|
||||
view.add_item(accept_button)
|
||||
|
||||
decline_button = discord.ui.Button(label="Decline", style=discord.ButtonStyle.red)
|
||||
decline_button.callback = decline_callback
|
||||
view.add_item(decline_button)
|
||||
|
||||
elif game["status"] in {"ready", "playing"}:
|
||||
async def fire_callback(interaction: discord.Interaction):
|
||||
if interaction.user.id != game["current_player"].id:
|
||||
await interaction.response.send_message(
|
||||
f"Wait for {game['current_player'].mention} to fire.",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
if interaction.user.id in game["fired"]:
|
||||
await interaction.response.send_message(
|
||||
"You already fired once. Only one shot per player is allowed.",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
game["fired"].add(interaction.user.id)
|
||||
is_shot = random.randint(1, 6) == 1
|
||||
|
||||
if is_shot:
|
||||
game["status"] = "finished"
|
||||
self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None)
|
||||
|
||||
await self._mute_member(interaction.user, 60)
|
||||
|
||||
if game.get("message"):
|
||||
await game["message"].edit(
|
||||
content=(
|
||||
f"💥 {interaction.user.mention} pulled the trigger and got shot.\n"
|
||||
f"They were muted for 60 seconds."
|
||||
),
|
||||
view=None,
|
||||
)
|
||||
else:
|
||||
if len(game["fired"]) >= 2:
|
||||
game["status"] = "finished"
|
||||
self.active_games.pop((game["guild_id"], game["author"].id, game["target"].id), None)
|
||||
|
||||
if game.get("message"):
|
||||
await game["message"].edit(
|
||||
content=(
|
||||
f"🔫 {interaction.user.mention} survived and both players have fired once.\n"
|
||||
f"No one was shot. The duel ends in a draw."
|
||||
),
|
||||
view=None,
|
||||
)
|
||||
else:
|
||||
game["status"] = "playing"
|
||||
game["current_player"] = game["target"] if interaction.user.id == game["author"].id else game["author"]
|
||||
|
||||
if game.get("message"):
|
||||
await game["message"].edit(
|
||||
content=(
|
||||
f"🔫 {interaction.user.mention} survived.\n"
|
||||
f"Next turn: {game['current_player'].mention}"
|
||||
),
|
||||
view=self._build_view(game),
|
||||
)
|
||||
|
||||
await interaction.response.defer()
|
||||
|
||||
fire_button = discord.ui.Button(label="Fire", style=discord.ButtonStyle.danger)
|
||||
fire_button.callback = fire_callback
|
||||
view.add_item(fire_button)
|
||||
|
||||
return view
|
||||
|
||||
async def _mute_member(self, member: discord.Member, seconds: int = 60):
|
||||
until = discord.utils.utcnow() + datetime.timedelta(seconds=seconds)
|
||||
try:
|
||||
await member.timeout(until=until, reason="Russian roulette loss")
|
||||
except AttributeError:
|
||||
await member.edit(timed_out_until=until, reason="Russian roulette loss")
|
||||
|
||||
@commands.slash_command(
|
||||
name="roulette",
|
||||
description="Challenge other server members. The loser is getting muted for 1 minute",
|
||||
)
|
||||
@option(
|
||||
name="target",
|
||||
description="The member to challenge",
|
||||
input_type=discord.Member,
|
||||
required=True,
|
||||
)
|
||||
async def roulette(
|
||||
self,
|
||||
ctx: discord.ApplicationContext,
|
||||
target: discord.Member,
|
||||
):
|
||||
author = ctx.author
|
||||
self_test = author.id == target.id
|
||||
|
||||
if self_test:
|
||||
target = author
|
||||
|
||||
game = {
|
||||
"author": author,
|
||||
"target": target,
|
||||
"status": "pending",
|
||||
"current_player": None,
|
||||
"guild_id": ctx.guild_id,
|
||||
"message": None,
|
||||
"fired": set(),
|
||||
}
|
||||
|
||||
self.active_games[(ctx.guild_id, author.id, target.id)] = game
|
||||
|
||||
await ctx.send_response(
|
||||
f"{author.mention} challenged {target.mention} to a Russian roulette duel.\n"
|
||||
f"The target has 60 seconds to accept or decline.",
|
||||
view=self._build_view(game),
|
||||
)
|
||||
|
||||
if self_test:
|
||||
await ctx.send_followup(
|
||||
"**Self-challenge enabled for testing.**\n"
|
||||
"The game will start immediately and you can test the flow.",
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
try:
|
||||
game["message"] = await ctx.interaction.original_response()
|
||||
except Exception:
|
||||
game["message"] = None
|
||||
|
||||
async def timeout_handler():
|
||||
try:
|
||||
await asyncio.sleep(60)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
|
||||
if game.get("status") == "pending":
|
||||
game["status"] = "timeout"
|
||||
self.active_games.pop((ctx.guild_id, author.id, target.id), None)
|
||||
|
||||
await self._mute_member(target, 60)
|
||||
|
||||
if game.get("message"):
|
||||
await game["message"].edit(
|
||||
content=f"⏰ {target.mention} did not respond in time and was muted for 60 seconds.",
|
||||
view=None,
|
||||
)
|
||||
|
||||
asyncio.create_task(timeout_handler())
|
||||
Reference in New Issue
Block a user