# antsy's cryptocurrency price check script
package require Tcl
package require http
package require json
package require tls
package require tls 1.6.7
namespace eval cryptocheck {
# Eggdrop bindings
bind pub - !crypto [namespace current]::cryptocheckpub
bind msg f !crypto [namespace current]::cryptocheckmsg
# Global variables
set ccVersion "Cryptocurrency price check script 1.0"
set outputCurrency "eur"
set outputSymbol "€"
putlog "Loaded script $ccVersion"
##
# Handler for calling the script via public message
#
# @param nick User nickname
# @param host User hostname
# @param handle User in bot
# @param channel Channel
# @param text Parameters
proc cryptocheckpub {nick host handle channel text} {
putlog "Cryptocurrency check: $nick ($handle/$host) requested \[$text\] at $channel"
if {$text == ""} {
variable ccVersion
msg $channel "$ccVersion - Usage: !crypto <bitcoin/ethereum/ripple/etc...> (you can query multiple, separated by space)"
return
}
# Clean input
set text [string trim $text]
set currency [split $text " "]
set output [fetch $currency]
putlog $output
set output [prettyPrint $output]
putlog $output
set length [string length $output]
if {$length > 1} {
msg $channel $output
}
}
##
# Handler for calling the script via public message
#
# @param nick User nickname
# @param uhost User hostname
# @param handle User in bot
# @param text Parameters
proc cryptocheckmsg {nick uhost handle text} {
putlog "Cryptocurrency check: $nick ($handle!$uhost) requested \[$text\] as privmsg"
if {$text == ""} {
variable ccVersion
msg $nick "$ccVersion - Usage: !crypto <bitcoin/ethereum/ripple/etc...> (you can query multiple, separated by space)"
return
}
# Clean input
set text [string trim $text]
set currency [split $text " "]
set output [fetch $currency]
set output [prettyPrint $output]
set length [string length $output]
if {$length > 1} {
msg $nick $output
}
}
##
# Send response to user.
# Separates output to multiple lines if the message is too long (for IRCnet)
#
# @param dest Target, can be nick or channel.
# @param data The message.
proc msg {dest data} {
set len [expr {512-[string len ":$::botname PRIVMSG $dest :\r\n"]}]
foreach line [wordwrap $data $len] {
puthelp "PRIVMSG $dest :$line"
}
}
proc wordwrap {data len} {
set out {}
foreach line [split [string trim $data] \n] {
set curr {}
set i 0
foreach word [split [string trim $line]] {
if {[incr i [string len $word]]>$len} {
lappend out [join $curr]
set curr [list $word]
set i [string len $word]
} {
lappend curr $word
}
incr i
}
if {[llength $curr]} {
lappend out [join $curr]
}
}
set out
}
##
# Format the result data
#
# @param coinData Result data Dictionary from Coingecko
# @return Message formatted for IRC
proc prettyPrint { data } {
set output [list]
variable outputCurrency
variable outputSymbol
dict for {coinName coinData} $data {
dict with coinData {
set coinValue [subst $$outputCurrency]
if {$coinValue > 0.001} {
# Round to 3 decimals unless value is really small
set coinValue [format {%0.3f} $coinValue]
}
set valueChange [formatValueChange $eur_24h_change]
set outdatedWarning [checkLastUpdated $last_updated_at]
set additionalInfo [join [list " (" $valueChange "%)"] ""]
if {$outdatedWarning != ""} {
# Really it makes no sense to display 24h values if the value is old so let's display warning message there instead
set additionalInfo [join [list " (" [colorize $outdatedWarning 5] ")"] ""]
}
set str [join [list $coinName ": " [bold $coinValue] $outputSymbol $additionalInfo] ""]
lappend output $str
}
}
return [join $output " "]
}
##
# Format value change percentage
#
# @param eur_24h_change Change in the last twenty four hours
# @return Formatted string
proc formatValueChange {eur_24h_change} {
set valueChange "0.0"
if [string is double -strict $eur_24h_change] {
set valueChange [format {%+0.2f} $eur_24h_change]
}
if {$eur_24h_change > 0} {
# Value gone up, color it green
set valueChange [colorize $valueChange 3]
} else {
# Value gone down, color it red
set valueChange [colorize $valueChange 4]
}
return $valueChange
}
##
# Check if the value is too old (older than one hour)
#
# @param last_updated_at UNIX timestamp
# @return Error message
proc checkLastUpdated {last_updated_at} {
set now [clock seconds]
set oneHour 3600
set timeDiff [expr {$now - $last_updated_at}]
if {$timeDiff > $oneHour} {
set hours [expr {$timeDiff / $oneHour}]
set hours [expr {round($hours)}]
if {$hours == 1} {
set warningText "Warning! the value is hour old"
} else {
set warningText "Warning! the value is $hours hours old"
}
if {$hours > 23} {
set days [expr {$hours / 24}]
set days [expr round($days)]
if {$days == 1} {
set warningText "Warning! the value is day old"
} else {
set warningText "Warning! the value is $days days old"
}
}
return [colorize $warningText 6]
}
return ""
}
##
# Colorize string and reset back to default color
#
# @param str String to be colorized
# @param color VGA color
proc colorize {str color} {
set output "\003$color"
append output $str
append output "\003"
return $output
}
##
# Bold string
#
# @param str Input string
# @return Formatted string
proc bold {str} {
return "\002$str\002"
}
##
# Fetches the prices from the Coingecko's free API.
#
# @see https://www.coingecko.com/api/documentations/v3
#
# @param currency Currency or currencies to check
# @return {dict} Result from request
proc fetch { currency } {
variable ccVersion
variable outputCurrency
set ids [join $currency "%2C"]
set url "https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=${outputCurrency}&include_last_updated_at=true&include_24hr_change=true"
# ::http::register https 443 [list ::tls::socket -autoservername true -require 0 -request 1]
proc tls:socket args {
set opts [lrange $args 0 end-2]
set host [lindex $args end-1]
set port [lindex $args end]
# From version 1.6.7, it shouldn't be necessary to specify any ciphers. If
# that's not true, please report your experience on this page.
# [APN] I think you misunderstand the full motivation for -ssl3 false. If you leave
# it out, you will afaik land up talking SSL3 if the server does not mind SSL3.
# That's something you generally don't want. Unless 1.6.7 has completely disabled
# SSL3 support which I don't think it has.
::tls::socket -servername $host {*}$opts $host $port
#If that doesn't work, try this
::tls::socket -ssl3 false -ssl2 false -tls1 true -servername $host {*}$opts $host $port
# And if that doesn't work, try this
# ::tls::socket -ssl3 false -ssl2 false -tls1 true -tls1.2 false -tls1.1 false \
# -servername $host {*}$opts $host $port
}
# using the http package also needs this:
package require http
http::register https 443 tls:socket
set token [http::config -useragent "$ccVersion"]
set token [http::geturl $url]
set result [json::json2dict [http::data $token]]
http::cleanup $token
return $result
}
} ;#end namespace
#########################################################################################
# Name m00nie::youtube
# Description Uses youtube v3 API to search and return videos
#
# Version 3.1 - Yet more duration decoding fixes. Specific case of 00s videos less than
# an hour too. It also adds checks for videos <24 hours long (who knew)
# Hope this is the last of the duration crap
# 3.0 - Again trying to fix or cover all scenarios for how duration is returned
# in ISO format without needing to load another library. Hopefully correct..
# Also reverted some encoding changed back to 2.6 as these were added in
# error. Live stream detection should also be working again as that value
# seems to have changed.
# 2.9 - OMG the time
# 2.8 - New variable "out" to more easily change the (I think lovely ;D) YouTube
# output at the beginning of any spam in this script.
# 2.7 - Handles API problems (e.g. having an incorrect key) nicer. Also since
# v1.9 of eggdrop utf8 is nativley handled so tries to accomodate that
# within the script.
# 2.6 - Add likes to auto spam (Thanks to ComputerTech for the suggestion)
# also a small update to time formating
# 2.5 - Correctly returns results for channels rather than videos for !yt
# 2.4 - Fixing bug that meant date was again reported wrongly. Thanks
# to caesar for suggesting the fix
# 2.3 - Fix for new date/time results from the API
# 2.2 - Reodering throttling to make it work better....
# 2.1 - Adds throttling to spammed links themselves (link_throt)
# Also includes a fix on some character output with !yt searching
# Thanks to <AlbozZ> for this spot and fix!
# 2.0 - Adds seperate flag for search and autoinfo (suggestion from m4s)
# This is a change from previous versions
# .chanset #chan +youtube = Enabled auto info grabbing on a URL spam
# .chanset #chan +youtubesearch = Enabled access to search via !yt
# 1.9 - Adding throttling controls (per user and per chan)
# 1.8 - Chanset +youtube now controls search access!
# 1.7 - Modify SSL params (fixes issues on some systems)
# 1.6 - Small correction to "stream" categorisation.....
# 1.5 - Added UTF-8 support thanks to CatboxParadox (Requires eggdrop
# to be compiled with UTF-8 support)
# 1.4 - Correct time format and live streams gaming etc
# 1.3 - Updated output to be RFC compliant for some IRCDs
# 1.2 - Added auto info grabber for spammed links
# 1.1 - Fixing regex!
# 1.0 - Initial release
# Website https://www.m00nie.com/youtube-eggdrop-script-using-api-v3/
# Notes Grab your own key @ https://developers.google.com/youtube/v3/
#########################################################################################
namespace eval m00nie {
namespace eval youtube {
# ----- CHANGE these variables -----
# This key is your own and shoudl remain a secret (e.g please dont email it to me! Obtain it on the link above in the notes)
variable key "---GET-YOUR-OWN---"
# Some people dont like coloured output! :O
# (you can see an example of the coloured output @ https://www.m00nie.com/youtube-eggdrop-script-using-api-v3/)
# Whatever text (if any) you set the below variable to will be spammed prior to the first result of search or autoinfo
variable out "\002\00301,00You\00300,04Tube\003\002"
# The two variables below control throttling in seconds. First is per user, second is per channel third is per link
variable user_throt 30
variable chan_throt 10
variable link_throt 300
# ---- Dont change things below this line -----
package require http
package require json
# We need to verify the revision of TLS since prior to this version is missing auto host for SNI
if { [catch {package require tls 1.7.11}] } {
# We dont have an autoconfigure option for SNI
putlog "m00nie::youtube *** WARNING *** OLD Version of TLS package installed please update to 1.7.11+ ... "
http::register https 443 [list ::tls::socket -servername www.googleapis.com]
} else {
package require tls 1.7.11
http::register https 443 [list ::tls::socket -autoservername true]
}
bind pub - !yt m00nie::youtube::search
bind pubm - * m00nie::youtube::autoinfo
variable version "3.1"
setudef flag youtube
setudef flag youtubesearch
variable regex {(?:http(?:s|).{3}|)(?:www.|)(?:youtube.com\/watch\?.*v=|youtu.be\/)([\w-]{11})}
::http::config -useragent "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:93.0) Gecko/20100101 Firefox/93.0"
variable throttled
#### Script Starts here #####
proc autoinfo {nick uhost hand chan text} {
if {[channel get $chan youtube] && [regexp -nocase -- $m00nie::youtube::regex $text url id]} {
if {[throttlecheck $nick $chan $id]} { return 0 }
putlog "m00nie::youtube::autoinfo is running"
putlog "m00nie::youtube::autoinfo url is: $url and id is: $id"
set url "https://www.googleapis.com/youtube/v3/videos?id=$id&key=$m00nie::youtube::key&part=snippet,statistics,contentDetails&fields=items(snippet(title,channelTitle,publishedAt),statistics(viewCount,likeCount,dislikeCount),contentDetails(duration))"
set ids [getinfo $url]
# Catch probs...
if {$ids eq "0"} {
putlog "m00nie::youtube::autoinfo there was a problem :("
return
}
set title [encoding convertfrom [lindex $ids 0 1 3]]
set pubiso [lindex $ids 0 1 1]
set pubiso [string map {"T" " " ".000Z" "" "Z" ""} $pubiso]
set pubtime [clock format [clock scan $pubiso] -format {%a %b %d %H:%M:%S %Y}]
set user [encoding convertfrom [lindex $ids 0 1 5]]
# Yes all quite horrible...
# ... it gets worse. ISO time format really isnt my friend...
# ... be sick on yourself.. I have
set isotime [lindex $ids 0 3 1]
regsub -all {P} $isotime "" isotime
# First off check for videos that are days and non 0 hours
if {[regexp {^[0-9]*DT[0-9]*H} $isotime]} {
regexp {(^[0-9]*DT)?([0-9]*H)} $isotime x days hours
regsub -all {DT} $days "" days
regsub -all {H} $hours "" hours
set hours [expr ($days * 24) + $hours]
set nh "${hours}H"
regsub -all {^.*H} $isotime "$nh" isotime
# Now check for videos that are days and "0" hours
} elseif {[regexp {^[0-9]*D} $isotime]} {
regexp {^[0-9]*D} $isotime days
regsub -all {D} $days "" days
set hours [expr $days * 24]
set nh "${hours}H"
regsub -all {^.*D} $isotime "$nh" isotime
}
# Everything else should be <24 hours and >24 shoud be formatted (maybe)
regsub -all {T} $isotime "" isotime
# No M or S returned e.g. PT10H for a 10:00:00 video...
if {[regexp {^[0-9].*H$} $isotime]} {
set isotime "${isotime}00M00S"
# No M but we do have S e.g. PT10H01S for a 10:00:01 video...
} elseif {[regexp {^[0-9]*H[0-9]*S$} $isotime]} {
regsub {H} $isotime "H00M" isotime
# No S but we do have M e.g. PT10M for a 10:00 video...
} elseif {[regexp {^[0-9]*M$} $isotime]} {
regsub {M} $isotime "M00S" isotime
}
# Now we should have some kind of "standard" to mangle as before
regsub -all {H|M} $isotime ":" isotime
regsub -all {S} $isotime "" isotime
# Seems it can now return P0D (it use to be 0)
if { [string index $isotime 0] == "0" || $isotime == "P0D" } {
set isotime "live"
} elseif { [string index $isotime end-1] == ":" } {
set sec [string index $isotime end]
set trim [string range $isotime 0 end-1]
set isotime ${trim}0$sec
} elseif { [string index $isotime end-2] != ":" } {
set isotime "${isotime}s"
}
set views [lindex $ids 0 5 1]
set like [lindex $ids 0 5 3]
# At the moment not used (it looked a little messy)
set dis [lindex $ids 0 5 5]
puthelp "PRIVMSG $chan :$m00nie::youtube::out \002$title\002 by $user (duration: $isotime) on $pubtime, $views views \[Likes: $like\]"
}
}
proc b0rkcheck {results} {
putlog "m00nie::youtube::b0rkcheck is running"
if {!([lindex $results 0 0] eq "items")} {
putlog "m00nie::youtube::b0rkcheck looks to be a problem with the API - [lindex $results 0 0] [lindex $results 1 1]: [lindex $results 1 3]"
return 0
} else {
return 1
}
}
proc throttlecheck {nick chan link} {
if {[info exists m00nie::youtube::throttled($link)]} {
putlog "m00nie::youtube::throttlecheck search term or video id: $link, is throttled at the moment"
return 1
} elseif {[info exists m00nie::youtube::throttled($chan)]} {
putlog "m00nie::youtube::throttlecheck Channel $chan is throttled at the moment"
return 1
} elseif {[info exists m00nie::youtube::throttled($nick)]} {
putlog "m00nie::youtube::throttlecheck User $nick is throttled at the moment"
return 1
} else {
set m00nie::youtube::throttled($nick) [utimer $m00nie::youtube::user_throt [list unset m00nie::youtube::throttled($nick)]]
set m00nie::youtube::throttled($chan) [utimer $m00nie::youtube::chan_throt [list unset m00nie::youtube::throttled($chan)]]
set m00nie::youtube::throttled($link) [utimer $m00nie::youtube::link_throt [list unset m00nie::youtube::throttled($link)]]
return 0
}
}
proc getinfo { url } {
for { set i 1 } { $i <= 5 } { incr i } {
set rawpage [::http::data [::http::geturl "$url" -timeout 5000]]
if {[string length rawpage] > 0} { break }
}
putlog "m00nie::youtube::getinfo Rawpage length is: [string length $rawpage]"
if {[string length $rawpage] == 0} { error "youtube returned ZERO no data :( or we couldnt connect properly" }
set results [json::json2dict $rawpage]
# Check is we have errors
if {[b0rkcheck $results]} {
# We dont :)
set ids [dict get $results items]
return $ids
} else {
# We do have errors :(
return 0
}
}
proc search {nick uhost hand chan text} {
if {![channel get $chan youtubesearch] } {
return
}
putlog "m00nie::youtube::search is running"
regsub -all {\s+} $text "%20" text
if {[throttlecheck $nick $chan $text]} { return 0 }
set url "https://www.googleapis.com/youtube/v3/search?part=snippet&fields=items(id(videoId),id(channelId),snippet(title))&key=$m00nie::youtube::key&q=$text"
set ids [getinfo $url]
# Catch probs...
if {$ids eq "0"} {
putlog "m00nie::youtube::autoinfo there was a problem :("
return
}
set output "$m00nie::youtube::out "
for {set i 0} {$i < 5} {incr i} {
set id [lindex $ids $i 1 1]
set type [lindex $ids $i 1 0]
# Catch Channels rather than videos (youtu.be doesnt work for channels)
if {$type eq "channelId"} {
set yout "https://www.youtube.com/channel/$id"
} else {
set yout "https://youtu.be/$id"
}
set desc [encoding convertfrom [lindex $ids $i 3 1]]
set desc [string map -nocase [list "&" "&" "'" "'" """ "\""] $desc ]
append output "\002" $desc "\002 - " $yout " | "
}
set output [string range $output 0 end-2]
puthelp "PRIVMSG $chan :$output"
}
}
}
putlog "m00nie::youtube $m00nie::youtube::version loaded"