Page MenuHomeDevCentral

No OneTemporary

diff --git a/Core.tcl b/Core.tcl
index 7e13721..1bb8645 100644
--- a/Core.tcl
+++ b/Core.tcl
@@ -1,828 +1,881 @@
package require http
package require tls
#
# HTTP support
#
::tls::init -ssl2 false -ssl3 false -tls1 true
::http::register https 443 ::tls::socket
# Determines if a specified page's source code contains the specified string
proc http_contains {pattern url} {
try {
set fd [::http::geturl $url -timeout 600]
} on error {result} {
return 0
}
if {[::http::status $fd] != "ok"} {
set result 0
} elseif {[::http::ncode $fd] != 200} {
set result 0
} elseif {[string first $pattern [::http::data $fd]] > -1} {
set result 1
} {
set result 0
}
::http::cleanup $fd
return $result
}
#
# TCL helpers
#
#Determines if $proc exists
proc proc_exists {proc} {
expr {[info procs $proc] == $proc}
}
proc is_package_present {package} {
expr ![catch {package present $package}]
}
#
# Loop constructs
#
# http://wiki.tcl.tk/3603
proc do {code while cond} {
tailcall try $code\n[list $while $cond $code]
}
#
# List procs
#
# Gets the maximal value of a numeric list
proc lmax {items} {
getultimatecomparedfromlist $items >
}
# Gets the minimal value of a numeric list
proc lmin {items} {
getultimatecomparedfromlist $items <
}
# Gets the ultimately compared item from a list
proc getultimatecomparedfromlist {items op} {
set hasValue 0
set max {}
foreach item $items {
if {![string is double $item]} {
continue
}
if {!$hasValue} {
set hasValue 1
set max $item
continue
}
if {[expr $item $op $max]} {
set max $item
}
}
return $max
}
#
# Trivial procs
#
#Determines if $v is a number
proc isnumber {v} {
return [expr {! [catch {expr {int($v)}}]}]
}
# Returns +-<number>
proc numberSign {number} {
if {$number > 0} {
return "+$number"
} {
return $number
}
}
# Returns the value as is if numeric, or "0" if not
proc zeroornumber {value} {
if {$value == "" || ![string is double $value]} {
return 0
}
return $value
}
#Returns "s" if $count implies a plural
#TODO: keep this method for French (ie NOT adjusting values for English)
# and grab the plural proc from wiki.tcl.tk for English.
proc s {count} {
if {$count >= 2 || $count <= -2} {return "s"}
}
proc isnotasciiutf8char {char} {
regexp {(?x)
[\xC0-\xDF] [\x80-\xBF] | # Two-byte chars (\u0080-\u07FF)
[\xE0-\xEF] [\x80-\xBF]{2} | # Three-byte chars (\u0800-\uFFFF)
[\xF0-\xF4] [\x80-\xBF]{3} # Four-byte chars (U+10000-U+10FFFF, not suppoted by Tcl 8.5)
} $char
}
proc isutf8char {char} {
regexp {(?x)
[\x00-\x7F] | # Single-byte chars (ASCII range)
[\xC0-\xDF] [\x80-\xBF] | # Two-byte chars (\u0080-\u07FF)
[\xE0-\xEF] [\x80-\xBF]{2} | # Three-byte chars (\u0800-\uFFFF)
[\xF0-\xF4] [\x80-\xBF]{3} # Four-byte chars (U+10000-U+10FFFF, not suppoted by Tcl 8.5)
} $char
}
#
# Dictionaries
#
# Gets recursively a value in a dictionary
#
# @param $dict the dictionary (without any dots in keys)
# @param $key the value's key; if dict are nested, succesive keys are separated by dots (e.g. change.owner.name)
# @param $throwErrorIfKeyDoesNotExist when the key doesn't exist: if true, throws an error; otherwise, returns an empty string
# @return the dictionary value at the specified key, or an empty string if the key doesn't exist
proc dg {dict key {throwErrorIfKeyDoesNotExist 0}} {
set keys [split $key .]
if {[llength $keys] > 1} {
# Recursive call
# dg $dict a.b = dict get [dict get $dict a] b
dg [dg $dict [lindex $keys 0] $throwErrorIfKeyDoesNotExist] [join [lrange $keys 1 end] .] $throwErrorIfKeyDoesNotExist
} elseif {([llength $dict] % 2 == 0) && [dict exists $dict $key]} {
# This is a dict and we have a key
dict get $dict $key
} elseif {$throwErrorIfKeyDoesNotExist > 0} {
error "Key not found: $key"
}
}
#
# Strings
#
proc strlen {str} {
string length $str
}
#Completes $text by spaces or $char so the returned text length is $len
proc completestring {text len {char " "}} {
set curlen [string length $text]
if {$curlen >= $len} {
return $text
}
if {[string length $char] < 2} {
append text [string repeat $char [expr $len - $curlen]]
} {
while {[string length $text] < $len} {
append text $char
}
string range $text 0 $len+1
}
}
proc completestringright {text len {char " "}} {
set curlen [string length $text]
if {$curlen >= $len} {
return $text
}
set completedtext [string range [completestring $text $len $char] $curlen end]
append completedtext $text
}
## Prepends 0s to a number
##
## @param $number The number to zerofill
## @param $digits The number length
## @return The zerofilled number
proc zerofill {number digits} {
format "%0${digits}d" $number
}
# Maps from a list of words the length of those words
# e.g. strlenmap "a aaa aa" will return {1 3 2}
proc strlenmap {words} {
lmap key $words { string length $key }
}
# Search the max string lenghth from a list of strings
proc strlenmax {words} {
zeroornumber [lmax [strlenmap $words]]
}
#
# SQL
#
#Reconnects to the sql & sql2 server
proc sqlrehash {} {
global sql
catch {
sql disconnect
sql2 disconnect
}
sql connect $sql(host) $sql(user) $sql(pass)
sql2 connect $sql(host) $sql(user) $sql(pass)
sql selectdb $sql(database)
sql2 selectdb $sql(database)
}
#Escape a string to use as sql query parameter
proc sqlescape {data} {
#\ -> \\
#' -> \'
string map {"\\" "\\\\" "'" "\\'"} $data
}
#Gets the first item of the first row of a sql query (scalar results)
proc sqlscalar {sql} {
lindex [lindex [sql $sql] 0] 0
}
#Adds specified data to specified SQL table
proc sqladd {table {data1 ""} {data2 ""}} {
if {$data1 == ""} {
set fields ""
#Prints field to fill
foreach row [sql "SHOW COLUMNS FROM $table"] {
lappend fields [lindex $row 0]
}
return $fields
}
if {$data2 == ""} {
set sql "INSERT INTO $table VALUES ("
set data $data1
} {
set sql "INSERT INTO $table (`[join $data1 "`, `"]`) VALUES ("
set data $data2
}
set first 1
foreach value $data {
if {$first == 1} {set first 0} {append sql ", "}
append sql "'[sqlescape $value]'"
}
append sql ")"
sql $sql
}
# Gets the value of the AUTOINCREMENT column for the last INSERT
#
# @return the last value of the primary key
proc sqllastinsertid {} {
sql "SELECT LAST_INSERT_ID()"
}
proc sqlconnect {{sqlinstance "sql"} {defaultsfile ""}} {
if {$defaultsfile == ""} {
global env
set defaultsfile $env(HOME)/.my.cnf
}
set config [readmycnf $defaultsfile]
$sqlinstance connect [dict get $config host] [dict get $config user] [dict get $config password]
}
proc readmycnf {defaultsfile} {
set config {}
set fd [open $defaultsfile]
while {[gets $fd line] != -1} {
set entry [split $line =]
if {[llength $entry] == 2} {
dict set config {*}$entry
}
}
close $fd
return $config
}
#
# Registry
#
#Gets, sets, deletes or increments a registry value
proc registry {command key {value ""}} {
switch -- $command {
"add" {
sqladd registry "data value" [list $key $value]
}
"get" {
sqlscalar "SELECT value FROM registry WHERE `data` = '$key'"
}
"set" {
sqlreplace registry "data value" [list $key $value]
}
"del" {
registry delete $key $value
}
"delete" {
sql "DELETE FROM registry WHERE `data` = '$key'"
}
"incr" {
if {$value == ""} {
set term 1
} elseif {[string is integer $value]} {
set term $value
} {
error "The increment term must be an integer."
}
set current [registry get $key]
if {$current == ""} {
registry set $key $term
} {
registry set $key [incr current $term]
}
}
default {
error "unknown subcommand: must be add, get, set, incr or delete"
}
}
}
#
# Users information
#
# Gets user_id from a username, idx or user_id
#
#
proc getuserid {who} {
if {$who == ""} {
return
} elseif {![isnumber $who]} {
#username -> user_id
sql "SELECT user_id FROM users WHERE username = '[sqlescape $who]'"
} elseif {$who < 1000} {
#idx -> user_id
getuserid [idx2hand $who]
} else {
#user_id -> user_id (or "" if not existing)
sql "SELECT user_id FROM users WHERE user_id = $who"
}
}
# Gets user info
#
# @param who The user
# @param what The information to get
proc getuserinfo {who what} {
sqlscalar "SELECT $what FROM users WHERE user_id = [getuserid $who]"
}
#
# Text parsing
#
proc geturls {text} {
#Finds the first url position
set pos -1
foreach needle "http:// https:// www. youtu.be" {
set pos1 [string first $needle $text]
if {$pos1 != -1 && ($pos == -1 || $pos1 < $pos)} {
set pos $pos1
}
}
#No URL found
if {$pos == -1} {return}
#URL found
set pos2 [string first " " $text $pos]
if {$pos2 == -1} {
#Last URL to be found
string range $text $pos end
} {
#Recursive call to get other URLs
concat [string range $text $pos $pos2-1] [geturls [string range $text $pos2+1 end]]
}
}
#Reads specified URL and returns content
proc geturltext {url {trim 1}} {
set fd [http::geturl $url]
set text [http::data $fd]
http::cleanup $fd
if $trim {
string trim $text
} {
return $text
}
}
proc numeric2ordinal {n} {
switch $n {
1 { return first }
2 { return second }
3 { return third }
5 { return fifth }
8 { return eight }
9 { return ninth }
#todo: ve -> f / y -> ie
12 { return twelfth }
default {
set ordinal "[numeric2en $n]th"
set m [expr $n % 10]
if {$m == 0} {
return [string map "yth ieth" $ordinal]
}
if {$n < 20} { return $ordinal }
if {$n > 100} { return "${n}th" }
return "[numeric2en [expr $n - $m]]-[numeric2ordinal $m]"
}
}
}
proc numeric2en {n {optional 0}} {
#---------------- English spelling for integer numbers
if {[catch {set n [expr $n]}]} {return $n}
if {$optional && $n==0} {return ""}
array set dic {
0 zero 1 one 2 two 3 three 4 four 5 five 6 six 7 seven
8 eight 9 nine 10 ten 11 eleven 12 twelve
}
if [info exists dic($n)] {return $dic($n)}
foreach {value word} {1000000 million 1000 thousand 100 hundred} {
if {$n>=$value} {
return "[numeric2en $n/$value] $word [numeric2en $n%$value 1]"
}
} ;#--------------- composing between 13 and 99...
if $n>=20 {
set res $dic([expr $n/10])ty
if $n%10 {append res -$dic([expr $n%10])}
} else {
set res $dic([expr $n-10])teen
} ;#----------- fix over-regular compositions
regsub "twoty" $res "twenty" res
regsub "threet" $res "thirt" res
regsub "fourty" $res "forty" res
regsub "fivet" $res "fift" res
regsub "eightt" $res "eight" res
set res
} ;#RS
# Truncates the first word
#
# @param string the string to truncate
# @return the truncated string
proc truncate_first_word {string} {
set pos [string first " " $string]
if {$pos == -1} return
string range $string $pos+1 end
}
proc xmlescape {text} {
#Determines if we should use <![CDATA[]]>
set useCDATA 0
if {[string first < $text] > -1 || [string first > $text] > -1} {
set useCDATA 1
}
#TODO: check if there is no other case for CDATA
# check when to use CDATA instead &lt; &gt;
#Output
set text [string map {& {&amp;} ' {&apos;} {"} {&quot;}} $text]
if {$useCDATA} {
return "<!\[CDATA\[$text]]>"
}
return $text
}
#
# URLs
#
namespace eval url {
variable map
variable alphanumeric a-zA-Z0-9._~-
namespace export encode decode
namespace ensemble create
}
proc url::init {} {
variable map
variable alphanumeric a-zA-Z0-9._~-
for {set i 0} {$i <= 256} {incr i} {
set c [format %c $i]
if {![string match \[$alphanumeric\] $c]} {
set map($c) %[format %.2x $i]
}
}
# These are handled specially
array set map { " " + \n %0d%0a }
}
url::init
proc url::encode {str} {
variable map
variable alphanumeric
# The spec says: "non-alphanumeric characters are replaced by '%HH'"
# 1 leave alphanumerics characters alone
# 2 Convert every other character to an array lookup
# 3 Escape constructs that are "special" to the tcl parser
# 4 "subst" the result, doing all the array substitutions
regsub -all \[^$alphanumeric\] $str {$map(&)} str
# This quotes cases like $map([) or $map($) => $map(\[) ...
regsub -all {[][{})\\]\)} $str {\\&} str
return [subst -nocommand $str]
}
# Decodes an URL
#
# @param $str The URL to decode
# @return The decoded URL
proc url::decode {str} {
# rewrite "+" back to space
# protect \ from quoting another '\'
set str [string map [list + { } "\\" "\\\\"] $str]
# prepare to process all %-escapes
regsub -all -- {%([A-Fa-f0-9][A-Fa-f0-9])} $str {\\u00\1} str
# process \u unicode mapped chars
return [subst -novar -nocommand $str]
}
#
# GUID
#
# Gets the MD5 of a string, and returns it following the GUID format
#
# @param $str The string to compute the hash
# @return The MD5, formatted as a GUID
proc guidmd5 {str} {
set md5 [md5 $str]
set output ""
for {set i 0} {$i < 32} {incr i} {
if {$i == 8 || $i == 12 || $i == 16 || $i == 20} {
append output "-"
}
append output [string index $md5 $i]
}
return $output
}
#
# Run interactive commands with callbacks
#
# Uses fileevent example code by Bryan Oakley
# http://stackoverflow.com/a/172061/1930997
#
# e.g. proc put_command_callback {fd line state} { put[lindex $state 0] [lindex $state 1] $line }
#
# run_command "pwd" put_command_callback "" {dcc 12}
# run_command "pwd" put_command_callback "" {quick "#foo"}
#
# (we provide a more generic print_command_callback procedure for this general use.)
# Callback to print non interactive commands output in partyline, debug, channel or query
#
# @param $fd File descriptor of the command (not used)
# @param $line Line printed by the command result
# @param $state A list of two items, the first the mode, the second the target
#
# Accepted modes and matched target descriptions:
# - bot: target is another linked bot. A third optional state parameter could be the bot command.
# - quick/serv/help: target is a channel or a nick (through a private message)
# - notice: target is a nick (through a notice, sent in the putserv queue)
# - dcc: target is the dcc connection IDX
# - debug: prints the line as debug, target is ignored
proc print_command_callback {fd line state} {
foreach "mode target" $state {}
switch $mode {
bot {
if {[llength $state] > 2} {
set cmd [lindex $state 2]
putbot $target $cmd $line
} {
putbot $target $line
}
}
dcc { putdcc $target $line }
quick { putquick "PRIVMSG $target :$line" }
serv { putserv "PRIVMSG $target :$line" }
help { puthelp "PRIVMSG $target :$line" }
notice { putserv "NOTICE $target :$line" }
debug { putdebug $line }
default { putdebug "Unknown message mode: $mode (line were $line)" }
}
}
# Same to print a Python error without the traceback
proc print_python_error_callback {fd output state} {
print_command_callback $fd [extractPythonError $output] $state
}
# Runs a command, opens a file descriptor to communicate with the process
#
# @param $cmd the command to run
# @param $callbackProc a callback proc to handle the command output and send input
# @param $errorCallbackProc a callback proc to handle the command error output
# @param $state a state object to send to the callback proc
proc run_command {cmd callbackProc {errorCallbackProc ""} state} {
set fd [open "| $cmd" r]
fconfigure $fd -blocking false
fileevent $fd readable [list interactive_command_handler $fd $callbackProc $errorCallbackProc $state]
}
# Closes a command
#
# @param $fd File descriptor to the command process
proc close_interactive_command {fd {errorCallbackProc ""} {state ""}} {
fconfigure $fd -blocking true
if {[catch {close $fd} err]} {
if {$errorCallbackProc == ""} {
putdebug $err
} {
$errorCallbackProc $fd $err $state
}
}
}
# Callback for fileevent to handle command output and state
#
# @param $fd File descriptor to the command process
# @param $callbackProc a callback proc to handle the command output and send input
# @param $state a state object to send to the callback proc
proc interactive_command_handler {fd callbackProc errorCallbackProc {state ""}} {
set status [catch {gets $fd line} result]
if { $status != 0 } {
# unexpected error
putdebug "Unexpected error running command: "
putdebug $result
close_interactive_command $fd $errorCallbackProc $state
} elseif {$result >= 0} {
$callbackProc $fd $line $state
} elseif {[eof $fd]} {
close_interactive_command $fd $errorCallbackProc $state
} elseif {[fblocked $f]} {
# Read blocked, so do nothing
}
}
proc posix_escape {name} {
foreach char [split $name {}] {
switch -regexp $char {
{'} {append escaped \\' }
{[[:alnum:]]} {append escaped $char }
{[[:space:]]} {append escaped \\$char }
{[[:punct:]]} {append escaped \\$char }
default {append escaped '$char' }
}
}
return $escaped
}
# Returns absolute path to external script
proc get_external_script {script} {
global env
set path $env(HOME)/bin/
append path $script
}
# Extracts the error from Python
proc extractPythonError {exception} {
# The exception is one of the line of the text (so the newline-sensitive matching)
# Before that, we have the full traceback we want to ignore
# e.g. of a line to match: pywikibot.data.api.APIError: modification-failed: Item …
if {[regexp -line {^([A-Za-z\.]+)\: (.*)$} $exception line type message]} {
return $line
} {
putdebug "Regexp doesn't match a Python error for this output in extractPythonError:"
putdebug $exception
}
}
+###
+### Handle / nick handling
+###
+
+proc resolve_nick {nickname} {
+ set resolved [whois $nickname 0]
+
+ if {$resolved == ""} {
+ return $nickname
+ }
+
+ return $resolved
+}
+
+# Returns an identified nickname or "" if not identified
+# By default, can also return strings like "kumkum!kumkum@eglide.org"
+proc whois {nickname {useUserHost 1}} {
+ # 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 {$useUserHost && [regexp {^[^0-9]*$} $host]} {
+ return "$nickname!$uhost"
+ }
+
+ # Can't identify
+ return ""
+}
+
#
# IP and host string manipulations
#
# Gets the host part of a [nick!]user@host string
proc gethost {uhost} {
set pos [string first @ $uhost]
if {$pos == -1} {
return ""
}
string range $uhost [expr $pos + 1] end
}
# Determines if the specified string is a valid IPv4 address
proc isipv4 {string} {
# http://wiki.tcl.tk/989 - Michael A. Cleverly
set octet {(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])}
regexp -- "^[join [list $octet $octet $octet $octet] {\.}]\$" $string
}
# Determines if the specified string is a valid IPv6 address
proc isipv6 {string} {
#
# NOTE: 2001:0db8:0000:0000:0000:0000:1428:57ab
# 2001:0db8:0000:0000:0000::1428:57ab
# 2001:0db8:0:0:0:0:1428:57ab
# 2001:0db8:0:0::1428:57ab
# 2001:0db8::1428:57ab
# 2001:db8::1428:57ab
# 2001:0db8:0000:0000:0000:0000:<IPv4>
# ::1
# ::
#
if {$string eq "::"} then {
return true
}
if {[string range $string 0 1] == "::"} then {
set string [string range $string 1 end]
}
if {[string range $string end-1 end] == "::"} then {
set string [string range $string 0 end-1]
}
set octets [split $string :]
set llength [llength $octets]
if {$llength > 0 && $llength <= 8} then {
set last [expr {$llength - 1}]
for {set index 0} {$index < $llength} {incr index} {
set octet [lindex $octets $index]
set length [string length $octet]
if {$length == 0} then {
if {![info exists null]} then {
set null $index; continue
} else {
return false
}
}
if {$length <= 4 && [string is xdigit -strict $octet]} then {
continue
}
if {$llength <= 7 && $index == $last && [isipv4 $octet]} then {
continue
}
return false
}
return true
}
return false
}
# Determines if the specified string is a valid IP address
proc isip {string} {
expr [isipv4 $string] || [isipv6 $string]
}
###
### IP helper procedures
###
# Extracts an IP address from a Freenode cloak
# Freenode tends to add IPs as suffix for gateway cloaks.
proc extract_addr_from_cloak {host} {
if {[string first gateway/web/cgi-irc/kiwiirc.com/ip. $host] == 0} {
return [string range $host 35 end]
}
# Finds an IPv4
# Regexp from http://www.jamesoff.net/site/projects/eggdrop-scripts/proxycheck
if {[regexp {[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$} $host ip]} {
return $ip
}
return ""
}
# Extracts an IP or hostname from an IRC host
# If the host is a cloak not parseable, returns "".
proc extract_addr {host} {
if {[string first / $host] > -1} {
return [extract_addr_from_cloak $host]
}
return $host
}
diff --git a/Daeghrefn/Communication.tcl b/Daeghrefn/Communication.tcl
index 876d653..7806f6d 100644
--- a/Daeghrefn/Communication.tcl
+++ b/Daeghrefn/Communication.tcl
@@ -1,501 +1,464 @@
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
bind pub - !styleencyclo pub:styleencyclo
bind pub - !style pub:styleencyclo
#
# 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 <the PIN code>"
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} {
}
# Publishes to special accounts
proc pub:idee {nick uhost handle chan text} {
twitter_pub_publish_to_account ideedarticles $nick $text
}
proc pub:styleencyclo {nick uhost handle chan text} {
twitter_pub_publish_to_account styleencyclo $nick $text
}
proc twitter_pub_publish_to_account {account nick text} {
set who [whois $nick]
if {$who == ""} {
append text " – via IRC."
} {
append text " – $who, via IRC."
}
twitterpublish $account $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 twitter_message_len {} { return 140 }
proc twitterpublish {account nick text} {
if {$text == ""} {
putquick "NOTICE $nick :Syntaxe : !pub <texte à publier sur identi.ca et Twitter>"
return
}
set len [twitter_compute_len $text]
if {$len > [twitter_message_len]} {
putquick "NOTICE $nick :[twitter_message_len] 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 <destinataire> \[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
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 12:27 (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2260164
Default Alt Text
(37 KB)

Event Timeline