Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ###############################################################################
- # Name: Youtube Title V2
- # Author: Jan Milants <[email protected]>
- # Version: 2.1 (22/08/2016)
- # Eggdrop Version: 1.6.x
- # Requires TCL version: 8.5
- # Package dependencies: http, tls, json
- # Credits: Based on original YouTube Script
- # by [email protected]
- # Design inspiration from
- # youtube.tcl by Mookie.
- ###############################################################################
- # Description
- # -------------
- # La escritura supervisa canales de texto para relaciones a Youtube.
- # Cuando encontrado, esto preguntará al servidor google para detalles, como el título de vídeo
- # y el número de visiones del vídeo youtube y anuncian los resultados en el canal.
- # La escritura también apoya la busca youtube con la orden, !youtube <texto a buscar>
- # o "!youtube <texto a buscar>". Opcionalmente, el número de resultados de la búsqueda deseados puede ser
- # especificando la utilización"!youtube + <el número de resultados> <buscan el texto>".
- # En la respuesta, la pregunta de búsqueda será pasada al youtube API y el mejor
- # el resultado (s) que está será unido en el canal.
- #
- # Esta escritura será activa en cualquier canal en el cual ella resida con el "youtube" flag.
- # existe dos formas en activar esta tcl una mediante el partylien ejemplo ".chanset #tu-canal +youtube" y la otra
- # forma es activando en el canal abierto ejemplo !youtube on
- # en ambos comanodos solo puede ser activada por el usuario que tenga los flags mno.
- #
- # Esta escritura pone en práctica una protección de antiflood, muy básica para búsquedas de YouTube,
- # requiriendo a usuarios, o mejor dicho todos los clientes con mismo userhost, esperar
- # un intervalo predeterminado antes de que ellos puedan buscar otra vez.
- # Nota que esto no aplica a YouTube URLs pegado en el canal.
- #
- # Getting your own Google API key
- # ---------------------------------
- # This script uses the Google Youtube Data API V3, which requires an API key to
- # authorize access to the API and is used as basis for limiting request per day etc.
- # Instructions can be found on
- # https://developers.google.com/youtube/registering_an_application
- # Required steps in short:
- # 1. Go to the Google developers console https://console.developers.google.com
- # 2. Create a new project. Give it a name like 'eggdrop', doesn't matter much.
- # 3. When the project is loaded, select menu "Library" under "API Manager"
- # This should load the list of available Google APIs on the right.
- # 4. In the list of APIs, select the "YouTube Data API".
- # This should load a page describing the "YouTube Data API v3".
- # 5. Enable the API for you application by clicking "Enable" next to the API name.
- # 6. Select menu "Credentials" and click "Create credentials".
- # 7. Select type "API key" type "server key".
- # 8. Optionally, fill in the IP(s) or IP range from which the eggdrop bot will send
- # requests to google's servers. This is a whitelist, if the request
- # comes from a different IP, it will be rejected.
- # Note: If my-ip or my-hostname is configured in eggdrop.conf, they should
- # be entered here.
- # 9. Click "Create".
- # You now have an API KEY; copy it to the config section below.
- #
- # !!! IMPORTANT !!!
- # When loading this script alongside other scripts which initiate web service calls,
- # ensure this script is loaded last! The script creates a handler for HTTPS connection
- # and sets the source IP to the my-ip or my-hostname from eggdrop.conf.
- # Most other scripts will not correctly enforce the source IP of requests and
- # can overwrite this scripts HTTPS handler. This results in connections coming from another
- # IP on the machine and may thus be rejected by the Google API Servers.
- # The typical error message logged would be "Error processing web service reply".
- #
- ###############################################################################
- # Changes:
- # 2.10 22/08/16
- # Added a default API key since IPs don't need to be individually whitelisted anymore.
- # Added support for YouTube searches returning multiple search results.
- # Added basic anti-flood measures by implementing a minimum interval between searches.
- # Updated dependency checking to be non-fatal to the hosting bot. (Suggested by caesar.)
- # Updated documentation for creating your own API key to match new dev console.
- # Fixed the socket not enabling TLS usage on some systems. (Fix suggested by Madalin.)
- # 2.00 28/08/14
- # Started development (Jan).
- # Almost complete rewrite most notable changes:
- # * Use the YouTube Data API V3. (Requires TLS support!)
- # * Strip out flat_json_decoder and use json & dict packages instead.
- # * Strip out tinyurl support. Better to use youtu.be in the response.
- # * Many more data elements supported in response format (possible by new API).
- # * Added possiblity to turn the script on/off on a channel by channel basis.
- # * Added ability to search youtube and return the first result.
- # 0.51 09/30/13
- # Small correction for caps in url (but not video id)
- # 0.5 01/02/09
- # Added better error reporting for restricted youtube content.
- # 0.4 10/11/09
- # Changed title scraping method to use the oembed api.
- # Added crude JSON decoder library.
- # 0.3 02/03/09
- # Fixed entity decoding problems in return titles.
- # Added customisable response format.
- # Fixed rare query string bug.
- ###############################################################################
- #
- # Configuration
- #
- ###############################################################################
- # API key assigned to your Google account.
- # Find detailed instructions for getting your own API key above or use mine.
- set youtube(api_key) "AIzaSyBVmqrzuW_CW0wc6qwzS6KEqrynY4mr2mw"
- # Base URI for links to YouTube videos.
- # Either use the normal YouTube link or youtu.be for shorter URLs.
- # I'd recommend keeping the HTTPS to avoid exposing user data.
- set youtube(base_url) "https://www.youtube.com/watch?v="
- #set youtube(base_url) "https://www.youtu.be/"
- # Date/time format
- # The format to be used when showing dates, for example in publish date.
- # All times are in UTC.
- #
- # Available tokens:
- # %year% 4 digit year notation
- # %month% 2 digit month notation
- # %day% 2 digit day of the month notation
- # %hours% 2 digit hour notation on a 24hours basis
- # %minutes% 2 digit minutes notation
- # %seconds% 2 digit seconds notation
- #
- # Example:
- # "%day%/%month%/%year% %hours%:%minutes% UTC"
- set youtube(date_format) "%day%/%month%/%year% %hours%:%minutes% UTC"
- # Response Formats
- # Template of the reply to be send to the channel showing the YouTube video details.
- # A separate response can be set for replies to a pasted URL or to a query.
- #
- # Available tokens in the response format:
- # %botnick% Nickname of bot
- # %poster% Nickname of person who posted the YouTube link
- # %youtube_url% URL to the YouTube link (This may not be the exact same
- # URL that was posted since it's rewritten based on the format
- # above to ensure all links posted by the bot are HTTPS.)
- # %id% ID of the linked YouTube video.
- # %author% Author/Uploader/channel of the video.
- # %title% Title of YouTube link
- # %description% Description of the video.
- # (Note that this is generally a VERY long text!)
- # %published% Date & time the video was published.
- # %views% The number of times the video has been viewed.
- # %likes% The number of users who have "liked" the video.
- # %dislikes% The number of users who have "disliked" the video.
- # %length% Length of the video.
- # Tokens only available in the response to searches (q_resp_format):
- # %query% The original search string.
- # %res_nbr% The index of the search result being shown.
- # %res_tot% Total number of search results.
- #
- # Example:
- # "\002YouTube\002: %poster%: %youtube_url% - \"\002%title%\002\" (Uploaded by \"%author%\" on %published%) - Length: %length% - Views: %views% - Likes / Dislikes: %likes% / %dislikes%"
- # The template used when looking up a URL found in the channel
- set youtube(response_format) "\002\00301,00You\00300,04Tube\003\002: %poster%: %youtube_url% - \"\002%title%\002\" (Cargado por \"%author%\" en fecha de %published%) - duracion: %length% - total veces visto: %views% - le gustan / no les gustan: %likes% / %dislikes%"
- # The template used when replying to a search query.
- set youtube(q_resp_format) "\002\00301,00You\00300,04Tube\003\002: %poster%: Resultado(s) %res_nbr%/%res_tot% para la busqueda '%query%': %youtube_url% - \"\002%title%\002\" (Cargado por \"%author%\" en fecha de %published%) - duracion: %length% - total veces visto: %views% - le gustan / no les gustan: %likes% / %dislikes%"
- # The maximum number of characters from a YouTube title to print.
- set youtube(max_title_length) 64
- # The maximum number of characters from a YouTube description to print.
- set youtube(max_desc_length) 128
- # numeros de resultados para busqueda en youtube web
- set youtube(search_res_def) 4
- # The maximum number of search results that can be requested with a YouTube search.
- set youtube(search_res_max) 3
- # The minimum interval (in seconds) a user must wait before he can execute another search query.
- # Set to 0 to disable interval checking.
- set youtube(search_int_min) 15
- ###############################################################################
- #
- # Advanced Configuration
- # !!! DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING !!!
- #
- ###############################################################################
- # URLs of the YouTube V3 API
- set youtube(api_get) "https://www.googleapis.com/youtube/v3/videos"
- set youtube(api_search) "https://www.googleapis.com/youtube/v3/search"
- # The groups of properties to be fetched
- set youtube(api_part) "snippet,statistics,contentDetails"
- # The fields from the selected property groups that are to be returned
- set youtube(api_fields) "items(id,snippet(publishedAt,title,description,channelTitle),statistics,contentDetails(duration))"
- # Maximum time in milliseconds to wait for YouTube to respond
- set youtube(api_timeout) "30000"
- # Pattern used to patch YouTube links in channel public text
- set youtube(pattern) {https{0,1}://.*youtu(?:\.be/|be\..*/watch\?(?:.*)v=)([A-Za-z0-9_\-]+)}
- ###############################################################################
- variable packages
- # Check for all dependencies without killing the bot that's trying to load the script.
- if { [catch {package require Tcl 8.5} error] } { set "packages(Tcl 8.5)" $error }
- if { [catch {package require http 2.7} error] } { set "packages(http 2.7)" $error }
- if { [catch {package require tls} error] } { set packages(tls) $error }
- if { [catch {package require json} error] } { set packages(json) $error }
- # If any dependencies failed loading, print the error and abort.
- if { [array size packages] > 0 } {
- foreach index [array names packages] {
- putlog "% YoutubeTitleV2: Error que carga paquete '$index' : $packages($index)"
- }
- putlog "% YoutubeTitleV2: Carga abortó (Ausencia de dependencias)!"
- return
- }
- ###############################################################################
- # We need HTTPS support for the Google APIs..
- # If local IP or host is configured in the main config, use it as the source
- # of the outgoing connections.
- if { [info exists {my-ip}] == 1 && [string length ${my-ip}] > 0} {
- http::register https 443 [list tls::socket -myaddr ${my-ip} -tls1 1]
- } elseif { [info exists {my-hostname}] == 1 && [string length ${my-hostname}] > 0} {
- http::register https 443 [list tls::socket -myaddr ${my-hostname} -tls1 1]
- } else {
- http::register https 443 [list tls::socket -tls1 1]
- }
- set YoutubeTitleVersion "2.1"
- # Keep a dictionary of the user hosts from where search queries are currently
- # ignored by the script.
- set yt_search_ignore [dict create]
- setudef flag youtube
- bind pubm - * public_youtube
- bind pub - !youtube youtube_query
- bind pub - !yt youtube_query
- bind pub - .youtube youtube_query
- bind pub - .yt youtube_query
- ###############################################################################
- proc note {msg} {
- putlog "% $msg"
- }
- # Ensure strings are no longer then given length. This will cut-off the string
- # at the desired length and append '...'.
- proc shorten {text maxlen} {
- if { [string length $text] > [expr $maxlen - 1] } {
- set text [string range $text 0 [expr $maxlen - 4]]"..."
- }
- return $text
- }
- # Convert an ISO8601 date into a more readable format..
- proc conv_iso8601_date {orig_date} {
- global youtube
- set pattern {(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d{1}|3[012])[T\s](?:(?:([01]\d|2[0-3]):([0-5]\d))|(24):(00)):(?:([0-5]\d)(?:[\.,](\d+))?|(60)(?:[\.,](0+))?)Z}
- if { [regexp $pattern $orig_date match year month day hours minutes hours_2 minutes_2 seconds milliseconds seconds_2 milliseconds_2] } {
- # The hour and hour_2 variables are mutually exclusive, so we append the _2 variables to
- # the original ones to have fewer variables to work with.
- append hours $hours_2
- append minutes $minutes_2
- append seconds $seconds_2
- # Put everything in a dictionary so we can have a configurable time format.
- set tokens [dict create]
- dict set tokens %year% $year
- dict set tokens %month% $month
- dict set tokens %day% $day
- dict set tokens %hours% $hours
- dict set tokens %minutes% $minutes
- dict set tokens %seconds% $seconds
- return [string map $tokens $youtube(date_format)]
- } else {
- error "Incapaz de tratar valor de fecha ($orig_date) devuelto por servicio web."
- }
- }
- # Convert an ISO8601 duration into a more readable format..
- proc conv_iso8601_duration {duration} {
- set length ""
- set pattern {P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)}
- if { [regexp $pattern $duration match years months days hours minutes seconds] } {
- if { [string length $years] > 0 } {
- append length $years Y " "
- }
- if { [string length $months] > 0 } {
- append length $months M " "
- }
- if { [string length $days] > 0 } {
- append length $days D " "
- }
- if { [string length $hours] > 0 } {
- append length $hours h " "
- }
- if { [string length $minutes] > 0 } {
- append length $minutes m " "
- }
- if { [string length $seconds] > 0 } {
- append length $seconds s " "
- }
- } else {
- error "Incapaz de tratar valor de duración ($duration) devuelto por servicio web."
- }
- return [string trim $length]
- }
- ###############################################################################
- # Process the reply string of a video lookup from the YouTube API (JSON) and add the
- # data to a dictionary containing all tokens the user will be able to use in the template.
- proc read_props {json_blob} {
- global youtube
- # Create an empty dictionary for the variables supported in the response format.
- set properties [dict create]
- # Convert the JSON response to a dictionary.
- set reply [json::json2dict $json_blob]
- # The web service returns a list of results, even though our query will always get 1.
- # So we have to take the first element from the list..
- if { ![dict exists $reply items] } {
- error "Error processing web service reply."
- } else {
- set video [lindex [dict get $reply items] 0]
- # Check whether the variables we support in the response are present in the
- # reply from the web service. We check this one by one instead of assuming
- # they exist in case the API changes or someone messes with the requested fields.
- # Properties of the view we extract from reply..
- if { [dict exists $video id] } {
- dict set properties %id% [dict get $video id]
- } else {
- dict set properties %id% ""
- }
- if { [dict exists $video snippet channelTitle] } {
- dict set properties %author% [dict get $video snippet channelTitle]
- } else {
- dict set properties %author% ""
- }
- if { [dict exists $video snippet title] } {
- dict set properties %title% [shorten "[dict get $video snippet title]" $youtube(max_title_length)]
- } else {
- dict set properties %title% ""
- }
- if { [dict exists $video snippet description] } {
- dict set properties %description% [shorten "[dict get $video snippet description]" $youtube(max_desc_length)]]
- } else {
- dict set properties %description% ""
- }
- if { [dict exists $video snippet publishedAt] } {
- dict set properties %published% [conv_iso8601_date [dict get $video snippet publishedAt]]
- } else {
- dict set properties %published% ""
- }
- if { [dict exists $video statistics viewCount] } {
- dict set properties %views% [dict get $video statistics viewCount]
- } else {
- dict set properties %views% ""
- }
- if { [dict exists $video statistics likeCount] } {
- dict set properties %likes% [dict get $video statistics likeCount]
- } else {
- dict set properties %likes% ""
- }
- if { [dict exists $video statistics dislikeCount] } {
- dict set properties %dislikes% [dict get $video statistics dislikeCount]
- } else {
- dict set properties %dislikes% ""
- }
- if { [dict exists $video contentDetails duration] } {
- dict set properties %length% [conv_iso8601_duration [dict get $video contentDetails duration]]
- } else {
- dict set properties %length% ""
- }
- }
- return $properties
- }
- # Process the reply string of a search query to the YouTube API (JSON) and extract the
- # video id of the results from the reply.
- proc read_searchres {json_blob} {
- global youtube
- # Create an empty list containing the video IDs returned for the search query.
- list video_ids
- # Convert the JSON response to a dictionary.
- set reply [json::json2dict $json_blob]
- # The web service returns a list of results, even though our query will always get 1.
- # So we have to take the first element from the list..
- if { ![dict exists $reply items] } {
- error "Error processing web service reply."
- } else {
- foreach res [dict get $reply items] {
- if { [dict exists $res id videoId] } {
- lappend video_ids [dict get $res id videoId]
- }
- }
- }
- return $video_ids
- }
- # Send a request to the YouTube API to fetch the video details for
- # the video with the given ID.
- proc fetch_props {youtube_id} {
- global youtube
- # Ensure an API key has been configured..
- if { [info exists youtube(api_key)] == 0 || [string length $youtube(api_key)] == 0 } {
- error "An API key must be configured to access the Google web API!"
- } else {
- set query [http::formatQuery id $youtube_id key $youtube(api_key) \
- part $youtube(api_part) fields $youtube(api_fields)]
- set response [http::geturl "$youtube(api_get)?$query" -timeout $youtube(api_timeout)]
- upvar #0 $response state
- if [expr [http::ncode $response] == 401] {
- error "La posición contuvo restringido empotran datos."
- } else {
- set response_body [http::data $response]
- http::cleanup $response
- return [read_props $response_body]
- }
- }
- }
- # Find the video ID of the first match for the given search.
- proc search_video {criteria results} {
- global youtube
- # Ensure an API key has been configured..
- if { [info exists youtube(api_key)] == 0 || [string length $youtube(api_key)] == 0 } {
- error "An API key must be configured to access the Google web API!"
- } else {
- set query [http::formatQuery type "video" q $criteria key $youtube(api_key) \
- part "id" fields "items(id(videoId))" maxResults "$results"]
- set response [http::geturl "$youtube(api_search)?$query" -timeout $youtube(api_timeout)]
- upvar #0 $response state
- if [expr [http::ncode $response] == 401] {
- error "Location contained restricted embed data."
- } else {
- set response_body [http::data $response]
- http::cleanup $response
- return [read_searchres $response_body]
- }
- }
- }
- ###############################################################################
- # This is triggered to analyse ever channel message for the presence of the YouTube URL.
- # When one is found, the ID is extracted and passed on to get a list of the video properties.
- # Finally, this list is used to fill in the tokens in the user defined reply template.
- proc public_youtube {nick userhost handle channel args} {
- global youtube botnick
- if { [channel get $channel youtube] && [regexp -nocase -- $youtube(pattern) $args match video_id] } {
- if { [catch {set tokens [fetch_props $video_id]} error] } {
- note "Failed to get video details: $error (querying '$video_id')"
- # If the reply contained an empty ID, we assume we found no video..
- } elseif { [string length [dict get $tokens %id%]] == 0 } {
- putserv "PRIVMSG $channel :Incapaz de encontrar un vídeo youtube con ID '$video_id'."
- } else {
- dict set tokens %botnick% $botnick
- dict set tokens %poster% $nick
- # Rebuild the URL so we use a url shortener or force SSL
- # in all messages coming from us
- dict set tokens %youtube_url% "$youtube(base_url)$video_id"
- set result [string map $tokens $youtube(response_format)]
- putserv "PRIVMSG $channel :$result"
- }
- }
- }
- # This is triggered on !youtube commands.
- # Allows turning monitoring on or off by admins.
- # All other queries are interpreted as a youtube search.
- proc youtube_query {nick userhost handle channel largs} {
- global youtube botnick yt_search_ignore
- # We get a list of arguments, join it to get rid of the curly braces..
- set args [join $largs]
- if { [string length $args] == 0 } {
- if { [channel get $channel youtube] } {
- putserv "PRIVMSG $channel :Syntax: \002!youtube <search criteria>\002 - Search for a video showing the top $youtube(search_res_def) result(s)."
- putserv "PRIVMSG $channel :Syntax: \002!youtube +<number of results> <search criteria>\002 - Search for a video showing the top results. (Shows max $youtube(search_res_max) result(s).)"
- }
- if { [matchattr $handle +mno|+mno $channel] } {
- putserv "NOTICE $nick :Syntax: \002!youtube <on/off>\002 - Turn youtube link lookups on/off."
- }
- } elseif { [matchattr $handle +mno|+mno $channel] && ([string compare $args "on"] == 0 \
- || [string compare $args "off"] == 0) } {
- if { ![channel get $channel youtube] && [string compare $args "on"] == 0 } {
- channel set $channel +youtube
- putserv "NOTICE $nick :YoutubeTitleV2: activado en $channel"
- note "YoutubeTitleV2: Monitoring enabled by $nick for $channel."
- } elseif { [channel get $channel youtube] && [string compare $args "off"] == 0 } {
- channel set $channel -youtube
- putserv "NOTICE $nick :YoutubeTitleV2: disabled on $channel"
- note "YoutubeTitleV2: Monitoring disabled by $nick for $channel."
- }
- } elseif { [channel get $channel youtube] } {
- # Stores the number of search results to be fetched.
- set nbr $youtube(search_res_def)
- # Check if the user requested a specific number of search results.
- if { [string first + $args] == 0 && [string is digit [string range [lindex $largs 0] 1 end]] == 1 } {
- set nbr [string range [lindex $largs 0] 1 end]
- set args [join [lrange $largs 1 end]]
- }
- # Ensure no more then the maximum number of search results is fetched
- if {$nbr > $youtube(search_res_max) } {
- set nbr $youtube(search_res_max)
- }
- # The magic number comes from the length of the string "cat".. ;)
- if { [string length $args] < 3 } {
- putserv "PRIVMSG $channel :Search criteria must be at least 3 characters long."
- return 0
- }
- # First verify whether the user isn't on ignore..
- if { $youtube(search_int_min) > 0 } {
- # Don't just check for the existence of the key, but also verify the expiry time
- # set, in case for some reason the key didn't get removed when it should have been..
- if { [dict exists $yt_search_ignore $userhost] && [dict get $yt_search_ignore $userhost] > [unixtime] } {
- putserv "NOTICE $nick :YoutubeTitleV2: Please wait $youtube(search_int_min) seconds between searches."
- return 0
- }
- # Add the user host to the YouTube search ignore list to ignore any queries for X seconds.
- dict set yt_search_ignore $userhost [expr [unixtime] + $youtube(search_int_min)]
- # Set a timer to remove the user host from the list after X seconds.
- utimer $youtube(search_int_min) [list dict unset yt_search_ignore $userhost]
- }
- # Search a video...
- # Note that we have to do 2 requests: one to fetch search results (id)
- # and a second to get video details. This is caused by the YouTube API
- # not being capable of returning details in the search functions.
- if { [catch { set video_ids [search_video $args $nbr] } error ] } {
- note "Failed to find a video: $error (searching for '$args')."
- # If the reply contained an empty ID, we assume we found no video..
- } elseif { [llength $video_ids] == 0 } {
- putserv "PRIVMSG $channel :Unable to find a YouTube video matching search '$args'"
- } else {
- # Go over each video ID returned by our search query..
- set index 1
- foreach video_id $video_ids {
- # We have the video id, now find the properties..
- if { [catch {set tokens [fetch_props $video_id]} error] } {
- note "Failed to get video details: $error (searching for '$args' and found '$video_id')."
- # If the reply contained an empty ID, we assume we found no video..
- } elseif { [string length [dict get $tokens %id%]] == 0 } {
- putserv "PRIVMSG $channel :Unable to fetch the video details of '$video_id' for the search result '$args'"
- } else {
- dict set tokens %botnick% $botnick
- dict set tokens %poster% $nick
- dict set tokens %query% $args
- dict set tokens %res_nbr% $index
- dict set tokens %res_tot% [llength $video_ids]
- # Rebuild the URL so we use a url shortener or force SSL
- # in all messages coming from us
- dict set tokens %youtube_url% "$youtube(base_url)$video_id"
- set result [string map $tokens $youtube(q_resp_format)]
- putserv "PRIVMSG $channel :$result"
- }
- incr index
- }
- }
- }
- }
- ###############################################################################
- note "YoutubeTitleV2 Version $YoutubeTitleVersion: loaded";
- hace 2 aÑos
Add Comment
Please, Sign In to add comment