2017-11-11 17:55:13 +00:00
import random
2018-02-26 15:38:47 +00:00
import re
2017-10-27 11:38:32 +00:00
import discord
2017-10-31 22:50:05 +00:00
import discord . opus
2018-02-25 18:50:57 +00:00
import discord . voice_client
2017-11-01 18:23:11 +00:00
import functools
2017-11-09 19:34:18 +00:00
import sys
2017-10-27 11:38:32 +00:00
import db
2017-11-05 21:50:37 +00:00
import youtube_dl
2018-01-15 12:42:40 +00:00
import concurrent . futures
2018-02-25 18:50:57 +00:00
import typing
2018-02-25 19:47:12 +00:00
import os
2018-02-26 09:48:17 +00:00
import asyncio
import configparser
2018-04-09 20:55:48 +00:00
import async_timeout
2018-04-12 16:04:13 +00:00
import raven
2018-05-31 19:43:43 +00:00
import logging
2018-07-25 19:10:43 +00:00
import errors
2018-07-26 17:26:03 +00:00
import datetime
2018-08-08 15:48:57 +00:00
import sqlalchemy . exc
2018-09-17 22:26:01 +00:00
import coloredlogs
2018-11-26 14:46:47 +00:00
import errors
2018-11-28 14:47:08 +00:00
import math
2018-12-03 11:00:37 +00:00
import enum
2018-05-31 19:43:43 +00:00
2018-09-18 22:02:39 +00:00
logging . getLogger ( ) . disabled = True
2018-08-16 17:46:07 +00:00
logger = logging . getLogger ( __name__ )
2018-09-18 22:13:41 +00:00
os . environ [ " COLOREDLOGS_LOG_FORMAT " ] = " %(asctime)s %(levelname)s %(name)s %(message)s "
2018-09-17 22:26:01 +00:00
coloredlogs . install ( level = " DEBUG " , logger = logger )
2017-10-27 11:38:32 +00:00
2018-02-26 15:38:47 +00:00
# Queue emojis
2018-05-25 17:58:30 +00:00
queue_emojis = [ " :one: " ,
" :two: " ,
" :three: " ,
" :four: " ,
" :five: " ,
" :six: " ,
" :seven: " ,
" :eight: " ,
" :nine: " ,
" :keycap_ten: " ]
2018-02-26 15:38:47 +00:00
2017-10-27 11:38:32 +00:00
# Init the event loop
loop = asyncio . get_event_loop ( )
2018-08-05 21:24:41 +00:00
# Radio messages
2018-10-10 17:48:05 +00:00
radio_messages = [ " https://www.youtube.com/watch?v=3-yeK1Ck4yk " ,
" https://youtu.be/YcR7du_A1Vc " ,
" https://clyp.it/byg3i52l " ]
2018-08-08 12:09:57 +00:00
song_special_messages = {
" despacito " : " :arrow_forward: this is so sad. alexa play {song} " ,
" faded " : " :arrow_forward: Basta Garf, lasciami ascoltare {song} " ,
" ligma " : " :arrow_forward: What is ligma? {song} ! " ,
" sugma " : " :arrow_forward: What is sugma? {song} ! " ,
" sugondese " : " :arrow_forward: What is sugondese? {song} ! " ,
" bofa " : " :arrow_forward: What is bofa? {song} ! " ,
2018-08-16 17:33:38 +00:00
" updog " : " :arrow_forward: What ' s up, dog? {song} ! " ,
2018-08-08 12:09:57 +00:00
" sayo-nara " : " :arrow_forward: I gently open the door. {song} awaits me inside. " ,
" monika " : " :arrow_forward: Just Monika. Just Monika. Just {song} . " ,
2018-11-06 22:11:35 +00:00
" country road " : " :arrow_forward: Take me home, to {song} , the place I belong! " ,
2018-08-08 12:09:57 +00:00
" never gonna give you up " : " :arrow_forward: Rickrolling in 2018. Enjoy {song} ! " ,
" september " : " :arrow_forward: Do you remember? {song} . " ,
" homestuck " : " :arrow_forward: > Enter song name. {song} " ,
" undertale " : " :arrow_forward: Howdy! I ' m Flowey! Listen to this friendly song: {song} " ,
" pumped up kicks " : " :arrow_forward: Non metterti mica in testa strane idee ascoltando {song} ... " ,
" jesus " : " :arrow_forward: Respawn in 3 giorni. Intanto, ascolta {song} . " ,
" through The fire And flames " : " :arrow_forward: Fai {song} su osu!, se ne sei capace! " ,
" slow clap " : " :arrow_forward: :clap: :clap: :clap: {song} :clap: :clap: :clap: " ,
" pub scrubs " : " :arrow_forward: MAV COME BACK WE MISS {song} ! " ,
" alleluia " : " :arrow_forward: Wah. Waaaah. Waluigi tiime: {song} " ,
" wah " : " :arrow_forward: Wah. Waaaah. Waluigi tiime: {song} " ,
" waluigi " : " :arrow_forward: Wah. Waaaah. Waluigi tiime: {song} " ,
" nyan cat " : " :arrow_forward: Meow! :3 {song} " ,
" dragonborn " : " :arrow_forward: FUS RO {song} ! " ,
" dovahkiin " : " :arrow_forward: FUS RO {song} ! " ,
" initial d " : " :arrow_forward: Guarda mamma sto driftando sul balcone di Balu grazie a {song} ! " ,
" persona " : " :arrow_forward: You ' ll never see {song} comiiiiing! " ,
" flamingo " : " :arrow_forward: How many {song} do you have to eat? " ,
" linkin park " : " :arrow_forward: Crawling in my {song} ! " ,
2018-11-06 22:11:35 +00:00
" magicite " : " ⚠️ Warning: {song} contiene numerosi bug. E ' ora in riproduzione. " ,
2018-08-08 12:09:57 +00:00
" papers please " : " :arrow_forward: Glory to Arstotzka! {song} ! " ,
" we are number one " : " :arrow_forward: Now paying respect to Robbie Rotten: {song} " ,
2018-11-06 22:11:35 +00:00
" jump up superstar " : " :arrow_forward: Is {song} the Tengen Toppa Guren Lagann opening? " ,
" the world revolving " : " :arrow_forward: CHAOS! CHAOS! I CAN DO {song} ! " ,
" deltarune " : " :arrow_forward: You hug Ralsei. A strange music starts playing: {song} " ,
" song of unhealing " : " :arrow_forward: BEN {song} " ,
" police academy " : " :arrow_forward: {song} - freedom.png " ,
2018-11-26 14:45:13 +00:00
" super smash bros. ultimate " : " :arrow_forward: Re-awaken the undying light with {song} ! " ,
" powerwolf " : " :arrow_forward: Spaggia, ma non ti sei un po ' stancato di {song} ? " ,
2018-11-26 14:49:28 +00:00
" eurobeat " : " :arrow_forward: Nemesis approva la scelta di {song} . Ben fatto, amico. "
2018-08-08 12:09:57 +00:00
}
2018-08-05 21:24:41 +00:00
2018-08-07 23:30:43 +00:00
# FFmpeg settings
ffmpeg_settings = { }
2018-08-08 12:09:57 +00:00
# Init the executor
executor = concurrent . futures . ThreadPoolExecutor ( max_workers = 3 )
2018-11-26 14:45:13 +00:00
class Succ :
""" All calls to this class return itself. """
def __bool__ ( self ) :
return False
def __getattr__ ( self ) :
return Succ ( )
def __call__ ( self , * args , * * kwargs ) :
return Succ ( )
def __str__ ( self ) :
return " succ "
def __repr__ ( self ) :
return " <Succ> "
2018-08-08 12:09:57 +00:00
# Init the Sentry client
2018-11-26 14:45:13 +00:00
if config . get ( " Sentry " ) and config [ " Sentry " ] . get ( " token " ) :
sentry = raven . Client ( config [ " Sentry " ] [ " token " ] ,
release = raven . fetch_git_sha ( os . path . dirname ( __file__ ) ) ,
install_logging_hook = False ,
hook_libraries = [ ] )
else :
logger . warning ( " Sentry not set, ignoring all calls to it. " )
sentry = Succ ( )
2018-08-08 12:09:57 +00:00
2018-05-27 13:23:33 +00:00
2018-12-03 11:00:37 +00:00
class Video :
def __init__ ( self , enqueuer : typing . Optional [ discord . Member ] = None ) :
self . is_ready = False
self . name = None
self . enqueuer = enqueuer
self . audio_source = None
def __str__ ( self ) :
2018-12-03 15:53:03 +00:00
if self . name is None :
return " _Untitled_ "
2018-12-03 11:00:37 +00:00
return self . name
def plain_text ( self ) :
""" Title without formatting to be printed on terminals. """
2018-12-03 15:53:03 +00:00
if self . name is None :
return " Untitled "
2018-12-03 11:00:37 +00:00
return self . name
def database_text ( self ) :
""" The text to be stored in the database for the stats. Usually the same as plain_text(). """
2018-12-03 15:53:03 +00:00
if self . name is None :
raise errors . VideoHasNoName ( )
2018-12-03 11:00:37 +00:00
return self . name
def __repr__ ( self ) :
return f " <Video { self . name } ( { ' ' if self . is_ready else ' not ' } ready) added by { self . enqueuer } > "
def ready_up ( self ) :
""" Prepare the video for playback in some way. For example, download it. """
raise NotImplementedError ( )
def make_audio_source ( self ) :
""" Create an AudioSource to be played through Discord, and store and return it. """
raise NotImplementedError ( )
def get_suggestion ( self ) :
""" Get the next suggested video, to be used when the queue is in LoopMode.FOLLOW_SUGGESTION """
raise NotImplementedError ( )
class YoutubeDLVideo ( Video ) :
""" A file sourcing from YoutubeDL. """
2018-12-03 15:53:03 +00:00
2018-12-03 11:00:37 +00:00
def __init__ ( self , url , enqueuer : typing . Optional [ discord . Member ] = None ) :
super ( ) . __init__ ( enqueuer )
self . url = url
self . info = None
def get_info ( self ) :
""" Get info about the video. """
2018-12-03 15:53:03 +00:00
if self . info :
return
with youtube_dl . YoutubeDL ( { " quiet " : True ,
" ignoreerrors " : True ,
" simulate " : True } ) as ytdl :
data = ytdl . extract_info ( url = self . url , download = False )
if data is None :
raise errors . VideoInfoExtractionFailed ( )
if " entries " in info :
raise errors . VideoIsPlaylist ( )
self . info = data
self . name = data . get ( " title " )
2018-12-03 11:00:37 +00:00
2018-12-03 15:53:03 +00:00
def __str__ ( self ) :
if self . info is None :
return f " ` { self . url } ` "
return f " _ { self . name } _ "
def plain_text ( self ) :
if self . info is None :
return self . url
if not self . name . isprintable ( ) :
return self . url
return self . name
2018-12-03 11:00:37 +00:00
def get_filename ( self ) :
""" Generate the filename of the video. """
2018-12-03 15:53:03 +00:00
if info is None :
raise errors . VideoInfoUnknown ( )
return f " ./opusfiles/ { re . sub ( r ' [/ \\ ?* " <>|!:] ' , " _ " , info [ " title " ] ) } .opus "
2018-12-03 11:00:37 +00:00
2018-12-03 15:53:03 +00:00
def ready_up ( self ) :
""" Download the video. """
# Skip download if it is already ready
if self . is_ready :
return
# Retrieve info about the video
self . get_info ( )
# Check if the file to download already exists
if os . path . exists ( self . get_filename ( ) ) :
self . is_ready = True
return
# Download the file
logger . info ( f " Starting youtube_dl download of { repr ( self ) } " )
with youtube_dl . YoutubeDL ( { " noplaylist " : True ,
" format " : " best " ,
" postprocessors " : [ {
" key " : ' FFmpegExtractAudio ' ,
" preferredcodec " : ' opus '
} ] ,
" outtmpl " : self . get_filename ( ) ,
" quiet " : True } ) as ytdl :
ytdl . download ( self . url )
logger . info ( f " Completed youtube_dl download of { repr ( self ) } " )
self . is_ready = True
def make_audio_source ( self ) :
if not self . is_ready :
raise errors . VideoIsNotReady ( )
self . audio_source = discord . PCMVolumeTransformer ( discord . FFmpegPCMAudio ( self . get_filename ( ) , * * ffmpeg_settings ) )
return self . audio_source
2018-12-03 11:00:37 +00:00
class LoopMode ( enum . Enum ) :
NORMAL = enum . auto ( )
LOOP_QUEUE = enum . auto ( )
LOOP_SINGLE = enum . auto ( )
FOLLOW_SUGGESTIONS = enum . auto ( )
2018-11-28 15:44:34 +00:00
class VideoQueue ( ) :
""" The queue of videos to be played. """
def __init__ ( self ) :
self . list : typing . List [ Video ] = [ ]
self . now_playing : typing . Optional [ Video ] = None
2018-12-03 11:00:37 +00:00
self . loop_mode = LoopMode . NORMAL
2018-11-28 15:44:34 +00:00
def __len__ ( self ) - > int :
return len ( self . list )
2018-12-03 11:00:37 +00:00
def __next__ ( self ) - > Video :
video = self . next_video ( )
self . advance_queue ( )
return video
def __repr__ ( self ) - > str :
return f " <VideoQueue of length { len ( self ) } > "
2018-11-28 15:44:34 +00:00
def add ( self , video : Video , position : int = None ) - > None :
if position is None :
self . list . append ( video )
return
self . list . insert ( position , video )
2018-12-03 11:00:37 +00:00
def advance_queue ( self ) :
if loop_mode == LoopMode . NORMAL :
2018-12-03 15:53:03 +00:00
try :
self . now_playing = self . list . pop ( 0 )
except IndexError :
self . now_playing = None
2018-12-03 11:00:37 +00:00
elif loop_mode == LoopMode . LOOP_QUEUE :
self . add ( self . list [ 0 ] )
2018-12-03 15:53:03 +00:00
self . now_playing = self . list . pop ( 0 )
2018-12-03 11:00:37 +00:00
elif loop_mode == LoopMode . LOOP_SINGLE :
pass
elif loop_mode == LoopMode . FOLLOW_SUGGESTIONS :
2018-12-03 15:53:03 +00:00
if now_playing is None :
self . now_playing = None
return
self . now_playing = self . now_playing . suggestion ( )
2018-12-03 11:00:37 +00:00
2018-11-28 15:44:34 +00:00
def next_video ( self ) - > typing . Optional [ Video ] :
if len ( self . list ) == 0 :
return None
return self . list [ 0 ]
def shuffle ( self ) :
random . shuffle ( self . list )
def clear ( self ) :
self . list = None
self . now_playing = None
def forward ( self ) - > None :
self . now_playing = self . list . pop ( 0 )
2018-12-03 15:53:03 +00:00
def find_video ( self , name : str ) - > typing . Optional [ Video ] :
""" Returns the first video with a certain name. """
2018-11-28 15:44:34 +00:00
for video in self . list :
2018-12-03 15:53:03 +00:00
if name in video . name :
2018-11-28 15:44:34 +00:00
return video
return None
2018-12-03 11:00:37 +00:00
2018-12-03 15:53:03 +00:00
def not_ready_videos ( self , limit : typing . Optional [ int ] = None ) :
""" Return the non-ready videos in the first limit positions of the queue. """
2018-12-03 11:00:37 +00:00
l = [ ]
for video in self . list [ : limit ] :
2018-12-03 15:53:03 +00:00
if not video . is_ready :
2018-12-03 11:00:37 +00:00
l . append ( video )
return l
2018-11-28 15:44:34 +00:00
def __getitem__ ( self , index : int ) - > Video :
""" Get an element from the list. """
return self . list [ index ]
2018-08-07 23:30:43 +00:00
2018-11-07 19:50:55 +00:00
def escape ( message : str ) :
return message . replace ( " < " , " < " ) . replace ( " > " , " > " )
2018-08-08 12:09:57 +00:00
def command ( func ) :
""" Decorator. Runs the function as a Discord command. """
async def new_func ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] , * args ,
* * kwargs ) :
if author is not None :
sentry . user_context ( {
" discord_id " : author . id ,
" username " : f " { author . name } # { author . discriminator } "
} )
else :
sentry . user_context ( {
" source " : " Telegram "
} )
try :
result = await func ( self , channel = channel , author = author , params = params , * args , * * kwargs )
except Exception :
ei = sys . exc_info ( )
2018-09-17 22:07:00 +00:00
# noinspection PyBroadException
2018-08-08 12:09:57 +00:00
try :
await channel . send ( f " ☢ **ERRORE DURANTE L ' ESECUZIONE DEL COMANDO { params [ 0 ] } ** \n "
f " Il comando è stato ignorato. \n "
f " Una segnalazione di errore è stata automaticamente mandata a Steffo. \n \n "
f " Dettagli dell ' errore: \n "
f " ```python \n "
f " { repr ( ei [ 1 ] ) } \n "
f " ``` " )
except Exception :
pass
sentry . captureException ( exc_info = ei )
else :
return result
return new_func
def requires_connected_voice_client ( func ) :
""" Decorator. Ensures the voice client is connected before running the command. """
async def new_func ( self : " RoyalDiscordBot " , channel : discord . TextChannel , author : discord . Member ,
params : typing . List [ str ] , * args , * * kwargs ) :
for voice_client in self . voice_clients :
if voice_client . channel in self . main_guild . channels and voice_client . is_connected ( ) :
break
else :
await channel . send ( " ⚠️ Non sono connesso alla cv! \n "
" Fammi entrare scrivendo `!cv` mentre sei in chat vocale. " )
return
return await func ( self , channel = channel , author = author , params = params , * args , * * kwargs )
return new_func
def requires_rygdb ( func , optional = False ) :
async def new_func ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] , * args ,
* * kwargs ) :
session = db . Session ( )
dbuser = await loop . run_in_executor ( executor ,
session . query ( db . Discord )
. filter_by ( discord_id = author . id )
. join ( db . Royal )
. first )
await loop . run_in_executor ( executor , session . close )
if not optional and dbuser is None :
await channel . send ( " ⚠️ Devi essere registrato su Royalnet per poter utilizzare questo comando. " )
return
return await func ( self , channel = channel , author = author , params = params , dbuser = dbuser , * args , * * kwargs )
return new_func
2018-08-07 23:30:43 +00:00
class RoyalDiscordBot ( discord . Client ) :
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . main_channel : typing . Optional [ discord . TextChannel ] = None
self . main_guild : typing . Optional [ discord . Guild ] = None
2018-08-08 12:09:57 +00:00
self . commands = {
" !ping " : self . cmd_ping ,
" !cv " : self . cmd_cv ,
" !summon " : self . cmd_cv ,
" !play " : self . cmd_play ,
" !p " : self . cmd_play ,
" !search " : self . cmd_play ,
" !file " : self . cmd_play ,
" !skip " : self . cmd_skip ,
" !s " : self . cmd_skip ,
2018-11-06 22:11:35 +00:00
" !next " : self . cmd_skip ,
2018-08-08 12:09:57 +00:00
" !remove " : self . cmd_remove ,
2018-10-02 22:18:41 +00:00
" !r " : self . cmd_remove ,
2018-08-08 12:09:57 +00:00
" !cancel " : self . cmd_remove ,
" !queue " : self . cmd_queue ,
" !q " : self . cmd_queue ,
" !shuffle " : self . cmd_shuffle ,
" !clear " : self . cmd_clear ,
" !register " : self . cmd_register ,
" !forceplay " : self . cmd_forceplay ,
" !fp " : self . cmd_forceplay ,
" !radiomessages " : self . cmd_radiomessages ,
" !yes " : self . null ,
2018-08-17 13:09:03 +00:00
" !no " : self . null ,
" !pause " : self . cmd_pause ,
2018-11-26 14:20:10 +00:00
" !resume " : self . cmd_resume
2018-08-08 12:09:57 +00:00
}
2018-11-28 15:44:34 +00:00
self . video_queue : VideoQueue = VideoQueue ( )
2018-11-28 14:47:08 +00:00
self . load_config ( " config.ini " )
2018-09-14 23:05:19 +00:00
self . inactivity_timer = 0
2018-08-07 23:30:43 +00:00
2018-11-28 14:47:08 +00:00
def load_config ( self , filename ) :
# Init the config reader
config = configparser . ConfigParser ( )
config . read ( " config.ini " )
# Token
try :
self . token = config [ " Discord " ] [ " bot_token " ]
except KeyError , ValueError :
raise errors . InvalidConfigError ( " Missing Discord bot token. " )
# Main channels, will be fully loaded when ready
try :
self . main_guild_id = int ( config [ " Discord " ] [ " server_id " ] )
self . main_channel_id = int ( config [ " Discord " ] [ " main_channel " ]
except KeyError , ValueError :
raise errors . InvalidConfigError ( " Missing main guild and channel ids. " )
# Max enqueable video duration
2018-12-03 15:53:03 +00:00
# Defined in the YoutubeDLVideo class
2018-11-28 14:47:08 +00:00
# Max videos to predownload
try :
2018-12-03 15:53:03 +00:00
self . max_videos_to_predownload = int ( config [ " Video " ] [ " cache_size " ] )
2018-11-28 14:47:08 +00:00
except KeyError , ValueError :
logger . warning ( " Max videos to predownload is not set, setting it to infinity. " )
self . max_videos_to_predownload = None
2018-12-03 15:53:03 +00:00
# Max time to ready a video
try :
self . max_video_ready_time = int ( config [ " Video " ] [ " max_ready_time " ] )
except KeyError , ValueError :
logger . warning ( " Max time to ready a video is not set, setting it to infinity. " )
self . max_video_ready_time = mathf . inf
2018-11-28 14:47:08 +00:00
# Radio messages
try :
self . radio_messages_enabled = True if config [ " Discord " ] [ " radio_messages_enabled " ] == " True " else False
self . radio_messages_every = int ( config [ " Discord " ] [ " radio_messages_every " ] )
self . radio_messages_next_in = self . radio_messages_every
except KeyError , ValueError :
logger . warning ( " Radio messages config error, disabling them. " )
self . radio_messages_enabled = False
self . radio_messages_every = mathf . inf
self . radio_messages_next_in = mathf . inf
2018-08-07 23:30:43 +00:00
async def on_ready ( self ) :
# Get the main guild
2018-11-28 14:47:08 +00:00
self . main_guild = self . get_guild ( self . main_guild_id )
2018-08-07 23:30:43 +00:00
if not isinstance ( self . main_guild , discord . Guild ) :
2018-11-26 14:46:47 +00:00
raise errors . InvalidConfigError ( " The main guild does not exist! " )
2018-11-28 14:47:08 +00:00
# Get the main channel
self . main_channel = self . get_channel ( self . main_channel_id )
if not isinstance ( self . main_channel , discord . TextChannel ) :
raise errors . InvalidConfigError ( " The main channel is not a TextChannel! " )
# Show yourself!
2018-08-07 23:30:43 +00:00
await self . change_presence ( status = discord . Status . online , activity = None )
2018-09-18 23:04:48 +00:00
logger . info ( " Bot is ready! " )
2018-11-28 14:47:08 +00:00
# Start the bot tasks
2018-11-18 16:45:14 +00:00
asyncio . ensure_future ( self . queue_predownload_videos ( ) )
asyncio . ensure_future ( self . queue_play_next_video ( ) )
asyncio . ensure_future ( self . inactivity_countdown ( ) )
asyncio . ensure_future ( self . activity_task ( ) )
2018-08-07 23:30:43 +00:00
async def on_message ( self , message : discord . Message ) :
if message . channel != self . main_channel or message . author . bot :
return
sentry . user_context ( {
" discord " : {
" discord_id " : message . author . id ,
" name " : message . author . name ,
" discriminator " : message . author . discriminator
}
} )
if not message . content . startswith ( " ! " ) :
await message . channel . send ( f " :warning: In questa chat sono consentiti solo comandi per il bot. \n "
f " Riinvia il tuo messaggio in un altro canale! " )
await message . delete ( )
return
data = message . content . split ( " " )
2018-08-08 12:09:57 +00:00
if data [ 0 ] not in self . commands :
2018-08-07 23:30:43 +00:00
await message . channel . send ( " :warning: Comando non riconosciuto. " )
return
2018-09-18 23:04:48 +00:00
logger . debug ( f " Received command: { message . content } " )
2018-09-05 17:48:34 +00:00
sentry . extra_context ( {
" command " : data [ 0 ] ,
" message " : message
} )
2018-09-14 23:05:19 +00:00
self . inactivity_timer = 3600
2018-08-08 12:09:57 +00:00
await self . commands [ data [ 0 ] ] ( channel = message . channel ,
author = message . author ,
params = data )
2017-11-06 15:54:36 +00:00
2018-08-07 23:30:43 +00:00
async def on_error ( self , event_method , * args , * * kwargs ) :
ei = sys . exc_info ( )
2018-08-16 17:46:07 +00:00
logger . error ( f " Critical error: { repr ( ei [ 1 ] ) } " )
2018-09-13 16:38:21 +00:00
# noinspection PyBroadException
2018-08-07 23:30:43 +00:00
try :
2018-09-14 23:05:19 +00:00
await self . main_channel . send ( f " ☢️ **ERRORE CRITICO NELL ' EVENTO** ` { event_method } `! \n "
2018-08-07 23:30:43 +00:00
f " Il bot si è chiuso e si dovrebbe riavviare entro qualche minuto. \n "
f " Una segnalazione di errore è stata automaticamente mandata a Steffo. \n \n "
f " Dettagli dell ' errore: \n "
f " ```python \n "
f " { repr ( ei [ 1 ] ) } \n "
f " ``` " )
await self . change_presence ( status = discord . Status . invisible )
await self . close ( )
2018-09-13 16:38:21 +00:00
except Exception :
logger . error ( f " Double critical error: { sys . exc_info ( ) } " )
2018-08-07 23:30:43 +00:00
loop . stop ( )
sentry . captureException ( exc_info = ei )
exit ( 1 )
async def feed_pipe ( self , connection ) :
await self . wait_until_ready ( )
while True :
msg = await loop . run_in_executor ( executor , connection . recv )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Received from the Telegram-Discord pipe: { msg } " )
2018-08-07 23:30:43 +00:00
if msg == " get cv " :
discord_members = list ( self . main_guild . members )
2018-08-28 13:48:12 +00:00
channels = { 0 : None }
members_in_channels = { 0 : [ ] }
message = " "
# Find all the channels
for member in discord_members :
2018-08-28 13:49:29 +00:00
if member . voice is not None :
2018-08-28 13:48:12 +00:00
channel = members_in_channels . get ( member . voice . channel . id )
if channel is None :
members_in_channels [ member . voice . channel . id ] = list ( )
channel = members_in_channels [ member . voice . channel . id ]
channels [ member . voice . channel . id ] = member . voice . channel
channel . append ( member )
else :
members_in_channels [ 0 ] . append ( member )
# Edit the message, sorted by channel
2018-09-18 23:04:48 +00:00
for channel in sorted ( channels , key = lambda c : - c ) :
2018-08-28 13:48:12 +00:00
members_in_channels [ channel ] . sort ( key = lambda x : x . nick if x . nick is not None else x . name )
if channel == 0 :
2018-09-18 23:04:48 +00:00
message + = " <b>Non in chat vocale:</b> \n "
2018-08-28 13:48:12 +00:00
else :
2018-11-07 19:50:55 +00:00
message + = f " <b>In # { escape ( channels [ channel ] . name ) } :</b> \n "
2018-08-28 13:48:12 +00:00
for member in members_in_channels [ channel ] :
2018-09-18 23:04:48 +00:00
# Ignore not-connected non-notable members
if channel == 0 and len ( member . roles ) < 2 :
continue
# Ignore offline members
2018-08-28 13:51:06 +00:00
if member . status == discord . Status . offline and member . voice is None :
2018-08-28 13:48:12 +00:00
continue
# Online status emoji
if member . bot :
message + = " 🤖 "
elif member . status == discord . Status . online :
message + = " 🔵 "
elif member . status == discord . Status . idle :
message + = " ⚫️ "
elif member . status == discord . Status . dnd :
message + = " 🔴 "
elif member . status == discord . Status . offline :
message + = " ⚪️ "
# Voice
if channel != 0 :
# Voice status
if member . voice . self_deaf :
message + = f " 🔇 "
elif member . voice . self_mute :
message + = f " 🔈 "
else :
message + = f " 🔊 "
# Nickname
if member . nick is not None :
2018-11-07 19:50:55 +00:00
message + = escape ( member . nick )
2018-08-28 13:48:12 +00:00
else :
2018-11-07 19:50:55 +00:00
message + = escape ( member . name )
2018-08-28 13:48:12 +00:00
# Game or stream
if member . activity is not None :
2018-09-18 23:04:48 +00:00
if member . activity . type == discord . ActivityType . playing :
2018-11-07 19:50:55 +00:00
message + = f " | 🎮 { escape ( member . activity . name ) } "
2018-09-18 23:04:48 +00:00
# Rich presence
2018-09-19 16:20:56 +00:00
try :
2018-11-06 22:11:35 +00:00
if member . activity . state is not None :
2018-11-07 19:50:55 +00:00
message + = f " ( { escape ( member . activity . state ) } ) "
2018-09-19 16:20:56 +00:00
except AttributeError :
try :
2018-11-06 22:11:35 +00:00
if member . activity . details is not None :
2018-11-07 19:50:55 +00:00
message + = f " ( { escape ( member . activity . details ) } ) "
2018-09-19 16:20:56 +00:00
except AttributeError :
pass
2018-09-18 23:04:48 +00:00
elif member . activity . type == discord . ActivityType . streaming :
2018-11-07 19:50:55 +00:00
message + = f " | 📡 [ { escape ( member . activity . name ) } ]( { escape ( member . activity . url ) } ) "
2018-09-18 23:04:48 +00:00
elif member . activity . type == discord . ActivityType . listening :
2018-11-07 19:50:55 +00:00
message + = f " | 🎧 { escape ( member . activity . name ) } "
2018-08-28 13:48:12 +00:00
elif member . activity . type == discord . ActivityType . watching :
2018-11-07 19:50:55 +00:00
message + = f " | 📺 { escape ( member . activity . name ) } "
2018-08-28 13:48:12 +00:00
message + = " \n "
message + = " \n "
connection . send ( message )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Answered successfully cvlist request. " )
2018-08-07 23:30:43 +00:00
elif msg . startswith ( " ! " ) :
data = msg . split ( " " )
2018-08-08 12:09:57 +00:00
if data [ 0 ] not in self . commands :
2018-08-07 23:30:43 +00:00
connection . send ( " error " )
continue
2018-09-18 23:04:48 +00:00
logger . debug ( f " Received command: { msg } " )
2018-08-08 12:09:57 +00:00
await self . main_channel . send ( f " { msg } \n "
f " _(da Telegram)_ " )
2018-09-05 17:48:34 +00:00
await self . commands [ data [ 0 ] ] ( channel = self . main_channel ,
2018-08-08 12:09:57 +00:00
author = None ,
params = data )
2018-08-07 23:30:43 +00:00
connection . send ( " success " )
2018-02-25 18:50:57 +00:00
2018-08-08 12:09:57 +00:00
async def queue_predownload_videos ( self ) :
while True :
2018-12-03 15:53:03 +00:00
await asyncio . sleep ( 1 )
# Might have some problems with del
2018-12-03 11:00:37 +00:00
for index , video in enumerate ( self . video_queue . undownloaded_videos ( self . max_videos_to_predownload ) ) :
2018-08-08 12:09:57 +00:00
try :
2018-12-03 15:53:03 +00:00
with async_timeout . timeout ( self . max_video_ready_time ) :
loop . run_in_executor ( executor , video . ready_up )
2018-08-08 12:09:57 +00:00
except asyncio . TimeoutError :
2018-12-03 15:53:03 +00:00
logger . warning ( f " Video { repr ( video ) } took more than { self . max_video_ready_time } to download, skipping... " )
await self . main_channel . send ( f " ⚠️ La preparazione di { video } ha richiesto più di { self . max_video_ready_time } secondi, pertanto è stato rimosso dalla coda. " )
2018-11-28 15:44:34 +00:00
del self . video_queue . list [ index ]
2018-08-08 12:09:57 +00:00
continue
except Exception as e :
2018-09-18 23:04:48 +00:00
sentry . user_context ( {
" discord " : {
" discord_id " : video . enqueuer . id ,
" name " : video . enqueuer . name ,
" discriminator " : video . enqueuer . discriminator
}
} )
sentry . extra_context ( {
" video " : video . plain_text ( )
} )
sentry . captureException ( )
2018-12-03 15:53:03 +00:00
logger . error ( f " Uncaught video download error: { e } " )
2018-08-08 12:09:57 +00:00
await self . main_channel . send ( f " ⚠️ E ' stato incontrato un errore durante il download di "
f " { str ( video ) } , quindi è stato rimosso dalla coda. \n \n "
f " ```python \n "
f " { str ( e ) } "
f " ``` " )
2018-11-28 15:44:34 +00:00
del self . video_queue . list [ index ]
2018-08-08 12:09:57 +00:00
continue
2018-02-25 18:50:57 +00:00
2018-08-08 12:09:57 +00:00
async def queue_play_next_video ( self ) :
await self . wait_until_ready ( )
while True :
2018-12-03 15:53:03 +00:00
await asyncio . sleep ( 1 )
for voice_client in self . voice_clients :
# Do not add play videos if something else is playing!
if not voice_client . is_connected ( ) :
continue
if voice_client . is_playing ( ) :
continue
if voice_client . is_paused ( ) :
continue
# Advance the queue
self . voice_queue . advance_queue ( )
# Try to generate an AudioSource
if self . voice_queue . now_playing is None :
continue
try :
audio_source = self . voice_queue . now_playing . make_audio_source ( )
except errors . VideoIsNotReady ( ) :
continue
# Start playing the AudioSource
logger . info ( f " Started playing { self . voice_queue . now_playing } " )
voice_client . play ( audio_source )
2018-12-03 15:53:14 +00:00
#TODO
2018-12-03 15:53:03 +00:00
2018-08-08 12:09:57 +00:00
2018-09-14 23:05:19 +00:00
async def inactivity_countdown ( self ) :
while True :
await asyncio . sleep ( 1 )
if self . inactivity_timer > 0 :
self . inactivity_timer - = 1
continue
for voice_client in self . voice_clients :
if voice_client . is_connected ( ) :
2018-09-18 23:04:48 +00:00
logger . info ( " Disconnecting due to inactivity. " )
2018-09-17 22:07:00 +00:00
await voice_client . disconnect ( )
2018-11-06 22:11:35 +00:00
await self . change_presence ( status = discord . Status . online , activity = None )
2018-09-17 22:07:00 +00:00
await self . main_channel . send ( " 💤 Mi sono disconnesso dalla cv per inattività. " )
2018-09-14 23:05:19 +00:00
2018-11-18 16:38:02 +00:00
async def create_activityreport ( self ) :
logger . debug ( " Fetching Discord users... " )
discord_users = list ( self . main_guild . members )
online_members_count = 0
ingame_members_count = 0
cv_count = 0
cv_members_count = 0
non_empty_channels = [ ]
for member in discord_users :
if member . bot :
continue
2018-11-19 23:07:53 +00:00
if member . voice is not None and member . voice . channel != self . main_guild . afk_channel :
2018-11-18 16:38:02 +00:00
cv_count + = 1
if member . voice . channel . id not in non_empty_channels :
non_empty_channels . append ( member . voice . channel . id )
if len ( member . roles ) > = 2 :
2018-11-19 23:07:53 +00:00
if member . voice is not None and member . voice . channel != self . main_guild . afk_channel :
2018-11-18 16:38:02 +00:00
cv_members_count + = 1
if member . status != discord . Status . offline and member . status != discord . Status . idle :
online_members_count + = 1
if member . activity is not None and member . activity . type == discord . ActivityType . playing :
ingame_members_count + = 1
logger . debug ( " Creating and committing db.ActivityReport... " )
session = db . Session ( )
activityreport = db . ActivityReport ( timestamp = datetime . datetime . now ( ) ,
discord_members_online = online_members_count ,
discord_members_ingame = ingame_members_count ,
discord_cv = cv_count ,
discord_members_cv = cv_members_count ,
discord_channels_used = len ( non_empty_channels ) )
session . add ( activityreport )
await loop . run_in_executor ( executor , session . commit )
await loop . run_in_executor ( executor , session . close )
logger . info ( " ActivityReport created. " )
async def activity_task ( self ) :
2018-11-18 16:42:49 +00:00
await self . wait_until_ready ( )
2018-11-18 16:55:00 +00:00
time_to_wait = int ( config [ " Discord " ] [ " activityreport_sample_time " ] )
2018-11-18 16:38:02 +00:00
while True :
await self . create_activityreport ( )
logger . debug ( f " Waiting { time_to_wait } seconds before the next record. " )
await asyncio . sleep ( time_to_wait )
2018-08-08 12:09:57 +00:00
async def add_video_from_url ( self , url , index : typing . Optional [ int ] = None , enqueuer : discord . Member = None ) :
# Retrieve info
2018-09-18 23:04:48 +00:00
logger . debug ( f " Retrieving info for { url } . " )
2018-08-08 12:09:57 +00:00
with youtube_dl . YoutubeDL ( { " quiet " : True ,
" ignoreerrors " : True ,
" simulate " : True } ) as ytdl :
info = await loop . run_in_executor ( executor ,
functools . partial ( ytdl . extract_info , url = url , download = False ) )
if info is None :
2018-09-18 23:04:48 +00:00
logger . debug ( f " No video found at { url } . " )
2018-08-08 12:09:57 +00:00
await self . main_channel . send ( f " ⚠ Non è stato trovato nessun video all ' URL ` { url } `, "
f " pertanto non è stato aggiunto alla coda. " )
return
if " entries " in info :
2018-09-18 23:04:48 +00:00
logger . debug ( f " Playlist detected at { url } . " )
2018-08-08 12:09:57 +00:00
for entry in info [ " entries " ] :
2018-11-28 15:44:34 +00:00
self . video_queue . add ( Video ( url = entry [ " webpage_url " ] , info = entry , enqueuer = enqueuer ) , index )
2018-08-08 12:09:57 +00:00
return
2018-09-18 23:04:48 +00:00
logger . debug ( f " Single video detected at { url } . " )
2018-11-28 15:44:34 +00:00
self . video_queue . add ( Video ( url = entry [ " webpage_url " ] , info = entry , enqueuer = enqueuer ) , index )
2018-05-25 17:58:30 +00:00
2018-08-08 12:09:57 +00:00
async def add_video_from_file ( self , file , index : typing . Optional [ int ] = None , enqueuer : discord . Member = None ) :
2018-11-28 15:44:34 +00:00
self . video_queue . add ( Video ( file = file , enqueuer = enqueuer ) , index )
2018-05-25 17:58:30 +00:00
2018-08-08 12:09:57 +00:00
@command
async def null ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
pass
2018-05-25 17:58:30 +00:00
2018-08-08 12:09:57 +00:00
@command
async def cmd_ping ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
await channel . send ( f " Pong! " )
2018-05-25 17:58:30 +00:00
2018-08-08 12:09:57 +00:00
@command
async def cmd_register ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
2018-07-31 21:41:05 +00:00
session = db . Session ( )
2018-08-08 12:09:57 +00:00
if len ( params ) < 1 :
await channel . send ( " ⚠️ Non hai specificato un username! \n "
" Sintassi corretta: `!register <username_ryg>` " )
2018-05-26 08:50:54 +00:00
return
2018-08-08 12:09:57 +00:00
try :
# noinspection PyTypeChecker
d = db . Discord . create ( session ,
royal_username = params [ 0 ] ,
discord_user = author )
except errors . AlreadyExistingError :
await channel . send ( " ⚠ Il tuo account Discord è già collegato a un account RYG "
" o l ' account RYG che hai specificato è già collegato a un account Discord. " )
return
session . add ( d )
session . commit ( )
session . close ( )
await channel . send ( " ✅ Sincronizzazione completata! " )
@command
async def cmd_cv ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
""" Summon the bot in the author ' s voice channel. """
if author is None :
await channel . send ( " ⚠ Questo comando richiede un autore. " )
return
if author . voice is None :
await channel . send ( " ⚠ Non sei in nessun canale! " )
return
if author . voice . channel == self . main_guild . afk_channel :
await channel . send ( " ⚠ Non posso connettermi al canale AFK! " )
return
if author . voice . channel . bitrate < 64000 :
await channel . send ( " ℹ ️ Sei in un canale con un bitrate ridotto.\n "
" L ' utilizzo del bot in quel canale ignorerà il limite di bitrate e potrebbe causare lag "
" o eccessivo consumo di dati. \n "
" Se vuoi procedere comunque, scrivi `!yes`. " )
try :
await self . wait_for ( " message " , check = lambda m : m . content == " !yes " , timeout = 10.0 )
except asyncio . TimeoutError :
return
# Check if there's already a connected client
for voice_client in self . voice_clients :
if voice_client . channel in self . main_guild . channels and voice_client . is_connected ( ) :
2018-09-18 23:04:48 +00:00
logger . info ( f " Moving to { author . voice . channel . name } . " )
2018-08-08 12:09:57 +00:00
await voice_client . move_to ( author . voice . channel )
await channel . send ( f " ⤵️ Mi sono spostato in <# { author . voice . channel . id } >. " )
break
else :
2018-09-18 23:04:48 +00:00
logger . info ( f " Connecting to { author . voice . channel . name } . " )
2018-08-08 12:09:57 +00:00
await author . voice . channel . connect ( )
await channel . send ( f " ⤵️ Mi sono connesso in <# { author . voice . channel . id } >. " )
@command
@requires_connected_voice_client
async def cmd_play ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
if len ( params ) < 2 :
await channel . send ( " ⚠ Non hai specificato una canzone da riprodurre! \n "
" Sintassi: `!play <url|ricercayoutube|nomefile>` " )
return
channel . typing ( )
2018-11-28 14:47:08 +00:00
self . next_radio_message_in - = 1
if self . next_radio_message_in < = 0 :
radio_message = random . sample ( radio_messages , 1 ) [ 0 ]
self . next_radio_message_in = self . radio_messages_every
await self . add_video_from_url ( radio_message )
await channel . send ( f " 📻 Aggiunto un messaggio radio, disattiva con `!radiomessages off`. " )
logger . info ( f " Radio message added to the queue. " )
2018-08-08 12:09:57 +00:00
# Parse the parameter as URL
url = re . match ( r " (?:https?://|ytsearch[0-9]*:).* " , " " . join ( params [ 1 : ] ) . strip ( " <> " ) )
if url is not None :
# This is a url
await self . add_video_from_url ( url . group ( 0 ) , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Added { url } to the queue as URL. " )
2018-08-08 12:09:57 +00:00
return
# Parse the parameter as file
file_path = os . path . join ( os . path . join ( os . path . curdir , " opusfiles " ) , " " . join ( params [ 1 : ] ) )
if os . path . exists ( file_path ) :
# This is a file
await self . add_video_from_file ( file = file_path , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Added { file_path } to the queue as file. " )
2018-08-08 12:09:57 +00:00
return
file_path + = " .opus "
if os . path . exists ( file_path ) :
# This is a file
await self . add_video_from_file ( file = file_path , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Added { file_path } to the queue as file. " )
2018-08-08 12:09:57 +00:00
return
# Search the parameter on youtube
search = " " . join ( params [ 1 : ] )
# This is a search
await self . add_video_from_url ( url = f " ytsearch: { search } " , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Added ytsearch: { search } to the queue as YouTube search. " )
2018-08-08 12:09:57 +00:00
@command
@requires_connected_voice_client
async def cmd_skip ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
for voice_client in self . voice_clients :
if voice_client . is_playing ( ) :
voice_client . stop ( )
await channel . send ( f " ⏩ Video saltato. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " A song was skipped. " )
2018-08-08 12:09:57 +00:00
break
else :
await channel . send ( " ⚠ Non c ' è nessun video in riproduzione. " )
2018-08-01 23:11:43 +00:00
2018-08-08 12:09:57 +00:00
@command
@requires_connected_voice_client
async def cmd_remove ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
if len ( self . video_queue ) == 0 :
await channel . send ( " ⚠ Non c ' è nessun video in coda. " )
return
if len ( params ) == 1 :
index = len ( self . video_queue ) - 1
else :
try :
index = int ( params [ 1 ] ) - 1
except ValueError :
await channel . send ( " ⚠ Il numero inserito non è valido. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
if len ( params ) < 3 :
if abs ( index ) > = len ( self . video_queue ) :
await channel . send ( " ⚠ Il numero inserito non corrisponde a nessun video nella playlist. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
2018-11-28 15:44:34 +00:00
video = self . video_queue . list . pop ( index )
2018-08-08 12:09:57 +00:00
await channel . send ( f " :regional_indicator_x: { str ( video ) } è stato rimosso dalla coda. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Removed from queue: { video . plain_text ( ) } " )
2018-08-08 12:09:57 +00:00
return
try :
start = int ( params [ 1 ] ) - 1
except ValueError :
await channel . send ( " ⚠ Il numero iniziale inserito non è valido. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
if start > = len ( self . video_queue ) :
await channel . send ( " ⚠ Il numero iniziale inserito non corrisponde a nessun video nella "
" playlist. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
try :
end = int ( params [ 2 ] ) - 2
except ValueError :
await channel . send ( " ⚠ Il numero finale inserito non è valido. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
if end > = len ( self . video_queue ) :
await channel . send ( " ⚠ Il numero finale inserito non corrisponde a nessun video nella "
" playlist. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
if start > end :
await channel . send ( " ⚠ Il numero iniziale è maggiore del numero finale. \n "
" Sintassi: `!remove [numerovideoiniziale] [numerovideofinale]` " )
return
2018-11-28 15:44:34 +00:00
del self . video_queue . list [ start : end ]
2018-08-08 12:09:57 +00:00
await channel . send ( f " :regional_indicator_x: { end - start } video rimossi dalla coda. " )
2018-09-18 23:04:48 +00:00
logger . debug ( f " Removed from queue { end - start } videos. " )
2018-08-08 12:09:57 +00:00
@command
async def cmd_queue ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
if len ( self . video_queue ) == 0 :
await channel . send ( " **Video in coda:** \n "
" nessuno " )
return
msg = " **Video in coda:** \n "
2018-11-28 15:44:34 +00:00
for index , video in enumerate ( self . video_queue . list [ : 10 ] ) :
2018-08-08 12:09:57 +00:00
msg + = f " { queue_emojis [ index ] } { str ( video ) } \n "
if len ( self . video_queue ) > 10 :
msg + = f " più altri { len ( self . video_queue ) - 10 } video! "
await channel . send ( msg )
@command
@requires_connected_voice_client
async def cmd_shuffle ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
if len ( self . video_queue ) == 0 :
await channel . send ( " ⚠ Non ci sono video in coda! " )
return
2018-11-28 15:44:34 +00:00
random . shuffle ( self . video_queue . list )
2018-09-18 23:04:48 +00:00
logger . info ( f " The queue was shuffled by { author . name } # { author . discriminator } . " )
2018-08-08 12:09:57 +00:00
await channel . send ( " ♠️ ♦️ ♣️ ♥️ Shuffle completo! " )
@command
@requires_connected_voice_client
async def cmd_clear ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
if len ( self . video_queue ) == 0 :
await channel . send ( " ⚠ Non ci sono video in coda! " )
return
2018-11-28 15:44:34 +00:00
# TODO
raise NotImplementedError ( )
2018-09-18 23:04:48 +00:00
logger . info ( f " The queue was cleared by { author . name } # { author . discriminator } . " )
2018-08-08 12:09:57 +00:00
await channel . send ( " :regional_indicator_x: Tutti i video in coda rimossi. " )
@command
@requires_connected_voice_client
async def cmd_forceplay ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
if len ( params ) < 2 :
await channel . send ( " ⚠ Non hai specificato una canzone da riprodurre! \n "
" Sintassi: `!forceplay <url|ricercayoutube|nomefile>` " )
return
for voice_client in self . voice_clients :
if voice_client . is_playing ( ) :
voice_client . stop ( )
2018-09-18 23:04:48 +00:00
logger . info ( f " Video play was forced by { author . name } # { author . discriminator } . " )
2018-08-08 12:09:57 +00:00
# Parse the parameter as URL
url = re . match ( r " (?:https?://|ytsearch[0-9]*:).* " , " " . join ( params [ 1 : ] ) . strip ( " <> " ) )
if url is not None :
# This is a url
2018-09-18 23:04:48 +00:00
await self . add_video_from_url ( url . group ( 0 ) , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
logger . debug ( f " Forced { url } as URL. " )
2018-08-08 12:09:57 +00:00
return
# Parse the parameter as file
file_path = os . path . join ( os . path . join ( os . path . curdir , " opusfiles " ) , " " . join ( params [ 1 : ] ) )
if os . path . exists ( file_path ) :
# This is a file
2018-09-18 23:04:48 +00:00
await self . add_video_from_file ( file = file_path , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
logger . debug ( f " Forced { file_path } as file. " )
2018-08-08 12:09:57 +00:00
return
file_path + = " .opus "
if os . path . exists ( file_path ) :
# This is a file
2018-09-18 23:04:48 +00:00
await self . add_video_from_file ( file = file_path , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
logger . debug ( f " Forced { file_path } as file. " )
2018-08-08 12:09:57 +00:00
return
# Search the parameter on youtube
search = " " . join ( params [ 1 : ] )
# This is a search
2018-09-18 23:04:48 +00:00
await self . add_video_from_url ( url = f " ytsearch: { search } " , enqueuer = author )
await channel . send ( f " ✅ Video aggiunto alla coda. " )
logger . debug ( f " Forced ytsearch: { search } as YouTube search. " )
2018-08-08 12:09:57 +00:00
@command
async def cmd_radiomessages ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
2018-11-28 14:47:08 +00:00
if not self . radio_messages_enabled :
await channel . send ( " ⚠ I messaggi radio sono stati disabilitati dall ' amministratore del bot. " )
return
2018-08-08 12:09:57 +00:00
if len ( params ) < 2 :
2018-11-28 14:47:08 +00:00
await channel . send ( " ⚠ Sintassi del comando non valida. \n "
" Sintassi: `!radiomessages <on|off>` " )
2018-08-08 12:09:57 +00:00
else :
if params [ 1 ] . lower ( ) == " on " :
2018-11-28 14:47:08 +00:00
self . radio_messages_next_in = self . radio_messages_every
2018-08-08 12:09:57 +00:00
elif params [ 1 ] . lower ( ) == " off " :
2018-11-28 14:47:08 +00:00
self . radio_messages_next_in = math . inf
2018-08-08 12:09:57 +00:00
else :
await channel . send ( " ⚠ Sintassi del comando non valida. \n "
2018-11-28 14:47:08 +00:00
" Sintassi: `!radiomessages <on|off>` " )
2018-08-08 12:09:57 +00:00
return
2018-11-28 14:47:08 +00:00
logger . info ( f " Radio messages status to { ' enabled ' if self . radio_messages . next_in < math . inf else ' disabled ' } . " )
await channel . send ( f " 📻 Messaggi radio ** { ' attivati ' if self . radio_messages . next_in < math . inf else ' disattivati ' } **. " )
2018-06-13 22:10:57 +00:00
2018-08-16 17:33:38 +00:00
@command
@requires_connected_voice_client
async def cmd_pause ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
2018-08-17 13:09:03 +00:00
for voice_client in self . voice_clients :
if voice_client . is_playing ( ) :
voice_client . pause ( )
2018-09-18 23:04:48 +00:00
logger . debug ( f " The audio stream was paused. " )
2018-08-17 13:09:03 +00:00
await channel . send ( f " ⏸ Riproduzione messa in pausa. \n "
f " Riprendi con `!resume`. " )
@command
@requires_connected_voice_client
async def cmd_resume ( self , channel : discord . TextChannel , author : discord . Member , params : typing . List [ str ] ) :
for voice_client in self . voice_clients :
2018-10-04 10:46:15 +00:00
if voice_client . is_paused ( ) :
2018-08-17 13:09:03 +00:00
voice_client . resume ( )
2018-09-18 23:04:48 +00:00
logger . debug ( f " The audio stream was resumed. " )
2018-08-17 13:09:03 +00:00
await channel . send ( f " ⏯ Riproduzione ripresa. " )
2018-08-16 17:33:38 +00:00
2018-01-15 12:42:40 +00:00
def process ( users_connection = None ) :
2018-08-16 17:46:07 +00:00
logger . info ( " Initializing the bot... " )
2018-08-07 23:30:43 +00:00
bot = RoyalDiscordBot ( )
2018-01-15 12:42:40 +00:00
if users_connection is not None :
2018-08-16 17:46:07 +00:00
logger . info ( " Initializing Telegram-Discord connection... " )
2018-08-07 23:30:43 +00:00
asyncio . ensure_future ( bot . feed_pipe ( users_connection ) )
2018-08-16 17:46:07 +00:00
logger . info ( " Logging in... " )
2018-11-28 14:47:08 +00:00
loop . run_until_complete ( bot . login ( bot . token , bot = True ) )
2018-08-16 17:46:07 +00:00
logger . info ( " Connecting... " )
2018-09-17 22:07:00 +00:00
loop . run_until_complete ( bot . connect ( ) )
logger . info ( " Now stopping... " )
loop . run_until_complete ( bot . logout ( ) )
2018-01-15 12:42:40 +00:00
if __name__ == " __main__ " :
2018-01-25 14:30:07 +00:00
process ( )