diff --git a/config/clanmembers.json b/config/clanmembers.json index d1083df..c81001f 100644 --- a/config/clanmembers.json +++ b/config/clanmembers.json @@ -12,11 +12,12 @@ "THAIlux", "Reijn7000", "McPikkie", - "Brabo-Gamer", "SquadKiller101", "HeteKip", "Pettie1972", - "Reijn7000" + "Reijn7001", + "Ghost_0223", + "Bossoemang" ], "alts": [ diff --git a/discord/report_new_matches.ps1 b/discord/report_new_matches.ps1 index d7a242e..d631d09 100644 --- a/discord/report_new_matches.ps1 +++ b/discord/report_new_matches.ps1 @@ -24,7 +24,7 @@ if ($fileContent -match "\`$apiKey\s*=\s*\'([^\']+)\'") { $apiKey = $matches[1] } else { - Write-Output "API Key not found" + write-output "API Key not found" } $headers = @{ @@ -38,15 +38,17 @@ $fileContent = Get-Content -Path "$scriptroot/../discord/config.php" -Raw # Use regex to match the apiKey value if ($fileContent -match "\`$webhookurl\s*=\s*'([^']+)'") { $webhookurl = $matches[1] -} else { - Write-Output "No web url found" +} +else { + write-output "No web url found" } # Use regex to match the losers webhook url if ($fileContent -match "\`$webhookurl_losers\s*=\s*'([^']+)'") { $webhookurl_losers = $matches[1] -} else { - Write-Output "No losers web url found" +} +else { + write-output "No losers web url found" } function send-discord { @@ -69,7 +71,13 @@ function send-discord-losers { $payload = [PSCustomObject]@{ content = $content } + if ($payload.content -eq "") { + $payload = [PSCustomObject]@{ + content = "Nothing to report" + } + } Invoke-RestMethod -Uri $webhookurl_losers -Method Post -Body ($payload | ConvertTo-Json) -ContentType 'Application/Json' + } $map_map = @{ @@ -91,142 +99,142 @@ try { $player_matches = get-content "$scriptroot/../data/player_matches.json" | convertfrom-json -Depth 100 } catch { - Write-Output 'Unable to read file exitin' + write-output 'Unable to read file exitin' } -Write-Output $player_matches -Write-Output $new_win_matches -$new_win_matches = $player_matches[-1].new_win_matches +write-output $player_matches +write-output $new_win_matches +$new_win_matches = $player_matches[-2].new_win_matches # Gebruik nu de lijst van nieuwe verloren matches uit het JSON-bestand $new_loss_matches = $player_matches[-1].new_loss_matches # Post verloren matches naar #losers kanaal -foreach ($lossid in $new_loss_matches) { - $lossmatch = $player_matches.player_matches | Where-Object { $_.id -eq $lossid } - if ($null -eq $lossmatch) { continue } - if ($lossmatch[0].gameMode -eq 'tdm') { continue } +# foreach ($lossid in $new_loss_matches) { +# $lossmatch = $player_matches.player_matches | Where-Object { $_.id -eq $lossid } +# if ($null -eq $lossmatch) { continue } +# if ($lossmatch[0].gameMode -eq 'tdm') { continue } - # Fetch detailed match stats and telemetry for the loss - $loss_match_stats = $null - $loss_telemetry = $null - try { - $loss_match_stats = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/matches/$lossid" -Method GET -Headers $headers - $loss_telemetry = (invoke-webrequest @($lossmatch.telemetry_url)[0]).content | convertfrom-json | where-object { ($_._T -eq 'LOGPLAYERTAKEDAMAGE') -or ($_._T -eq 'LOGPLAYERKILLV2') } - } catch { - $errorMessage = $_.Exception.Message - Write-Warning ("Failed to fetch API/telemetry data for loss match {0}: {1}" -f $lossid, $errorMessage) - } +# # Fetch detailed match stats and telemetry for the loss +# $loss_match_stats = $null +# $loss_telemetry = $null +# try { +# $loss_match_stats = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/matches/$lossid" -Method GET -Headers $headers +# $loss_telemetry = (invoke-webrequest @($lossmatch.telemetry_url)[0]).content | convertfrom-json | where-object { ($_._T -eq 'LOGPLAYERTAKEDAMAGE') -or ($_._T -eq 'LOGPLAYERKILLV2') } +# } catch { +# $errorMessage = $_.Exception.Message +# Write-Warning ("Failed to fetch API/telemetry data for loss match {0}: {1}" -f $lossid, $errorMessage) +# } - $loss_stats_table = @() - $loss_victims = @() # For team damage +# $loss_stats_table = @() +# $loss_victims = @() # For team damage - # Iterate through players found in the locally stored match data for this loss - foreach ($player_stat in $lossmatch[0].stats) { - $player_name = $player_stat.name - # Find the corresponding detailed stats from the API response - $detailed_player_stats = $null - if ($null -ne $loss_match_stats) { - $detailed_player_stats = $loss_match_stats.included | Where-Object {$_.type -eq 'participant'} | ForEach-Object {$_.attributes.stats} | Where-Object { $_.name -eq $player_name } - } +# # Iterate through players found in the locally stored match data for this loss +# foreach ($player_stat in $lossmatch[0].stats) { +# $player_name = $player_stat.name +# # Find the corresponding detailed stats from the API response +# $detailed_player_stats = $null +# if ($null -ne $loss_match_stats) { +# $detailed_player_stats = $loss_match_stats.included | Where-Object {$_.type -eq 'participant'} | ForEach-Object {$_.attributes.stats} | Where-Object { $_.name -eq $player_name } +# } - if ($null -eq $detailed_player_stats) { - Write-Warning "Could not find detailed stats for player $player_name in loss match $lossid. Using basic stats." - # Fallback to basic stats if detailed stats are missing - $loss_stats_table += [PSCustomObject]@{ - Name = $player_name - 'Human dmg' = "N/A" - 'Human Kills' = "N/A" - 'Dmg' = "$([math]::Round($player_stat.damageDealt))" # Use basic stat - 'Kills' = "$($player_stat.kills)" # Use basic stat - 'alive' = "$([math]::Round(($player_stat.timeSurvived / 60)))" # Use basic stat - } - continue # Skip telemetry processing if detailed stats failed - } +# if ($null -eq $detailed_player_stats) { +# Write-Warning "Could not find detailed stats for player $player_name in loss match $lossid. Using basic stats." +# # Fallback to basic stats if detailed stats are missing +# $loss_stats_table += [PSCustomObject]@{ +# Name = $player_name +# 'Human dmg' = "N/A" +# 'Human Kills' = "N/A" +# 'Dmg' = "$([math]::Round($player_stat.damageDealt))" # Use basic stat +# 'Kills' = "$($player_stat.kills)" # Use basic stat +# 'alive' = "$([math]::Round(($player_stat.timeSurvived / 60)))" # Use basic stat +# } +# continue # Skip telemetry processing if detailed stats failed +# } - # Calculate stats (similar to win stats calculation) - $human_dmg = "N/A" - $human_kills = "N/A" - if ($null -ne $loss_telemetry) { - try { - $human_dmg = [math]::Round(($loss_telemetry | Where-Object { $_._T -eq 'LOGPLAYERTAKEDAMAGE' -and $_.attacker.name -eq $player_name -and $_.victim.accountId -notlike "ai.*" -and $_.victim.teamId -ne $_.attacker.teamId } | Measure-Object -Property damage -Sum).Sum) - $human_kills = ($loss_telemetry | Where-Object { $_._T -eq 'LOGPLAYERKILLV2' -and $_.killer.name -eq $player_name -and $_.victim.accountId -notlike "ai.*" }).count - } catch { - $errorMessage = $_.Exception.Message - Write-Warning ("Error processing telemetry stats for {0} in loss {1}: {2}" -f $player_name, $lossid, $errorMessage) - } - } +# # Calculate stats (similar to win stats calculation) +# $human_dmg = "N/A" +# $human_kills = "N/A" +# if ($null -ne $loss_telemetry) { +# try { +# $human_dmg = [math]::Round(($loss_telemetry | Where-Object { $_._T -eq 'LOGPLAYERTAKEDAMAGE' -and $_.attacker.name -eq $player_name -and $_.victim.accountId -notlike "ai.*" -and $_.victim.teamId -ne $_.attacker.teamId } | Measure-Object -Property damage -Sum).Sum) +# $human_kills = ($loss_telemetry | Where-Object { $_._T -eq 'LOGPLAYERKILLV2' -and $_.killer.name -eq $player_name -and $_.victim.accountId -notlike "ai.*" }).count +# } catch { +# $errorMessage = $_.Exception.Message +# Write-Warning ("Error processing telemetry stats for {0} in loss {1}: {2}" -f $player_name, $lossid, $errorMessage) +# } +# } - $loss_stats_table += [PSCustomObject]@{ - Name = $player_name - 'Human dmg' = "$human_dmg" - 'Human Kills' = "$human_kills" - 'Dmg' = "$([math]::Round($detailed_player_stats.damageDealt))" - 'Kills' = "$($detailed_player_stats.kills)" - 'alive' = "$([math]::Round(($detailed_player_stats.timeSurvived / 60)))" - } +# $loss_stats_table += [PSCustomObject]@{ +# Name = $player_name +# 'Human dmg' = "$human_dmg" +# 'Human Kills' = "$human_kills" +# 'Dmg' = "$([math]::Round($detailed_player_stats.damageDealt))" +# 'Kills' = "$($detailed_player_stats.kills)" +# 'alive' = "$([math]::Round(($detailed_player_stats.timeSurvived / 60)))" +# } - # Calculate team damage - if ($null -ne $loss_telemetry) { - try { - $teamdmg = $loss_telemetry | Where-Object { - $_._T -eq 'LOGPLAYERTAKEDAMAGE' -and - $_.victim.teamId -eq $_.attacker.teamId -and - $_.victim.accountId -notlike "ai.*" -and - $_.victim.name -ne $_.attacker.name -and - $_.attacker.name -eq $player_name - } - if ($teamdmg.count -ge 1) { - foreach ($victim_name in ($teamdmg.victim.name | Select-Object -Unique)) { - $loss_victims += [PSCustomObject]@{ - attacker = $player_name - victim = $victim_name - Damage = "$([math]::Round((($teamdmg | Where-Object { $_.victim.name -eq $victim_name }).damage | Measure-Object -Sum).Sum))" - } - } - } - } catch { - $errorMessage = $_.Exception.Message - Write-Warning ("Error processing team damage for {0} in loss {1}: {2}" -f $player_name, $lossid, $errorMessage) - } - } - } +# # Calculate team damage +# if ($null -ne $loss_telemetry) { +# try { +# $teamdmg = $loss_telemetry | Where-Object { +# $_._T -eq 'LOGPLAYERTAKEDAMAGE' -and +# $_.victim.teamId -eq $_.attacker.teamId -and +# $_.victim.accountId -notlike "ai.*" -and +# $_.victim.name -ne $_.attacker.name -and +# $_.attacker.name -eq $player_name +# } +# if ($teamdmg.count -ge 1) { +# foreach ($victim_name in ($teamdmg.victim.name | Select-Object -Unique)) { +# $loss_victims += [PSCustomObject]@{ +# attacker = $player_name +# victim = $victim_name +# Damage = "$([math]::Round((($teamdmg | Where-Object { $_.victim.name -eq $victim_name }).damage | Measure-Object -Sum).Sum))" +# } +# } +# } +# } catch { +# $errorMessage = $_.Exception.Message +# Write-Warning ("Error processing team damage for {0} in loss {1}: {2}" -f $player_name, $lossid, $errorMessage) +# } +# } +# } - # Format the stats table - $content_lossstats = "" - if ($loss_stats_table.Count -gt 0) { - $content_lossstats = '```' + ($loss_stats_table | Format-Table -AutoSize | Out-String) + '```' - } +# # Format the stats table +# $content_lossstats = "" +# if ($loss_stats_table.Count -gt 0) { +# $content_lossstats = '```' + ($loss_stats_table | Format-Table -AutoSize | Out-String) + '```' +# } - # Format team damage table - $content_loss_victims = "" - if ($loss_victims.Count -gt 0) { - $content_loss_victims = ":skull::skull: Team Damage :skull::skull:`n" + '```' + ($loss_victims | Format-Table -AutoSize | Out-String) + '```' - } +# # Format team damage table +# $content_loss_victims = "" +# if ($loss_victims.Count -gt 0) { +# $content_loss_victims = ":skull::skull: Team Damage :skull::skull:`n" + '```' + ($loss_victims | Format-Table -AutoSize | Out-String) + '```' +# } - # Original message construction variables - $losers = $lossmatch[0].stats.name -join ', ' # Join names for display - $map = $map_map[$lossmatch[0].mapName] - $place = ($lossmatch[0].stats | Select-Object -First 1).winPlace # Get placement from the first player stat - $first_player_name = ($lossmatch[0].stats | Select-Object -First 1).name - $replay_url = $lossmatch[0].telemetry_url -replace 'https://telemetry-cdn.pubg.com/bluehole-pubg', 'https://chickendinner.gg' - $replay_url = $replay_url -replace '-telemetry.json', '' - $replay_url = $replay_url + "?follow=$first_player_name" # Follow the first player - $match_settings = @" -`````` -match mode $($lossmatch[0].gameMode) -match type $($lossmatch[0].matchType) -map $($map_map[$lossmatch[0].mapName]) -id $($lossmatch[0].id) -`````` -"@ - send-discord-losers -content "We hebben een LOSERT! Geen Kip voor jou! :skull::skull:" - send-discord-losers -content ":partying_face::partying_face::partying_face: Helaas, $($losers) :partying_face::partying_face::partying_face:" - send-discord-losers -content $match_settings - send-discord-losers -content $content_lossstats - send-discord-losers -content $content_loss_victims - send-discord-losers -content "[2D replay](<$replay_url>)" - send-discord-losers -content "Meer match details [DTCH_STATS]()" -} +# # Original message construction variables +# $losers = $lossmatch[0].stats.name -join ', ' # Join names for display +# $map = $map_map[$lossmatch[0].mapName] +# $place = ($lossmatch[0].stats | Select-Object -First 1).winPlace # Get placement from the first player stat +# $first_player_name = ($lossmatch[0].stats | Select-Object -First 1).name +# $replay_url = $lossmatch[0].telemetry_url -replace 'https://telemetry-cdn.pubg.com/bluehole-pubg', 'https://chickendinner.gg' +# $replay_url = $replay_url -replace '-telemetry.json', '' +# $replay_url = $replay_url + "?follow=$first_player_name" # Follow the first player +# $match_settings = @" +# `````` +# match mode $($lossmatch[0].gameMode) +# match type $($lossmatch[0].matchType) +# map $($map_map[$lossmatch[0].mapName]) +# id $($lossmatch[0].id) +# `````` +# "@ +# send-discord-losers -content "We hebben een LOSERT! Geen Kip voor jou! :skull::skull:" +# send-discord-losers -content ":partying_face::partying_face::partying_face: Helaas, $($losers) :partying_face::partying_face::partying_face:" +# send-discord-losers -content $match_settings +# send-discord-losers -content $content_lossstats +# send-discord-losers -content $content_loss_victims +# send-discord-losers -content "[2D replay](<$replay_url>)" +# send-discord-losers -content "Meer match details [DTCH_STATS]()" +# } foreach ($winid in $new_win_matches) { @@ -237,13 +245,20 @@ foreach ($winid in $new_win_matches) { $winmatches = $player_matches.player_matches | Where-Object { $_.id -eq $winid } $telemetry = (invoke-webrequest @($winmatches.telemetry_url)[0]).content | convertfrom-json | where-object { ($_._T -eq 'LOGPLAYERTAKEDAMAGE') -or ($_._T -eq 'LOGPLAYERKILLV2') } $winners = @(($winmatches | where-object { $_.stats.winPlace -eq 1 }).stats.name) - $2D_replay_url = @($winmatches.telemetry_url)[0] -replace 'https://telemetry-cdn.pubg.com/bluehole-pubg', 'https://chickendinner.gg' - $2D_replay_url = $2D_replay_url -replace '-telemetry.json', '' - $2D_replay_url = $2D_replay_url + "?follow=$($winners[0])" - + # $2D_replay_url = @($winmatches.telemetry_url)[0] -replace 'https://telemetry-cdn.pubg.com/bluehole-pubg', 'https://chickendinner.gg' + # $2D_replay_url = $2D_replay_url -replace '-telemetry.json', '' + # $2D_replay_url = $2D_replay_url + "?follow=$($winners[0])" + $2D_replay_url = 'https://chickendinner.gg/' + $winid $match_stats = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/matches/$winid" -Method GET -Headers $headers if ($winmatches[0].gameMode -eq 'tdm' ) { continue + } + if ( + ($winmatches[0].matchtype -eq 'event' -and $winmatches[0].gameMode -ne 'ibr') -or + ($winmatches[0].gameMode -eq 'tdm') + ) { + Write-Output 'Skipping because of event or tdm' + continue } #skip tdm matches if ($winmatches[0].matchType -eq 'custom') { $players_to_report = $match_stats.included.attributes.stats | where-object { $_.playerId -notlike "ai.*" } @@ -251,11 +266,15 @@ foreach ($winid in $new_win_matches) { else { $players_to_report = $match_stats.included.attributes.stats | where-object { $_.winplace -eq 1 } } - + $2D_replay_url = 'https://chickendinner.gg/' + $winid + "/" + $players_to_report[0].name if ($new_win_matches.count -le 10) { #fail safe + $winnerswithurl = @() + foreach ($winner in $winners) { + $winnerswithurl += "[$winner]()" + } send-discord -content ":chicken: :chicken: **WINNER WINNER CHICKEN DINNER!!** :chicken: :chicken:" - send-discord -content ":partying_face::partying_face::partying_face: Gefeliciteerd $($winners -join ', ') :partying_face::partying_face::partying_face:" + send-discord -content ":partying_face::partying_face::partying_face: Gefeliciteerd $($winnerswithurl -join ', ') :partying_face::partying_face::partying_face:" $match_settings = @" `````` match mode $($winmatches[0].gameMode) @@ -336,6 +355,8 @@ k_t = Team eliminaties foreach ($item in $player_matches) { if ($item.PSObject.Properties.Name -contains "new_win_matches") { $item.new_win_matches = $null + } + if ($item.PSObject.Properties.Name -contains "new_loss_matches") { $item.new_loss_matches = $null } } @@ -347,4 +368,5 @@ $newJson = $player_matches | ConvertTo-Json -Depth 100 $newJson | out-file "$scriptroot/../data/player_matches.json" remove-lock -Stop-Transcript \ No newline at end of file +Stop-Transcript + diff --git a/discord/teammakerv2.py b/discord/teammakerv2.py index ff7858e..105c30a 100644 --- a/discord/teammakerv2.py +++ b/discord/teammakerv2.py @@ -4,9 +4,11 @@ import discord import random import asyncio import re +from textwrap import dedent from discord.ext import commands +from openai import OpenAI - +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") def get_token(): with open("config.php", "r") as file: content = file.read() @@ -29,10 +31,13 @@ intents.presences = True # Nodig als de bot presences moet zien intents.members = True # Nodig om leden in een voice channel te zien bot = commands.Bot(command_prefix="!", intents=intents) - +client = OpenAI(api_key=OPENAI_API_KEY) @bot.event async def on_ready(): print(f'Bot is ingelogd als {bot.user}') + channel = bot.get_channel(759006368832159745) + if channel: + await channel.send("Ben er weer!") @bot.command() async def test(ctx): @@ -125,19 +130,19 @@ async def teamify(ctx, *args): await ctx.send(f"Kanaal {channel.name} is opgeruimd omdat het leeg was!") @bot.command() -async def whoisbest(ctx, category="Casual", matchesback=18): +async def whoisbest(ctx, category="Casual", matchesback=18, top=3): if category.lower() == "help": help_message = ( "**Gebruik van het commando `whoisbest`:**\n" - "`!whoisbest [category] [matchesback]`\n\n" + "`!whoisbest [category] [matchesback] [top]`\n\n" "**Parameters:**\n" "`category` - De categorie van de stats, bijv. 'Casual' of 'Ranked'. Niet hoofdlettergevoelig.\n" "`matchesback` - Het minimum aantal matches dat een speler gespeeld moet hebben om mee te tellen (standaard 18).\n\n" "**Voorbeeld:**\n" "`!whoisbest Casual 18`\n" - "Laat de top 3 spelers zien op basis van winratio en gemiddelde damage in de Casual categorie met minimaal 18 matches.\n\n" + "Laat de top 3 spelers zien op basis van winratio en gemiddelde damage in de Casual categorie met minimaal 18 matches.\n" "Typ `!whoisbest help` om deze uitleg opnieuw te zien." ) await ctx.send(help_message) @@ -173,10 +178,10 @@ async def whoisbest(ctx, category="Casual", matchesback=18): return # Sorteer spelers op winratio (aflopend) - top_winratio = sorted(players, key=lambda x: x.get("winratio", 0), reverse=True)[:3] + top_winratio = sorted(players, key=lambda x: x.get("winratio", 0), reverse=True)[:top] # Sorteer spelers op gemiddelde damage (aflopend) - top_ahd = sorted(players, key=lambda x: x.get("ahd", 0), reverse=True)[:3] + top_ahd = sorted(players, key=lambda x: x.get("ahd", 0), reverse=True)[:top] # Bouw het bericht op message = f"**\U0001F3C6 Top 3 Winratio ({actual_category})**\n" @@ -186,11 +191,55 @@ async def whoisbest(ctx, category="Casual", matchesback=18): message += f"\n**\U0001F480 Top 3 AHD ({actual_category})**\n" for i, player in enumerate(top_ahd, start=1): message += f"{i}. **{player['playername']}** - {player['ahd']:.2f}\n" +##AI - await ctx.send(message) + system_prompt = dedent(f""" + Je bent een Discord announcer-bot op de PUBG-server van DTCH. + Stijl: brutaal/competitief, licht denigrerend maar leesbaar. + AHD = Average Human Damage + Regels: + - Gebruik uitsluitend de meegeleverde stats-tekst. + - Output ALLEEN Discord-markdown (geen JSON, geen codeblokken). + - Structuur: + 1) Titel met category en korte snedige ondertitel. + 2) **๐Ÿ† Top {top} Winratio** en **๐Ÿ’€ Top {top} AHD** (exact die koppen). + 3) Per regel: ๐Ÿฅ‡/๐Ÿฅˆ/๐Ÿฅ‰ + **naam** + waarde (winratio met %). + 4) De rest van de regels doe je zonder medaille gewoon een cijfer + 5) Sluit af met 1 of 2 regels analyse van de stats, gebruik humor. + - Opgegeven parameters: + Categorie: {category} + Minimaal aantal matches: {matchesback} + Top: {top} + - verdere info + 1) Als de aantal matches laag is (onder de 15) dan zijn cijfers niet echt meer representatief. Meld dat dan ook. + - Max ~1800 tekens. + """).strip() + user_prompt = dedent(f""" + Verpak onderstaande stats-tekst in รฉรฉn strakke Discord-post volgens de regels. + Wijzig geen waardes, haal alles uit de tekst tussen START/EINDE. + [STATS-TEKST START] + {message} + [STATS-TEKST EINDE] + """).strip() + response = client.chat.completions.create( + model="gpt-5-nano", + #temperature=0.6, + #presence_penalty=0.2, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + ) + + antwoord = response.choices[0].message.content + await ctx.send(f"{ctx.author.mention} {antwoord[:1900]}") except Exception as e: - await ctx.send(f"Fout bij het laden van de statistieken: {str(e)}") + await ctx.send(f"{ctx.author.mention} Er ging iets mis: {e}") + + +##AIEND + @bot.event async def on_voice_state_update(member, before, after): @@ -215,7 +264,15 @@ async def on_voice_state_update(member, before, after): async def on_member_join(member): logging_channel = discord.utils.get(member.guild.text_channels, name="raadhuisplein") if logging_channel: - await logging_channel.send(f"๐ŸŽ‰ Welkom {member.mention} op de server! We hopen dat je een leuke tijd hebt!") + welcome_message = ( + f"๐ŸŽ‰ Welcome {member.mention} to **{member.guild.name}**!\n\n" + "๐Ÿ‘‹ We're glad to have you here.\n" + "๐Ÿ‘‰ Want to jump right in? Type `!iamgamer` in the chat and you'll get the **Tourists** role.\n" + "With that role you can join the fun and games with everyone else. ๐ŸŽฎ\n" + "Use !dtch_help for more info\n\n" + "Enjoy your stay and have a great time! ๐Ÿš€" + ) + await logging_channel.send(welcome_message) @bot.event async def on_member_remove(member): @@ -251,5 +308,203 @@ async def moveall(ctx): await ctx.send(f"{moved_members} speler(s) zijn verplaatst naar het teamify kanaal.") else: await ctx.send("Er waren geen spelers om te verplaatsen.") - +@bot.command() +async def iamgamer(ctx): + role = discord.utils.get(ctx.guild.roles, name="Tourists") + if role is None: + await ctx.send("De rol **Tourists** bestaat niet!") + return + + try: + await ctx.author.add_roles(role) + await ctx.send(f"โœ… {ctx.author.mention}, je bent nu een **Tourist**! Veel plezier! ๐ŸŽฎ") + except Exception as e: + await ctx.send(f"Er is iets misgegaan bij het toekennen van de rol: {e}") +@bot.command(name="dtch_help", aliases=["commands"]) +async def dtch_help_command(ctx): + embed = discord.Embed( + title="๐Ÿ“– DTCH Bot Command Help", + description="Hereโ€™s a list of all available commands and how to use them:", + color=discord.Color.blue() + ) + + embed.add_field( + name="๐Ÿ‘‰ !teamify", + value=( + "`!teamify` - Split players into random teams of max 4.\n" + "`!teamify ` - Split players into a given number of teams.\n" + "`!teamify move` - Split & move players into temporary voice channels.\n" + "`!teamify move` - Auto split & move players.\n" + "๐Ÿ”น Works only in the **#teamify** channel." + ), + inline=False + ) + + embed.add_field( + name="๐Ÿ‘‰ !moveall", + value="`!moveall` - Moves all players from other voice channels into the **teamify** channel.", + inline=False + ) + + embed.add_field( + name="๐Ÿ‘‰ !whoisbest", + value=( + "`!whoisbest [category] [matchesback]`\n" + "Shows the top 3 players based on win ratio and average damage.\n" + "`category` = e.g. Casual, Ranked\n" + "`matchesback` = minimum matches required (default: 18)\n" + "Example: `!whoisbest Casual 18`" + ), + inline=False + ) + + embed.add_field( + name="๐Ÿ‘‰ !iamgamer", + value="`!iamgamer` - Gives you the **Tourists** role ๐ŸŽฎ so you can join games and unlock more fun.", + inline=False + ) + + embed.set_footer(text="โœจ For advanced options, use: !teamify help or !whoisbest help") + + await ctx.send(embed=embed) + +@bot.command() +@commands.cooldown(1, 15, commands.BucketType.user) +async def ask(ctx, *, vraag: str): + ## CLAN MEMBERS + file_path = os.path.join("..", "config", "clanmembers.json") + # Inlezen als string + with open(file_path, "r", encoding="utf-8") as f: + clanmembers_str = f.read() + + #LIFETIME STATS + + file_path_lifetimestats = os.path.join("..", "data", "player_lifetime_data.json") + with open(file_path_lifetimestats, "r", encoding="utf-8") as file: + data_lifetimestats = json.load(file) + squad_str = json.dumps(data_lifetimestats.get("squad", {}), indent=2) + + #Last stats + file_path_laststats = os.path.join("..", "data", "player_last_stats.json") + with open(file_path_laststats, "r", encoding="utf-8") as file: + data_laststat = json.load(file) + casual_str = json.dumps(data_laststat.get("Casual", []), indent=2) + ranked_str = json.dumps(data_laststat.get("Ranked", []), indent=2) + custom_str = json.dumps(data_laststat.get("custom", []), indent=2) + # Online members + voice_members = [ + member.display_name + for vc in ctx.guild.voice_channels + for member in vc.members + ] + + voice_str = ", ".join(voice_members) if voice_members else "Niemand zit in een voice kanaal" + + """Stuur een vraag naar OpenAI""" + try: + async with ctx.typing(): + response = client.chat.completions.create( + model="gpt-5-nano", + messages=[ + { + "role": "system", + "content": ( + "Je bent een licht denigrerende chatbot in Discord, actief op de PUBG server van clan DTCH. " + f"De vraag is gesteld door {ctx.author.display_name}. " + + # Context + f"Clanleden: {clanmembers_str}. " + f"Momenteel in voice: {voice_str}. " + f"Lifetime stats (squad): {squad_str}. " + f"Casual (spelen we toch wel het vaakst met elkaar, belangrijke stat dus) stats: {casual_str}. " + f"Ranked stats: {ranked_str}. " + f"Custom stats (clan games meestal tegen elkaar): {custom_str}. " + + # Richtlijnen voor antwoord + "Houd je antwoorden kort en bondig, met maximaal 1600 tekens. " + "Kill Death Human (KD_H) en winrato zijn de belangrijkste stats omdat bots heel makkelijk te killen zijn vinden de Human Kill death het belangrijkste" + "Gebruik Discord markdown (``` of **vetgedrukt**) wanneer je stats of tabellen toont. " + "Wees een beetje sarcastisch, maar wel begrijpelijk. " + "Stel geen vragen want je hebt geen context bij de volgende prompt." + + ) + }, + {"role": "user", "content": vraag}, + ], + ) + + antwoord = response.choices[0].message.content + await ctx.send(f"{ctx.author.mention} {antwoord[:1900]}") + except Exception as e: + await ctx.send(f"{ctx.author.mention} Er ging iets mis: {e}") + +@ask.error +async def ask_error(ctx, error): + if isinstance(error, commands.CommandOnCooldown): + retry_after = int(error.retry_after + 0.999) + await ctx.reply(f"Rustig {ctx.author.display_name}, probeer het over {retry_after}s nog eens.") + else: + raise error + +@bot.command() +async def loterij(ctx, *members: discord.Member): + """ + Gebruik: + !loterij @naam1 @naam2 @naam3 + """ + # Alleen in #teamify? -> uncomment als je dat ook wilt + # if ctx.channel.name != "teamify": + # await ctx.send("Dit commando kan alleen worden gebruikt in het #teamify kanaal.") + # return + + if not members or len(members) < 2: + await ctx.send("Gebruik: `!loterij @naam1 @naam2 ...` (minimaal 2 mensen, anders is het wel hรฉรฉl zielig).") + return + + # Uniek maken (als iemand 2x getagd wordt telt 'ie maar 1x mee) + unique_members = [] + seen_ids = set() + for m in members: + if m.id not in seen_ids: + unique_members.append(m) + seen_ids.add(m.id) + + # Tekst โ€œtussen A, B en Cโ€ netjes bouwen + mentions = [m.mention for m in unique_members] + if len(mentions) == 2: + between_text = f"{mentions[0]} en {mentions[1]}" + else: + between_text = f"{', '.join(mentions[:-1])} en {mentions[-1]}" + + winnaar = random.choice(unique_members) + + await ctx.send(f"En de loterij gaat tussen {between_text}.") + + # Countdown + await asyncio.sleep(1) + await ctx.send("in 3") + await asyncio.sleep(1) + await ctx.send("2") + await asyncio.sleep(1) + await ctx.send("1") + await asyncio.sleep(1) + + await ctx.send("trom gerofel ๐Ÿฅ") + await asyncio.sleep(1) + + await ctx.send(f"De winnaar is {winnaar.mention} ๐ŸŽ†๐ŸŽ‡") + +@loterij.error +async def loterij_error(ctx, error): + if isinstance(error, commands.BadArgument): + await ctx.send( + "Ik snap er niks van ๐Ÿคจ\n" + "Gebruik het zo:\n" + "`!loterij @naam1 @naam2 @naam3`\n" + "Zorg dat je **echte Discord-mentions** gebruikt." + ) + else: + raise error + + bot.run(token) diff --git a/includes/navigation.php b/includes/navigation.php index 0ceb42b..15fddc2 100644 --- a/includes/navigation.php +++ b/includes/navigation.php @@ -12,7 +12,8 @@ if ($host == 'dev.dtch.online') { Last quarter % Last Matches Top20 - User Stats + User Stats + Videos diff --git a/includes/styles.css b/includes/styles.css index 6ba4400..e022ed1 100644 --- a/includes/styles.css +++ b/includes/styles.css @@ -240,3 +240,87 @@ td, th { background-color: #0f0f0f; color: #e69109; } + +/* Videos page styles */ +.videos-container { + display: flex; + flex-wrap: wrap; + gap: 16px; + justify-content: center; + padding: 1rem; +} +.video-item { + flex: 1 1 300px; + max-width: 100%; +} +.video-item video { + max-width: 100%; + max-height: 80vh; + width: auto; + height: auto; + object-fit: contain; + display: block; + margin: 0 auto; +} + +/* Share and theatre button styles */ +.video-controls { + display: flex; + justify-content: center; + align-items: center; + margin-top: 0.5rem; + gap: 0.5rem; +} + +/* Theatre mode styles */ +.video-item.theatre-mode { + flex: 1 1 100%; + max-width: 100%; +} +.video-item.theatre-mode video { + width: 100%; + height: auto; + max-height: none; +} + +.video-title { + text-align: center; + font-size: 1.2em; + font-weight: bold; + margin-top: 10px; + margin-bottom: 10px; +} + +.video-item.theatre-mode { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.9); + z-index: 1000; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + box-sizing: border-box; +} + +.video-item.theatre-mode video { + max-width: 90%; + max-height: 80vh; + width: auto; + height: auto; +} + +.video-item.theatre-mode .video-title { + color: #fff; + order: -1; /* Display title above the video */ +} + +.video-item.theatre-mode .video-controls { + position: relative; + bottom: auto; + margin-top: 15px; +} diff --git a/includes/styles_mobile.css b/includes/styles_mobile.css index 70bdf9e..dbea823 100644 --- a/includes/styles_mobile.css +++ b/includes/styles_mobile.css @@ -5,6 +5,16 @@ table { td, th { padding: 8px; } +/* Theatre mode mobile styles */ +.video-item.theatre-mode { + flex: 1 1 100%; + max-width: 100%; +} +.video-item.theatre-mode video { + width: 100%; + height: auto; + max-height: none; +} .topnav { overflow: hidden; @@ -81,4 +91,50 @@ table { section h2 { margin-top: 10px; +} +/* Videos page responsive adjustments */ +.videos-container { + display: flex; + flex-wrap: wrap; + gap: 16px; + justify-content: center; + padding: 1rem; +} +.video-item { + flex: 1 1 100%; + max-width: 100%; +} +.video-item video { + width: 100%; + max-width: 100%; + height: auto; + display: block; + margin: 0 auto; +} +/* Share and theatre button mobile styles */ +.video-controls { + display: flex; + justify-content: center; + align-items: center; + margin-top: 0.5rem; + gap: 0.5rem; +} +.video-title { + text-align: center; + font-size: 1.1em; + font-weight: bold; + margin-top: 8px; + margin-bottom: 8px; +} +.video-item.theatre-mode { + padding: 10px; +} + +.video-item.theatre-mode video { + max-width: 100%; + max-height: 70vh; +} + +.video-item.theatre-mode .video-controls { + margin-top: 10px; } \ No newline at end of file diff --git a/matchinfo.php b/matchinfo.php index 0c7c879..2b3a001 100644 --- a/matchinfo.php +++ b/matchinfo.php @@ -157,15 +157,15 @@ $lastMatches = array_slice($allMatches, 0, 8); if (substr($playerStats['playerId'], 0, 2) !== 'ai') { // Create links for each stat echo ""; - echo "" . htmlspecialchars($playerStats['name']) . ""; - echo " Human "; - echo "" . htmlspecialchars($playerStats['kills']) . ""; - echo "" . htmlspecialchars($playerStats['HumanDmg']) . ""; - echo "" . htmlspecialchars($playerStats['timeSurvived']) . ""; - echo "" . htmlspecialchars($playerStats['winPlace']) . ""; - echo "" . htmlspecialchars($playerStats['revives']) . ""; - echo "" . htmlspecialchars($playerStats['DBNOs']) . ""; - echo "" . htmlspecialchars($playerStats['assists']) . ""; + echo "" . htmlspecialchars($playerStats['name']) . ""; + echo " Human "; + echo "" . htmlspecialchars($playerStats['kills']) . ""; + echo "" . htmlspecialchars($playerStats['HumanDmg']) . ""; + echo "" . htmlspecialchars($playerStats['timeSurvived']) . ""; + echo "" . htmlspecialchars($playerStats['winPlace']) . ""; + echo "" . htmlspecialchars($playerStats['revives']) . ""; + echo "" . htmlspecialchars($playerStats['DBNOs']) . ""; + echo "" . htmlspecialchars($playerStats['assists']) . ""; echo ""; } else { // Display without link diff --git a/update/get_matches.ps1 b/update/get_matches.ps1 index 35e5d5f..d999ea6 100644 --- a/update/get_matches.ps1 +++ b/update/get_matches.ps1 @@ -48,7 +48,13 @@ foreach ($player in $player_data) { $stats.included = $sortedStats $stats | ConvertTo-Json -Depth 100 | Out-File "$scriptroot/../data/matches/$match.json" } - + if ( + ($stats.data.attributes.matchtype -eq 'event' -and $stats.data.attributes.gameMode -ne 'ibr') -or + ($stats.data.attributes.gameMode -eq 'tdm') + ) { + Write-Output 'Skipping because of event or tdm' + continue + } $playermatches += [PSCustomObject]@{ stats = $stats.included.ATTRIBUTES.stats | where-object { $_.name -eq $player.attributes.name } matchType = $stats.data.attributes.matchtype diff --git a/update/matchparser.ps1 b/update/matchparser.ps1 index 85b1ea7..f77dc3c 100644 --- a/update/matchparser.ps1 +++ b/update/matchparser.ps1 @@ -161,6 +161,14 @@ $groupedGuids_clan_matches_gt_3 = $guids | Group-Object | Where-Object { $_.Coun $last_month = (get-date).AddMonths($monthsback) foreach ($file in $matchfiles) { $json = get-content $file | ConvertFrom-Json + + if ( + ($stats.data.attributes.matchtype -eq 'event' -and $stats.data.attributes.gameMode -ne 'ibr') -or + ($stats.data.attributes.gameMode -eq 'tdm') + ) { + Write-Output 'Skipping because of event or tdm' + continue + } if ($json.created -gt $last_month) { $killstats += $json if ($groupedGuids_clan_matches_gt_1.Name -contains $json.matchid) { diff --git a/videos.php b/videos.php new file mode 100644 index 0000000..113215e --- /dev/null +++ b/videos.php @@ -0,0 +1,102 @@ + $file, + 'ctime' => filectime($path) + ]; +} +usort($videoData, function($a, $b) { + return $b['ctime'] - $a['ctime']; +}); +?> + + + + + + + + +
+
+

Videos

+ + +
+ +
+ +

+
+ + +
+
+ +
+ +

No videos found.

+ + +
+
+ + + + + + \ No newline at end of file