# Announces new Trump posts via trumpstruth.org RSS # Feed: https://www.trumpstruth.org/feed catch {setudef flag enabletruth} catch {setudef flag enableTruth} namespace eval ::trumptruthrss { variable cfg array set cfg { feed_url "https://www.trumpstruth.org/feed" poll_seconds 3600 curl "/usr/bin/curl" curl_timeout 15 user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" state_file "trumptruthrss.state" max_chars 420 strip_non_ascii 1 exclude_retruths 1 max_new_announce 5 debug 0 } variable last_guid "" variable in_progress 0 variable timer_id "" } if {[info commands lreverse] eq ""} { proc lreverse {lst} { set out {} foreach x $lst { set out [linsert $out 0 $x] } return $out } } proc ::trumptruthrss::dlog {msg} { variable cfg if {$cfg(debug)} { putlog "trumptruthrss: $msg" } } proc ::trumptruthrss::chan_enabled {chan} { if {![catch {set v [channel get $chan enabletruth]}]} { return $v } if {![catch {set v [channel get $chan enableTruth]}]} { return $v } putlog "trumptruthrss: channel flag enabletruth/enableTruth not defined" return 0 } proc ::trumptruthrss::enabled_channels {} { set out {} foreach c [channels] { if {[::trumptruthrss::chan_enabled $c]} { lappend out $c } } return $out } proc ::trumptruthrss::load_state {} { variable cfg variable last_guid if {![file exists $cfg(state_file)]} { set last_guid ""; return } catch { set f [open $cfg(state_file) r] set last_guid [string trim [read $f]] close $f } } proc ::trumptruthrss::save_state {} { variable cfg variable last_guid catch { set f [open $cfg(state_file) w] puts $f $last_guid close $f } } proc ::trumptruthrss::is_retruth {item} { # look for "ReTruth", "RT @user ..." # Sometimes the link points away from @realDonaldTrump set raw [string tolower [dict get $item text]] if {[string first "retruth" $raw] >= 0} { return 1 } set clean [string tolower [::trumptruthrss::strip_html [dict get $item text]]] if {[string first "rt @" $clean] == 0} { return 1 } if {[dict exists $item link]} { set link [string tolower [dict get $item link]] if {$link ne "" && [string first "%40realdonaldtrump" $link] < 0 && [string first "/@realdonaldtrump" $link] < 0} { return 1 } } return 0 } proc ::trumptruthrss::curl_fetch {url} { variable cfg set cmd [list $cfg(curl) -s -L --compressed -m $cfg(curl_timeout) \ -H "Accept: application/rss+xml, application/xml;q=0.9, */*;q=0.8" \ -H "User-Agent: $cfg(user_agent)" \ -w "\n__CURLMETA__:%{http_code}:%{content_type}\n" \ $url \ ] dlog "Fetching: $url" if {[catch {set out [eval exec $cmd]} err]} { putlog "trumptruthrss: curl failed: $err" return [list 0 "" ""] } set code 0 set ctype "" if {[regexp {__CURLMETA__:(\d+):([^\r\n]+)} $out -> code ctype]} { regsub {(\r?\n)?__CURLMETA__:[0-9]+:[^\r\n]+(\r?\n)?$} $out "" body } else { set body $out } dlog "HTTP $code (ctype=$ctype) bytes=[string length $body]" return [list $code $ctype $body] } proc ::trumptruthrss::html_decode {s} { set s [string map { "&" "&" "<" "<" ">" ">" """ "\"" """ "\"" "'" "'" "'" "'" " " " " } $s] return $s } proc ::trumptruthrss::strip_html {html} { set s $html regsub -all -nocase {} $s "\n" s regsub -all -nocase {

