Files
kurwa-strona/bot.py
Bartłomiej Patyk e5e64b6dc8 quick fix 2
2025-10-22 19:05:25 +02:00

742 lines
29 KiB
Python

import discord
from discord.ext import commands, tasks
import random
import os
from glob import glob
import time
import logging
import pyodbc # type: ignore
import re
from datetime import datetime
# --- KONFIGURACJA BAZY DANYCH ACCESS ---
ACCESS_DB_PATH = 'C:/Users/marek_yrlsweo/Documents/furrystatus/bot/playcount.accdb'
# Użyj odpowiedniego sterownika ODBC dla Twojej wersji Office/Windows
ODBC_DRIVER = '{Microsoft Access Driver (*.mdb, *.accdb)}'
# Globalna zmienna na połączenie z bazą
db_connection = None
# --- KONFIGURACJA ---
MUSIC_DIR = 'C:/Users/marek_yrlsweo/Documents/furrystatus/bot/music' # Nazwa folderu z plikami muzycznymi
FILE_EXT = 'flac' # Rozszerzenie plików (możesz dodać 'ogg', 'mp3' itp.)
ADS_DIR = 'C:/Users/marek_yrlsweo/Documents/furrystatus/bot/ads' # NOWA ZMIENNA: Folder z reklamami
AD_CHANCE = 10 # NOWA ZMIENNA: Szansa na reklamę w %
PREFIX = 'frog!' # Prefiks komend
FFMPEG_EXE_PATH = r'C:/FFmpeg/bin/ffmpeg.exe'
tocen = "MTM3OTA2ODIyNDk0NzA5MzUzNg.GuSMW3.OzhV0eLNLSeEZvyoEfMkXxIckHUwp_b12p8wvc"
FFMPEG_OPTIONS = {
# -i: Wskaźnik wejściowego strumienia (podajemy go w play_next)
# -f opus: Zmusza FFmpeg do użycia kodeka Opus (zalecany przez Discorda)
# -ac 2: Ustawia dwa kanały (stereo)
# -ar 48000: Ustawia częstotliwość próbkowania na 48kHz (standard Discorda)
# -b:a 192k: Ustawia bitrate audio na 192 kbps (najwyższy dozwolony)
'options': '-vn -filter:a "volume=0.5" -f s16le -ar 48000 -ac 2', # s16le dla PCM
# before_options może zawierać argumenty specyficzne dla źródła,
# ale w przypadku lokalnych plików FLAC, te domyślne są wystarczające.
}
# Uwaga: Discord.py automatycznie zajmie się większością tych opcji,
# ale możemy wymusić bitrate, używając FFMPEG_OPUS w połączeniu z ffmpeg_before_args.
# Przykładowy, bardziej agresywny zestaw dla Opus (jeśli użylibyśmy FFmpegOpusAudio):
# W przypadku FFmpegPCMAudio, używamy PCM (s16le), więc bitrate jest stały.
# Najważniejsze jest, abyś używał FFmpegOpusAudio, jeśli chcesz dostosować bitrate (patrz sekcja 2).
# Ustawienie Intencji (Intents)
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.voice_states = True
start = time.time()
LOG_CHANNEL_ID = 1425511034482982943
bot = commands.Bot(
command_prefix=PREFIX,
intents=intents,
# === KLUCZOWA ZMIANA ===
help_command=None
# ========================
)
bot.current_track_is_ad = False
bot.is_looping_queue = True # Flaga dla pętli listy (jak wcześniej)
bot.is_looping_track = False
# Lista plików muzycznych
music_files = []
ad_files = []
HELP_PAGES = {
"🎧 Muzyka": [
"`!play_loop` - Zaczyna odtwarzać muzykę w pętli (losowo/kolejno).",
"`!skip` - Pomija bieżący utwór i przechodzi do następnego.",
"`!repeat` - Przełącza powtarzanie obecnie odtwarzanego utworu.",
"`!loop` - Przełącza pętlę listy odtwarzania (losowe utwory).",
"`!leave` - Rozłącza bota, ale resetuje timer reklam."
],
"📊 Statystyki": [
"`!stats` - Pokazuje TOP 10 najczęściej odtwarzanych utworów.",
"`!debug` - Wyświetla informacje o statusie bota (wewnętrzne)."
],
"🛠️ Administracja": [
f"`{PREFIX}help` - Wyświetla to menu pomocy."
]
}
PAGE_KEYS = list(HELP_PAGES.keys())
# Klasa, która tworzy i zarządza widokiem (przyciskami)
class HelpView(discord.ui.View):
def __init__(self, ctx, timeout=60):
super().__init__(timeout=timeout)
self.ctx = ctx
self.current_page = 0
self.message = None # Do przechowywania wiadomości do edycji
# --- FUNKCJE POMOCNICZE ---
# Funkcja generująca obiekt Embed dla bieżącej strony
def create_embed(self):
category = PAGE_KEYS[self.current_page]
description_lines = HELP_PAGES[category]
embed = discord.Embed(
title=f"🐸 Komendy Bota: {category}",
description="\n".join(description_lines),
color=discord.Color.blue()
)
embed.set_footer(text=f"Strona {self.current_page + 1} z {len(PAGE_KEYS)} | Wygasa za 60s.")
return embed
# Funkcja aktualizująca stan przycisków
def update_buttons(self):
# Dezaktywuje przycisk "Wstecz" na pierwszej stronie
self.children[0].disabled = (self.current_page == 0)
# Dezaktywuje przycisk "Dalej" na ostatniej stronie
self.children[1].disabled = (self.current_page == len(PAGE_KEYS) - 1)
# --- PRZYCISKI ---
# Przycisk "Wstecz" (Pierwszy przycisk w widoku)
@discord.ui.button(label="⬅️ Wstecz", style=discord.ButtonStyle.secondary)
async def previous_button(self, interaction: discord.Interaction, button: discord.ui.Button):
# Sprawdzenie, czy kliknął autoryzowany użytkownik (ten, który użył komendy)
if interaction.user != self.ctx.author:
await interaction.response.send_message("❌ Tylko osoba, która wywołała tę komendę, może klikać!", ephemeral=True)
return
self.current_page -= 1
self.update_buttons()
# Edytuj wiadomość nowym embedem i zaktualizowanymi przyciskami
await interaction.response.edit_message(embed=self.create_embed(), view=self)
# Przycisk "Dalej" (Drugi przycisk w widoku)
@discord.ui.button(label="Dalej ➡️", style=discord.ButtonStyle.secondary)
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user != self.ctx.author:
await interaction.response.send_message("❌ Tylko osoba, która wywołała tę komendę, może klikać!", ephemeral=True)
return
self.current_page += 1
self.update_buttons()
await interaction.response.edit_message(embed=self.create_embed(), view=self)
# Przycisk "Zamknij" (Trzeci przycisk w widoku)
@discord.ui.button(label="Zakończ", style=discord.ButtonStyle.danger)
async def close_button(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user != self.ctx.author:
await interaction.response.send_message("❌ Tylko osoba, która wywołała tę komendę, może klikać!", ephemeral=True)
return
# Usuń przyciski i zmień kolor/tytuł, aby zaznaczyć koniec sesji
new_embed = self.create_embed()
new_embed.title = f"🐸 Komendy Bota: Zakończono"
new_embed.color = discord.Color.dark_grey()
await interaction.response.edit_message(embed=new_embed, view=None) # view=None usuwa przyciski
self.stop() # Zakończ oczekiwanie na timeout
# Co się dzieje po wygaśnięciu czasu (timeout)
async def on_timeout(self) -> None:
if self.message:
new_embed = self.message.embeds[0]
new_embed.title = f"🐸 Komendy Bota: Sesja Wygasła"
new_embed.color = discord.Color.dark_grey()
# Używamy edit zamiast interaction.response.edit_message, bo nie mamy interakcji
await self.message.edit(embed=new_embed, view=None)
class DiscordHandler(logging.Handler):
def __init__(self, bot, channel_id, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bot = bot
self.channel_id = channel_id
def emit(self, record):
# Wysyłanie logów jest operacją asynchroniczną, musi być uruchomiona w pętli zdarzeń
log_entry = self.format(record)
# Ograniczenie długości wiadomości Discorda
if len(log_entry) > 2000:
log_entry = log_entry[:1990] + '...'
# Utworzenie zadania do wysłania wiadomości
async def send_log():
try:
channel = self.bot.get_channel(self.channel_id)
if channel:
# Wysyłamy log w bloku kodu, aby był czytelny (Markdown)
await channel.send(f'```\n{log_entry}\n```')
except Exception as e:
# Jeśli wystąpi błąd podczas wysyłania loga (np. brak połączenia)
print(f"Błąd podczas wysyłania loga na Discorda: {e}")
# Używamy bot.loop do uruchomienia asynchronicznej funkcji
if self.bot.is_ready():
self.bot.loop.create_task(send_log())
def setup_logging(bot):
"""Konfiguruje standardowe logowanie i dodaje handler Discorda."""
# 1. Konfiguracja logowania do konsoli (jak dotąd)
# Ustawiamy poziom WARNING, aby nie zalewało konsoli
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(name)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
# 2. Utworzenie instancji Handlera dla Discorda
discord_handler = DiscordHandler(bot, LOG_CHANNEL_ID)
# Ustawienie poziomu dla Discorda na ERROR lub CRITICAL, aby wysyłać tylko poważne błędy
discord_handler.setLevel(logging.ERROR)
# Ustawienie formatu dla wiadomości Discorda
formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s')
discord_handler.setFormatter(formatter)
#drugi
discord_handler1 = DiscordHandler(bot, LOG_CHANNEL_ID)
# Ustawienie poziomu dla Discorda na ERROR lub CRITICAL, aby wysyłać tylko poważne błędy
discord_handler1.setLevel(logging.INFO)
# Ustawienie formatu dla wiadomości Discorda
formatter1 = logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s')
discord_handler1.setFormatter(formatter1)
# 3. Dodanie Handlera do głównego loggera discord.py
logger = logging.getLogger('discord')
logger.addHandler(discord_handler)
logger.addHandler(discord_handler1)
print("Konfiguracja logowania Discord zakończona. Błędy będą wysyłane na kanał.")
def get_music_files():
"""Wyszukuje pliki muzyczne w folderze MUSIC_DIR."""
global music_files
# Używamy glob do znalezienia wszystkich plików pasujących do wzorca
# np. 'music/*.flac'
music_files = glob(os.path.join(MUSIC_DIR, f'*.{FILE_EXT}'))
if not music_files:
print(f"Brak plików .{FILE_EXT} w folderze '{MUSIC_DIR}'. Sprawdź ścieżkę i rozszerzenie.")
else:
print(f"Znaleziono {len(music_files)} plików muzycznych.")
def get_ad_files():
"""Wyszukuje pliki reklamowe w folderze ADS_DIR."""
global ad_files
# Używamy glob do znalezienia wszystkich plików pasujących do wzorca
ad_files = glob(os.path.join(ADS_DIR, f'*.{FILE_EXT}'))
if not ad_files:
print(f"Brak plików .{FILE_EXT} w folderze '{ADS_DIR}'. Reklamy będą ignorowane.")
else:
print(f"Znaleziono {len(ad_files)} plików reklamowych.")
def clean_track_name(file_path):
# Pobierz samą nazwę pliku (np. 'Nazwa [ID].flac')
file_name = os.path.basename(file_path)
# 1. Usuń rozszerzenie (.flac, .mp3 itp.)
name_without_ext = os.path.splitext(file_name)[0]
# 2. Usuń zawartość w nawiasach kwadratowych (np. [ID])
# Używamy wyrażenia regularnego: \[.*?\] znajduje i usuwa tekst między [...]
cleaned_name = re.sub(r'\s*\[.*?\]', '', name_without_ext).strip()
return cleaned_name
def update_play_count(file_to_play):
# 1. KONFIGURACJA POŁĄCZENIA LOKALNIE
conn = None
conn_str = (
f'DRIVER={ODBC_DRIVER};' # Upewnij się, że ODBC_DRIVER jest zdefiniowany globalnie
f'DBQ={ACCESS_DB_PATH};' # Upewnij się, że ACCESS_DB_PATH jest zdefiniowany globalnie
)
try:
# OTWIERANIE POŁĄCZENIA LOKALNIE
conn = pyodbc.connect(conn_str)
cursor = conn.cursor()
# Używamy PEŁNEJ ŚCIEŻKI jako klucza w bazie danych
track_key = os.path.basename(file_to_play)
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 2. LOGIKA UPDATE
update_query = """
UPDATE Odtworzenia
SET PlayCount = PlayCount + 1, LastPlayed = ?
WHERE FilePath = ?
"""
cursor.execute(update_query, current_time, track_key)
if cursor.rowcount == 0:
# 3. LOGIKA INSERT
insert_query = """
INSERT INTO Odtworzenia (FilePath, PlayCount, LastPlayed)
VALUES (?, 1, ?)
"""
cursor.execute(insert_query, track_key, current_time)
conn.commit()
except pyodbc.Error as ex:
print(f"Błąd podczas aktualizacji licznika: {ex}")
if conn:
conn.rollback()
finally:
# 4. ZAMYKANIE KURORA I POŁĄCZENIA W BLOKU FINALLY
if 'cursor' in locals() and cursor:
cursor.close()
if conn:
conn.close() # To zwolni plik .laccdb natychmiast!
@bot.event
async def on_ready():
"""Wywoływane, gdy bot jest gotowy i zalogowany."""
print(f'Zalogowano jako {bot.user.name} ({bot.user.id})')
# --- NOWA LINIA: KONFIGURACJA LOGÓW ---
setup_logging(bot)
# -------------------------------------
get_music_files()
get_ad_files()
if music_files:
# Pętla do rozpoczęcia odtwarzania
bot.loop.create_task(initial_play_start())
# Write initial status file for web UI
try:
write_bot_status()
except Exception as e:
print(f"Nie udało się zapisać bot_status.json: {e}")
def write_bot_status():
"""Zapisuje plik bot_status.json obok skryptu z liczbą serwerów i listą guildów."""
import json
try:
data = {
'guild_count': len(bot.guilds),
'guilds': [{'id': g.id, 'name': g.name} for g in bot.guilds]
}
base = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(base, 'bot_status.json')
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"Błąd podczas zapisu bot_status.json: {e}")
@bot.event
async def on_guild_join(guild):
# Update status file when the bot joins a guild
try:
write_bot_status()
except Exception as e:
print(f"Błąd on_guild_join write: {e}")
@bot.event
async def on_guild_remove(guild):
# Update status file when the bot is removed from a guild
try:
write_bot_status()
except Exception as e:
print(f"Błąd on_guild_remove write: {e}")
# Musimy poprawić logikę startu pętli, użyjemy prostego asynchronicznego startu
async def initial_play_start():
# Czekamy chwilę, aż bot całkowicie się zaloguje i połączy
await bot.wait_until_ready()
# Logika do rozpoczęcia odtwarzania, jeśli już jest na kanale
for guild in bot.guilds:
# Znajdź voice client
vc = discord.utils.get(bot.voice_clients, guild=guild)
if vc and not vc.is_playing() and music_files:
# Tworzenie tymczasowego obiektu Context do wywołania play_next
class SimpleContext:
def __init__(self, voice_client, channel):
self.voice_client = voice_client
self.channel = channel
# Wymaga znalezienia kanału tekstowego do wysyłania wiadomości.
# Użyjemy pierwszego znalezionego kanału tekstowego dla uproszczenia
text_channel = discord.utils.get(guild.text_channels, name='general') or guild.text_channels[0]
simple_ctx = SimpleContext(vc, text_channel)
play_next(simple_ctx)
break
@bot.command(name='join', help='Każe botowi dołączyć do kanału głosowego, na którym jesteś.')
async def join(ctx):
"""Bot dołącza do kanału głosowego użytkownika."""
if not ctx.author.voice:
await ctx.send(f"{ctx.author.name} nie jest w kanale głosowym.")
return
channel = ctx.author.voice.channel
# Sprawdzenie, czy bot jest już podłączony
if ctx.voice_client is not None:
await ctx.voice_client.move_to(channel)
else:
await channel.connect()
await ctx.send(f"Dołączam do kanału: **{channel.name}**")
@bot.command(name='leave', help='Każe botowi opuścić kanał głosowy.')
async def leave(ctx):
"""Bot opuszcza kanał głosowy."""
if ctx.voice_client:
await ctx.voice_client.disconnect()
# Zatrzymanie pętli, jeśli bot opuszcza kanał
if random_music_loop.is_running():
random_music_loop.stop()
await ctx.send("Opuszczam kanał głosowy.")
else:
await ctx.send("Bot nie jest podłączony do żadnego kanału głosowego.")
def play_next(ctx):
vc = ctx.voice_client
if not vc:
print("Bot nie jest podłączony do kanału głosowego.")
return
# ----------------------------------------------------
# 1. LOGIKA WYBORU UTWORU
# ----------------------------------------------------
file_to_play = None
is_ad = False
# 1.1. Tryb powtarzania pojedynczego utworu
if ctx.bot.is_looping_track and ctx.bot.current_track_path:
file_to_play = ctx.bot.current_track_path
is_ad = ctx.bot.current_track_is_ad # Utrzymaj stan reklamy/muzyki
# 1.2. Normalne odtwarzanie (lub pętla listy)
else:
# Losowanie, czy to będzie reklama, czy muzyka
is_ad_chance = random.choices([True, False], [1, 9])[0] # 10% szans na reklamę (przykładowo)
if is_ad_chance and ad_files:
file_to_play = random.choice(ad_files)
is_ad = True
elif music_files:
file_to_play = random.choice(music_files)
is_ad = False
else:
print("Brak plików muzycznych lub reklam do odtworzenia.")
return
# Zapisanie stanu dla kolejnych iteracji i komend
ctx.bot.current_track_is_ad = is_ad
ctx.bot.current_track_path = file_to_play # Zapisz pełną ścieżkę (jako string!)
# ----------------------------------------------------
# 2. MECHANIZM CALLBACK (PĘTLA)
# ----------------------------------------------------
def after_playing(e):
# Funkcja wywoływana, gdy utwór się skończy
if e:
print(f'Błąd podczas odtwarzania: {e}')
return
# Zmieniamy warunek kontynuacji pętli
if ctx.bot.is_looping_track or ctx.bot.is_looping_queue:
# Użyj vc.loop.call_soon_threadsafe do bezpiecznego wywołania z wątku after
vc.loop.call_soon_threadsafe(play_next, ctx)
else:
print("Odtwarzanie zakończone. Bot czeka na komendę.")
# ----------------------------------------------------
# 3. ROZPOCZĘCIE ODTWARZANIA
# ----------------------------------------------------
if file_to_play:
# 3.1. Aktualizacja statystyk (tylko dla muzyki)
if not is_ad:
# Pamiętaj, aby zaimplementować tę funkcję, aby używała track_name = os.path.basename(file_to_play)
update_play_count(file_to_play)
# 3.2. Tworzenie źródła
source = discord.FFmpegOpusAudio(
file_to_play,
options='-b:a 192k -f opus',
executable=FFMPEG_EXE_PATH
)
# 3.3. Odtwarzanie!
vc.play(source, after=after_playing)
# 3.4. Wysyłanie powiadomienia (ASYNCHRONICZNE!)
# Ponieważ jesteśmy w normalnej funkcji (nie async), musisz użyć loop.create_task:
vc.loop.create_task(send_now_playing(ctx, file_to_play, is_ad))
else:
print("Nie znaleziono pliku do odtworzenia.")
# Wymagana funkcja do asynchronicznego wysyłania wiadomości
async def send_now_playing(ctx, file_to_play, is_ad):
# 1. Przygotowanie tytułów
if is_ad:
# Dla reklam
vc_name = "🔇 Reklama"
chat_title = "REKLAMA SPONSOROWANA"
else:
# Dla muzyki: czyszczenie nazwy i formatowanie
cleaned_title = clean_track_name(file_to_play)
vc_name = f"🎧 {cleaned_title}"
chat_title = os.path.basename(file_to_play) # Oryginalna nazwa dla wiadomości na czacie
# 2. DODANIE ZMIANY STATUSU BOTA (ACTIVITY)
if is_ad:
new_activity = discord.Game(name="Reklamy...")
else:
cleaned_title = clean_track_name(file_to_play)
# Użyj ActivityType.listening dla statusu "Słuchanie..."
new_activity = discord.Activity(
name=cleaned_title,
type=discord.ActivityType.listening
)
try:
# Zmiana statusu bota
await ctx.bot.change_presence(activity=new_activity, status=discord.Status.online)
except Exception as e:
print(f"Błąd podczas zmiany statusu bota: {e}")
# 3. Wysyłanie wiadomości na kanał tekstowy
await ctx.send(f"▶️ Teraz odtwarzam: **{chat_title}**")
@tasks.loop(seconds=5.0, count=1) # Używamy count=1, aby wykonać funkcję tylko raz
async def random_music_loop():
"""Zadanie w pętli do rozpoczęcia odtwarzania, jeśli jest podłączony."""
# Użyjemy tej pętli tylko do jednorazowego rozpoczęcia odtwarzania
# po dołączeniu, prawdziwa pętla jest w play_next.
for vc in bot.voice_clients:
# Znajdź kontekst dla VoiceClient
ctx = vc.guild.id
if not vc.is_playing() and music_files:
# Ponieważ potrzebujemy obiektu Context do wysłania wiadomości i
# informacji o kanale, musimy odzyskać go. W uproszczonej wersji
# przyjmiemy, że bot jest podłączony i użyjemy prostego obiektu
# do wywołania play_next.
class SimpleContext:
def __init__(self, voice_client):
self.voice_client = voice_client
# Tworzymy prosty obiekt, aby przekazać go do play_next
# W bardziej rozbudowanych botach, ten kawałek kodu jest lepiej
# zaimplementowany w dedykowanej klasie 'MusicPlayer'.
simple_ctx = SimpleContext(vc)
# Rozpocznij odtwarzanie pierwszego utworu
play_next(simple_ctx)
break
# --- KOMENDA STARTUJĄCA I ZATRZYMUJĄCA ---
@bot.command(name='play_loop', help='Rozpoczyna odtwarzanie losowej muzyki w pętli.')
async def play_loop(ctx):
"""Rozpoczyna odtwarzanie muzyki w pętli losowo."""
# 1. Sprawdzenie kanału głosowego
if not ctx.author.voice:
await ctx.send("Musisz być w kanale głosowym, aby użyć tej komendy!")
return
# 2. Dołączenie do kanału, jeśli nie jest podłączony
if ctx.voice_client is None:
await join(ctx) # Wywołujemy komendę join
# 3. Sprawdzenie, czy są pliki
if not music_files:
await ctx.send(f"Brak plików .{FILE_EXT} w folderze '{MUSIC_DIR}'. Sprawdź konfigurację.")
return
# 4. Rozpoczęcie odtwarzania/restart
if ctx.voice_client and not ctx.voice_client.is_playing():
await ctx.send("Rozpoczynam odtwarzanie losowej muzyki w pętli...")
play_next(ctx) # Rozpoczęcie pierwszej piosenki, a callback zajmie się resztą
elif ctx.voice_client and ctx.voice_client.is_playing():
await ctx.send("Muzyka już jest odtwarzana.")
@bot.command(name='stop_loop', help='Zatrzymuje odtwarzanie i opuszcza kanał.')
async def stop_loop(ctx):
"""Zatrzymuje odtwarzanie i opuszcza kanał."""
if ctx.voice_client:
ctx.voice_client.stop()
await leave(ctx)
else:
await ctx.send("Bot nie odtwarza muzyki.")
@bot.command(name='runtime', help='Pokazuje czas działania')
async def runtime(ctx):
koniec = time.time()
calkowity_czas_sekundy = koniec - start
# Przeliczenia:
godziny = int(calkowity_czas_sekundy // 3600) # Ile pełnych godzin (1h = 3600s)
reszta = calkowity_czas_sekundy % 3600
minuty = int(reszta // 60) # Ile pełnych minut (1m = 60s) z reszty
sekundy = reszta % 60
await ctx.send(f"działam: {godziny} godzin, {minuty} minut, {sekundy:.2f} sekund")
@bot.command(name='skip', help='Pomiń muzykę na kanale głosowym')
async def skip(ctx):
vc = ctx.voice_client
if not vc:
await ctx.send("Bot nie jest w kanale głosowym.")
return
if not vc.is_playing():
await ctx.send("Bot nic nie odtwarza.")
return
# Używamy atrybutu bota do sprawdzenia stanu
if ctx.bot.current_track_is_ad == True: # <--- KLUCZOWA ZMIANA
await ctx.send("Gra reklama. Odtwarzanie przerwane. Proszę czekać na kolejny utwór. 📣")
return
# 2. Pomijanie muzyki
# Jeśli nie jest reklamą, kontynuuj pomijanie
if ctx.bot.current_track_is_ad == False: # <--- KLUCZOWA ZMIANA
vc.stop()
await ctx.send("Muzyka została pominięta. ⏩")
@bot.command(name='stats', help='Pokazuje 10 najczęściej odtwarzanych utworów.')
async def stats(ctx):
global db_connection
if not db_connection:
await ctx.send("Nie można połączyć się z bazą danych, statystyki niedostępne.")
return
cursor = db_connection.cursor()
# Zapytanie o 10 najczęściej odtwarzanych
query = """
SELECT TOP 10 FilePath, PlayCount
FROM Odtworzenia
ORDER BY PlayCount DESC
"""
try:
cursor.execute(query)
rows = cursor.fetchall()
if not rows:
await ctx.send("Baza statystyk jest pusta.")
return
# 1. Tworzenie obiektu Embed
embed = discord.Embed(
title="🐸 Statystyki Odtwarzania (TOP 10)",
description="Najczęściej odtwarzane utwory od startu bota.",
color=discord.Color.green()
)
embed.set_thumbnail(url=ctx.guild.icon.url if ctx.guild.icon else None)
# Przygotowanie danych do dwóch kolumn
titles = []
counts = []
# Iteracja przez wyniki i formatowanie
for i, row in enumerate(rows[:10]):
file_path = row.FilePath
count = row.PlayCount
# Użyj funkcji czyszczącej, aby wyświetlić ładny tytuł (zakładając, że masz clean_track_name)
try:
# Wersja bez os.path.basename, jeśli potrzebujesz tylko nazwy utworu
cleaned_title = clean_track_name(file_path)
except NameError:
# Awaryjnie, jeśli nie masz clean_track_name
cleaned_title = os.path.basename(file_path)
# Wypełnianie list. Używamy kodu bloku, aby wyglądało schludniej.
titles.append(f"`{i+1}.` {cleaned_title}")
counts.append(f"`{count} razy`")
# 2. Dodawanie pól do Embeda (dwie kolumny obok siebie)
# Tytuły
embed.add_field(name="Tytuł Utworu", value="\n".join(titles), inline=True)
# Licznik
embed.add_field(name="Odtworzeń", value="\n".join(counts), inline=True)
embed.set_footer(text=f"Aktualizacja statystyk: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# 3. Wysłanie Embeda
await ctx.send(embed=embed)
except pyodbc.Error as ex:
# Obsługa błędów bazy danych
await ctx.send(f"Wystąpił błąd podczas pobierania statystyk z bazy: {ex}")
finally:
cursor.close()
@bot.command(name='repeat', help='Włącza/wyłącza powtarzanie obecnie odtwarzanego utworu.')
async def repeat_track_toggle(ctx):
vc = ctx.voice_client
if vc is None or not vc.is_playing():
await ctx.send("Bot niczego nie odtwarza. Najpierw użyj komendy `!play`.")
return
# Sprawdzamy, czy bot ma zapamiętany jakiś utwór do powtarzania
if not ctx.bot.current_track_path:
await ctx.send("Nie zapamiętano ścieżki utworu. Najpierw odtwórz coś komendą `!play`.")
return
# Przełącz stan
ctx.bot.is_looping_track = not ctx.bot.is_looping_track
if ctx.bot.is_looping_track:
# Jeśli włączamy powtarzanie utworu, wyłączamy pętlę listy, by uniknąć kolizji
ctx.bot.is_looping_queue = False
current_name = os.path.basename(ctx.bot.current_track_path)
await ctx.send(f"🔁 **Włączono powtarzanie**! Utwór **{current_name}** będzie odtwarzany w pętli.")
else:
# Po wyłączeniu, przywracamy pętlę listy jako domyślny tryb
ctx.bot.is_looping_queue = True
await ctx.send("❌ **Wyłączono powtarzanie.** Bot powrócił do odtwarzania listy.")
@bot.command(name='loop', help='Włącza/wyłącza automatyczne odtwarzanie w pętli (z listy).')
async def loop_toggle(ctx):
ctx.bot.is_looping_queue = not ctx.bot.is_looping_queue
if ctx.bot.is_looping_queue:
ctx.bot.is_looping_track = False # Upewnij się, że powtarzanie utworu jest wyłączone
await ctx.send("🔄 **Włączono** automatyczne odtwarzanie w pętli. Muzyka będzie grała bez przerwy.")
else:
await ctx.send("❌ **Wyłączono** automatyczne odtwarzanie. Po zakończeniu utworu bot się zatrzyma.")
@bot.command(name='help', help='Wyświetla interaktywne menu pomocy z podziałem na strony.')
async def help_command(ctx):
# 1. Utwórz instancję widoku (kontrolera przycisków)
view = HelpView(ctx)
# 2. Ustaw stan przycisków dla pierwszej strony (Wstecz jest wyłączony)
view.update_buttons()
# 3. Wyślij wiadomość z pierwszym embedem i przyciskami
message = await ctx.send(embed=view.create_embed(), view=view)
# 4. Zapisz wiadomość w widoku, aby móc ją edytować w on_timeout
view.message = message
# Uruchomienie bota
bot.run('MTM3OTA2ODIyNDk0NzA5MzUzNg.GuSMW3.OzhV0eLNLSeEZvyoEfMkXxIckHUwp_b12p8wvc') # Replace with your own token.