diff --git a/Daeghrefn/Communication.tcl b/Daeghrefn/Communication.tcl index 1ef54bf..6a32fb7 100644 --- a/Daeghrefn/Communication.tcl +++ b/Daeghrefn/Communication.tcl @@ -1,454 +1,489 @@ bind dcc - sms dcc:sms bind dcc - mail dcc:mail bind dcc - twitter dcc:twitter bind pub - !sms pub:sms bind pub - !identica pub:identica bind pub - !pub pub:twitter bind pub - !twit pub:twitter bind pub - !tweet pub:twitter bind pub - !idee pub:idee bind pub - !idees pub:idee bind pub - !idée pub:idee bind pub - !ideert pub:ideert bind pub - !idéert pub:ideert bind pub - !idee-rt pub:ideert bind pub - !idée-rt pub:ideert # # SMS # #Sends a SMS to $to with $message as text and $from as source #Returns "" if SMS were sent, the error message otherwise proc sendsms {from to message} { switch [set mode [registry get sms.$to.mode]] { 1 { #Triskel/Wolfplex form set url [registry get sms.$to.url] set pass [registry get sms.$to.pass] set xtra [registry get sms.$to.xtra] if {$url == ""} {return "URL inconnue pour $to"} #Check length set len [string length $from$message] if {$len > 113} { return "Message trop long, réduisez-le de [expr $len-113] caractère[s $len-113]." } #Posts form set query [::http::formatQuery m $from p $message v $pass envoi Envoyer] if {$xtra != ""} {append query &$xtra} set tok [::http::geturl $url -query $query] set result [::http::data $tok] ::http::cleanup $tok #Parses reply if {[string first "Impossible d'envoyer" $result] != -1} { return "Le formulaire web indique qu'il est immpossible d'envoyer le message." } elseif {[string first "Tu as subtilement" $result] != -1} { return "Pass incorrect : $pass, regardez sur $url si la question antibot n'a pas été modifiée." } elseif {[string first "envoi \[ Ok \]" $result] != -1} { return "" } { putdebug $result return "D'après la réponse du formulaire, il n'est pas possible de déterminer si oui ou non il a été envoyé." } } "" { return "$to n'a pas activé la fonction SMS." } default { return "Unknown sms mode: $mode." } } } #.sms proc dcc:sms {handle idx arg} { # The SMS configuration is stored in the following registry variables: # sms.$destinataire.mode 1 Triskel or Wolfplex form # # For mode 1: # sms.$destinataire.url form URL # sms.$destinataire.pass e.g. rose # sms.$destinataire.xtra not needed for triskel forms, who=Darkknow needed for Wolfplex form if {$arg == "" || $arg == "config"} { #Prints config switch [set mode [registry get sms.$handle.mode]] { 1 { putdcc $idx "URL ..... [registry get sms.$handle.url]" if {[set pass [registry get sms.$handle.pass]] != ""]} { putdcc $idx "Pass .... $pass" } if {[set xtra [registry get sms.$handle.xtra]] != ""} { putdcc $idx "Extra ... $xtra" } } "" { putdcc $idx "Vous n'avez pas encore configuré votre fonctionnalité SMS." } default { putdcc $idx "Unknown sms mode: $mode" } } return 1 } elseif {[string range $arg 0 6] == "config "} { putdcc $idx "Le script interactif de configuration n'est pas encore prêt." putcmdlog "#$handle# sms config ..." return 0 } else { #Sends a SMS set to [lindex $arg 0] #TODO: use a proc to remove the first word instead and keep $arg as string set message [lrange $arg 1 end] if {[set result [sendsms $handle $to $message]] == ""} { putdcc $idx "Envoyé." putcmdlog "#$handle# sms ..." } { putdcc $idx $result } } return 0 } #!sms proc pub:sms {nick uhost handle chan text} { #Sends a SMS if {$handle == "" || $handle == "*"} { set from $nick } { set from $handle } set to [lindex $text 0] #TODO: use a proc to remove the first word instead and keep $arg as string set message [lrange $text 1 end] if {[set result [sendsms $from $to $message]] == ""} { putquick "PRIVMSG $chan :$nick, c'est envoyé." putcmdlog "!$nick! sms ..." } { putquick "PRIVMSG $chan :$nick, $result." } return 0 } # # Identi.ca and Twitter # #Posts $message on the identi.ca $account account proc identicapost {account message} { package require base64 set row [lindex [sql "SELECT account_username, account_password FROM identica_accounts WHERE account_code = '$account'"] 0] set auth "Basic [base64::encode [join $row :]]" set tok [::http::geturl http://identi.ca/api/statuses/update.xml -headers [list Authorization $auth] -query [::http::formatQuery status $message]] #putdebug [::http::data $tok] ::http::cleanup $tok } #Posts $message on the Twitter $account account proc twitterpost {account message} { set status_url "https://api.twitter.com/1.1/statuses/update.json" if {[catch {twitter_query $status_url $account [list status $message]} error]} { putdebug "Twitter error: $error" return 0 } return 1 } #Gets the Twitter OAuth token proc twitter_token {account} { registry get twitter.oauth.tokens.$account } proc dcc:twitter {handle idx arg} { set command [lindex $arg 0] switch $command { "setup" { set account [lindex $arg 1] if {$account == ""} { putdcc $idx "What account to setup?" return 0 } if {[twitter_token $account] != ""} { switch [lindex $arg 2] { "--force" { registry del twitter.oauth.tokens.$account } "" { putdcc $idx "There is already a token set for this account. Please use '.twitter setup $account --force' to erase it." return 0 } } } set pin [lindex $arg 2] if {$pin == "" || $pin == "--force"} { #Initializes requests if {[catch {oauth::get_request_token {*}[registry get twitter.oauth.consumer]} data]} { putdebug "Can't request OAuth token for Twitter $account account: $data" putdcc $idx "An error occured, I can't request an OAuth token for you account." return 0 } { registry set twitter.oauth.tokens.$account "[dict get $data oauth_token] [dict get $data oauth_token_secret]" putdcc $idx "Step 1 — Go to [dict get $data auth_url]" putdcc $idx "Step 2 — .twitter setup $account " return 1 } } { #Saves token if {[catch {oauth::get_access_token {*}[registry get twitter.oauth.consumer] {*}[twitter_token $account] $pin} data]} { putdebug "Can't confirm OAuth token for Twitter $account account: $data" putdcc $idx "An error occured, I can't confirm an OAuth token for you account." return 0 } { registry set twitter.oauth.tokens.$account "[dict get $data oauth_token] [dict get $data oauth_token_secret]" putdcc $idx "Ok, I've now access to account [dict get $data screen_name]." putcmdlog "#$handle# twitter setup $account ..." return 0 } } } + "reconfigure" { + twitter_update_short_url_length + return 1 + } + default { putdcc $idx "Unknown Twitter command: $arg"} } } #Sends a query proc twitter_query {url account {query_list {}} {method {}}} { # Uses POST for any query if {$method == ""} { if {$query_list == ""} { set method GET } { set method POST } } if {$method == "GET" && $query_list == ""} { append url ? append url [http::formatQuery {*}$query_list] } # Requests set token [twitter_token $account] if {$token == ""} { error "Unidentified Twitter account: $account" } { set reply [oauth::query_api $url {*}[registry get twitter.oauth.consumer] $method {*}$token $query_list] json::json2dict $reply } } #.identica proc dcc:identica {handle idx arg} { } #!idee proc pub:idee {nick uhost handle chan text} { set who [whois $nick] if {$who == ""} { append text " – via IRC." } { append text " – $who, via IRC." } twitterpublish ideedarticles $nick $text } #!ideert proc pub:ideert {nick uhost handle chan text} { set status "" if {[twitter_try_extract_status $text status]} { twitter_retweet ideedarticles $status return 1 } { return 0 } } #!identica proc pub:identica {nick uhost handle chan text} { putquick "NOTICE $nick :!identica is currently disabled. Is identi.ca still usable since pump.io migration? If so, please request the command." } proc whois {nickname} { # By handle set result [nick2hand $nickname] if {$result != "*"} { #Will return "", when nick doesn't exist to avoid further processing. return $result } #Gets user@host set uhost [getchanhost $nickname] set host [lindex [split $uhost @] 1] # By Cloak if {[regexp / $host]} { set cloak [split $host /] set group [lindex $cloak 0] if {$group != "gateway" && $group != "nat"} { # @freenode/staff/ubuntu.member.niko → niko # @wikipedia/pdpc.21for7.elfix → elfix # @wikipedia/poulpy → poulpy return [lindex [split [lindex $cloak end] .] end] } } # By NickServ # TODO: code with callback # By user@host, when the host doesn't contain any digit if {[regexp {^[^0-9]*$} $host]} { return "$nickname!$uhost" } # Can't identify return "" } #!pub or !twit or !tweet #The account is channel dependant proc pub:twitter {nick uhost handle chan text} { set account [registry get twitter.channels.$chan.account] set is_protected [registry get twitter.channels.$chan.protected] if {$account == ""} { putquick "NOTICE $nick :!pub isn't n'est pas activé sur le canal $chan / !pub isn't enabled on channel $chan" return } if {$is_protected == "1"} { set who [whois $nick] if {$who == ""} { putquick "NOTICE $nick :Pour utiliser !pub sur $chan, vous devez disposer d'un cloak projet ou unaffiliated, être connecté depuis un host sans chiffre ou encore avoir votre user@host reconnu par mes soins." return 0 } { append text " — $who" } } twitterpublish $account $nick $text } proc twitterpublish {account nick text} { if {$text == ""} { putquick "NOTICE $nick :Syntaxe : !pub " return } - set len [string length $text] + set len [twitter_compute_len $text] if {$len > 140} { - putquick "NOTICE $nick :140 caractères max, là il y en a $len." + putquick "NOTICE $nick :140 caractères max, là il y en a $len ([twitter_get_short_url_length] par lien)." return } if [twitterpost $account $text] { putquick "NOTICE $nick :Publié sur Twitter" return 1 } { putquick "NOTICE $nick :Non publié, une erreur a eu lieu." } } # Extracts the id of the status from an URL or directly from this ID # # @param text The text containing the status ID # @param If successful, will be set to the id of the status to retweet # @return 1 if successful; otherwise, 0 proc twitter_try_extract_status {text status} { upvar 1 $status value # Trivial case: value is already the status identifier if {[isnumber $text]} { set value $text return 1 } regexp "twitter\.com/.*/status/(\[0-9\]+)" $text matches value } # Retweets a status # # @param account The retweeting account username # @param status The id of the status to retweet # @return The API reply, as a dictionary proc twitter_retweet {account status} { set url https://api.twitter.com/1.1/statuses/retweet/$status.json twitter_query $url $account "" POST } +# @param param The parameter to fetch in the API reply +# return The value from configuration the JSON document, or a dict if it contains several parameters +proc twitter_get_configuration_parameter {param} { + set account [registry get twitter.default_account] + set url https://api.twitter.com/1.1/help/configuration.json + set config [twitter_query $url $account] + dict get $config $param +} + +proc twitter_update_short_url_length {} { + set len [twitter_get_configuration_parameter short_url_length] + registry set twitter.short_url_length $len +} + +proc twitter_get_short_url_length {} { + registry get twitter.short_url_length +} + +# Computes len of a tweet, taking in consideration t.co URL length +# See https://dev.twitter.com/basics/tco +proc twitter_compute_len {text} { + set short_url_length [twitter_get_short_url_length] + + set len [strlen $text] + foreach url [geturls $text] { + incr len [expr $short_url_length - [strlen $url]] + } + return $len +} + # # Mail # # .mail proc dcc:mail {handle idx arg} { global mail special if {$arg == ""} { putdcc $idx "## Syntaxe : .mail \[objet\]" return } elseif {[validuser [lindex $arg 0]]} { set mail($idx-to) [getuserinfo [lindex $arg 0] user_email] } elseif {[regexp {^[A-Za-z0-9._-]+@[[A-Za-z0-9.-]+$} [lindex $arg 0]]} { set mail($idx-to) [lindex $arg 0] } else { putdcc $idx "Destinataire invalide : [lindex $arg 0]" return } set mail($idx) "" putdcc $idx "\002Alors, que désires tu envoyer comme mail ?\002" putdcc $idx "Pour envoyer l'e-mail, entre une ligne ne contenant que ceci: \002+\002" putdcc $idx "Pour annuler l'e-mail, entre une ligne ne contenant que ceci: \002-\002" set mail($idx-subject) [truncate_first_word $arg] if {$mail($idx-subject) == ""} { putdcc $idx "\002Objet :\002" } else { putdcc $idx "\002Message :\002" } control $idx control:mail dccbroadcast "Tiens, $handle est parti rédiger un mail ..." } # Controls mail encoding processus proc control:mail {idx text} { global mail if {$text == "+"} { mail.send $mail($idx-to) $mail($idx-subject) $mail($idx) [getuserinfo $idx user_email] unset mail($idx) putdcc $idx "Envoyé :-)" dccbroadcast "Et, hop, un mail d'envoyé pour [idx2hand $idx] :)" return 1 } elseif {$text == "-"} { unset mail($idx) dccbroadcast "[idx2hand $idx] vient de changer d'avis et revient." putdcc $idx "Ok, le mail a été annulé: retour au party line !" return 1 } elseif {$mail($idx-subject) == ""} { set mail($idx-subject) $text putdcc $idx "\002Message :\002" } else { regsub -all {\\} $text "\\\\\\" text regsub -all "'" $text "\\'" text append mail($idx) "\n$text" } } # Sends a mail # # @param to The recipient # @param subject The mail subject # @param message The message to send # @param from The mail author (optional) proc mail.send {to subject message {from {}}} { set fd [open "|sendmail -t" w] if {$from != ""} { puts $fd "From: $from" } puts $fd "To: $to" puts $fd "Subject: $subject" puts $fd puts $fd "$message" flush $fd close $fd } diff --git a/tests/Communication.test b/tests/Communication.test new file mode 100644 index 0000000..a92c8ea --- /dev/null +++ b/tests/Communication.test @@ -0,0 +1,40 @@ +package require tcltest +namespace import ::tcltest::* + +### +### Init +### + +# Tested code +source init.tcl +source $dir/Daeghrefn/Communication.tcl + +### +### Mocks +### + +# This value is normally given by a cached value in the registry +# and updated through an API call to /help/configuration.json. +proc twitter_get_short_url_length {} { return 23 } + +### +### Tests +### + +test twitter_compute_len_empty {} -body { + twitter_compute_len "" +} -result 0 + +test twitter_compute_len_regular_string {} -body { + twitter_compute_len "quux" +} -result 4 + +test twitter_compute_len_links {} -body { + twitter_compute_len "Je vais sur http://www.perdu.com pour y lire https://fr.wikipedia.org/w/index.php?title=Les_B%C3%A2tards_du_Nord&type=revision&diff=133589772&oldid=133589631" +} -result 71 + +### +### Cleanup +### + +cleanupTests diff --git a/tests/init.tcl b/tests/init.tcl index 89b7f23..7265d68 100644 --- a/tests/init.tcl +++ b/tests/init.tcl @@ -1,15 +1,20 @@ # Procs to mock proc bind {type flags cmdOrMask {procName ""}} {} proc unbind {type flags cmdOrMask procname} {} # Tests config set dir [info script] if {$dir == ""} { set dir [pwd] append dir "/scripts" } { set dir [file dirname [file dirname [file normalize $dir]]] } # Standard procedures source $dir/Core.tcl + +# Eggdrop procedures +proc strlen {text} { + string length $text +}