\s*

} $s "\n" s regsub -all {<[^>]+>} $s "" s set s [::trumptruthrss::html_decode $s] regsub -all {[[:space:]]+} $s " " s return [string trim $s] } proc ::trumptruthrss::rss_gettag {block tag} { set open "<$tag" set close "" set i [string first $open $block] if {$i < 0} { return "" } set gt [string first ">" $block $i] if {$gt < 0} { return "" } set j [string first $close $block $gt] if {$j < 0} { return "" } set val [string range $block [expr {$gt+1}] [expr {$j-1}]] set val [string trim $val] if {[string first {} $val] if {$end > 9} { set val [string range $val 9 [expr {$end-1}]] set val [string trim $val] } } } proc ::trumptruthrss::parse_feed {xml} { set head [string tolower [string range $xml 0 200]] if {[string first "= 0 || [string first "= 0} { putlog "trumptruthrss: Got HTML instead of RSS/XML." return {} } if {([string first "" $xml $s] if {$e < 0} break lappend items [string range $xml $s [expr {$e+6}]] set pos [expr {$e+7}] } if {[llength $items] == 0} { putlog "trumptruthrss: No elements found." return {} } set out {} foreach it $items { set guid [rss_gettag $it "guid"] set link [rss_gettag $it "link"] set title [rss_gettag $it "title"] set desc [rss_gettag $it "description"] set enc [rss_gettag $it "content:encoded"] set pub [rss_gettag $it "pubDate"] set text "" if {$enc ne ""} { set text $enc } elseif {$desc ne ""} { set text $desc } else { set text $title } if {$guid eq ""} { if {$link ne ""} { set guid $link } else { set guid $title } } lappend out [dict create guid $guid link $link text $text pubDate $pub] } return $out } proc ::trumptruthrss::format_item {item} { variable cfg set text [::trumptruthrss::strip_html [dict get $item text]] regsub -all {[[:space:]]+} $text " " text set text [string trim $text] if {$cfg(strip_non_ascii)} { regsub -all {[^\x00-\x7F]} $text "" text } if {[string length $text] > $cfg(max_chars)} { set text "[string range $text 0 [expr {$cfg(max_chars)-4}]]..." } set pub [dict get $item pubDate] set link [dict get $item link] set msg "Orange Truth: $text" if {$pub ne ""} { append msg " | $pub" } if {$link ne ""} { append msg " | $link" } return $msg } proc ::trumptruthrss::announce_items {items} { set chans [::trumptruthrss::enabled_channels] if {[llength $chans] == 0} { return } foreach it $items { set msg [::trumptruthrss::format_item $it] foreach c $chans { putserv "PRIVMSG $c :$msg" } } } proc ::trumptruthrss::schedule {} { variable cfg variable timer_id if {$timer_id ne ""} { catch {killutimer $timer_id} } set timer_id [utimer $cfg(poll_seconds) ::trumptruthrss::poll] } proc ::trumptruthrss::poll {} { variable cfg variable last_guid variable in_progress ::trumptruthrss::schedule if {$in_progress} { return } set in_progress 1 if {[llength [::trumptruthrss::enabled_channels]] == 0} { set in_progress 0 return } lassign [::trumptruthrss::curl_fetch $cfg(feed_url)] code ctype body if {$code != 200 || $body eq ""} { putlog "trumptruthrss: fetch failed (HTTP=$code ctype=$ctype)" set in_progress 0 return } set items [::trumptruthrss::parse_feed $body] if {[info exists cfg(exclude_retruths)] && $cfg(exclude_retruths)} { set filtered {} foreach it $items { if {[::trumptruthrss::is_retruth $it]} { continue } lappend filtered $it } set items $filtered } if {[llength $items] == 0} { set in_progress 0; return } if {$last_guid eq ""} { set last_guid [dict get [lindex $items 0] guid] ::trumptruthrss::save_state set in_progress 0 return } set new {} set found 0 foreach it $items { set g [dict get $it guid] if {$g eq $last_guid} { set found 1; break } lappend new $it } if {[llength $new] == 0} { set in_progress 0; return } if {[llength $new] > $cfg(max_new_announce)} { set new [lrange $new 0 [expr {$cfg(max_new_announce)-1}]] } ::trumptruthrss::announce_items [lreverse $new] set last_guid [dict get [lindex $items 0] guid] ::trumptruthrss::save_state set in_progress 0 } proc ::trumptruthrss::cmd_latest {nick host hand chan text} { variable cfg if {![::trumptruthrss::chan_enabled $chan]} { return } lassign [::trumptruthrss::curl_fetch $cfg(feed_url)] code ctype body if {$code != 200 || $body eq ""} { putserv "PRIVMSG $chan :Orange Truth: couldn't fetch feed (HTTP=$code)." return } set items [::trumptruthrss::parse_feed $body] if {[llength $items] == 0} { putserv "PRIVMSG $chan :Orange Truth: couldn't parse feed." return } putserv "PRIVMSG $chan :[::trumptruthrss::format_item [lindex $items 0]]" } proc ::trumptruthrss::cmd_sync {nick host hand chan text} { variable cfg variable last_guid if {![::trumptruthrss::chan_enabled $chan]} { return } lassign [::trumptruthrss::curl_fetch $cfg(feed_url)] code ctype body if {$code != 200 || $body eq ""} { putserv "PRIVMSG $chan :Orange Truth: couldn't sync (HTTP=$code)." return } set items [::trumptruthrss::parse_feed $body] if {[llength $items] == 0} { putserv "PRIVMSG $chan :Orange Truth: couldn't parse feed." return } # Collect items newer than last_guid set new {} if {$last_guid ne ""} { foreach it $items { set g [dict get $it guid] if {$g eq $last_guid} break lappend new $it } } else { # empty, treat everything in feed as "new" set new $items } if {[llength $new] == 0} { putserv "PRIVMSG $chan :Orange Truth: no new posts." return } # Announce oldest to newest, paced set new [lreverse $new] set delay 0 foreach it $new { set msg [::trumptruthrss::format_item $it] utimer $delay [list putserv "PRIVMSG $chan :$msg"] incr delay 2 } # Update pointer to newest item in feed set last_guid [dict get [lindex $items 0] guid] ::trumptruthrss::save_state putserv "PRIVMSG $chan :Orange Truth: synced (+[llength $new] announced)." } catch {unbind pub - "!truth" ::trumptruthrss::cmd_latest} catch {unbind pub - "!truthsync" ::trumptruthrss::cmd_sync} bind pub - "!truth" ::trumptruthrss::cmd_latest bind pub - "!truthsync" ::trumptruthrss::cmd_sync ::trumptruthrss::load_state ::trumptruthrss::schedule putlog "trumptruths.tcl loaded - Enable with: .chanset #chan +enabletruth"