Trumptruths.tcl

Text Detected Guest 7 Views Size: 10.77 KB Posted on: Jan 16, 26 @ 9:13 PM
  1.  
  2. # Announces new Trump posts via trumpstruth.org RSS
  3. # Feed: https://www.trumpstruth.org/feed
  4.  
  5. catch {setudef flag enabletruth}
  6. catch {setudef flag enableTruth}
  7.  
  8. namespace eval ::trumptruthrss {
  9.   variable cfg
  10.   array set cfg {
  11.     feed_url         "https://www.trumpstruth.org/feed"
  12.     poll_seconds     3600
  13.     curl             "/usr/bin/curl"
  14.     curl_timeout     15
  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"
  16.     state_file       "trumptruthrss.state"
  17.     max_chars        420
  18.     strip_non_ascii  1
  19.         exclude_retruths  1
  20.     max_new_announce 5
  21.     debug            0
  22.   }
  23.  
  24.   variable last_guid ""
  25.   variable in_progress 0
  26.   variable timer_id ""
  27. }
  28.  
  29. if {[info commands lreverse] eq ""} {
  30.   proc lreverse {lst} {
  31.     set out {}
  32.     foreach x $lst { set out [linsert $out 0 $x] }
  33.     return $out
  34.   }
  35. }
  36.  
  37. proc ::trumptruthrss::dlog {msg} {
  38.   variable cfg
  39.   if {$cfg(debug)} { putlog "trumptruthrss: $msg" }
  40. }
  41.  
  42. proc ::trumptruthrss::chan_enabled {chan} {
  43.   if {![catch {set v [channel get $chan enabletruth]}]} { return $v }
  44.   if {![catch {set v [channel get $chan enableTruth]}]} { return $v }
  45.   putlog "trumptruthrss: channel flag enabletruth/enableTruth not defined"
  46.   return 0
  47. }
  48.  
  49. proc ::trumptruthrss::enabled_channels {} {
  50.   set out {}
  51.   foreach c [channels] {
  52.     if {[::trumptruthrss::chan_enabled $c]} { lappend out $c }
  53.   }
  54.   return $out
  55. }
  56.  
  57. proc ::trumptruthrss::load_state {} {
  58.   variable cfg
  59.   variable last_guid
  60.   if {![file exists $cfg(state_file)]} { set last_guid ""; return }
  61.   catch {
  62.     set f [open $cfg(state_file) r]
  63.     set last_guid [string trim [read $f]]
  64.     close $f
  65.   }
  66. }
  67.  
  68. proc ::trumptruthrss::save_state {} {
  69.   variable cfg
  70.   variable last_guid
  71.   catch {
  72.     set f [open $cfg(state_file) w]
  73.     puts $f $last_guid
  74.     close $f
  75.   }
  76. }
  77.  
  78. proc ::trumptruthrss::is_retruth {item} {
  79.   # look for "ReTruth", "RT @user ..."
  80.   # Sometimes the link points away from @realDonaldTrump
  81.   set raw [string tolower [dict get $item text]]
  82.   if {[string first "retruth" $raw] >= 0} { return 1 }
  83.  
  84.   set clean [string tolower [::trumptruthrss::strip_html [dict get $item text]]]
  85.   if {[string first "rt @" $clean] == 0} { return 1 }
  86.  
  87.   if {[dict exists $item link]} {
  88.     set link [string tolower [dict get $item link]]
  89.     if {$link ne "" && [string first "%40realdonaldtrump" $link] < 0 && [string first "/@realdonaldtrump" $link] < 0} {
  90.       return 1
  91.     }
  92.   }
  93.  
  94.   return 0
  95. }
  96.  
  97. proc ::trumptruthrss::curl_fetch {url} {
  98.   variable cfg
  99.   set cmd [list $cfg(curl) -s -L --compressed -m $cfg(curl_timeout) \
  100.     -H "Accept: application/rss+xml, application/xml;q=0.9, */*;q=0.8" \
  101.     -H "User-Agent: $cfg(user_agent)" \
  102.     -w "\n__CURLMETA__:%{http_code}:%{content_type}\n" \
  103.     $url \
  104.   ]
  105.  
  106.   dlog "Fetching: $url"
  107.  
  108.   if {[catch {set out [eval exec $cmd]} err]} {
  109.     putlog "trumptruthrss: curl failed: $err"
  110.     return [list 0 "" ""]
  111.   }
  112.  
  113.   set code 0
  114.   set ctype ""
  115.   if {[regexp {__CURLMETA__:(\d+):([^\r\n]+)} $out -> code ctype]} {
  116.     regsub {(\r?\n)?__CURLMETA__:[0-9]+:[^\r\n]+(\r?\n)?$} $out "" body
  117.   } else {
  118.     set body $out
  119.   }
  120.  
  121.   dlog "HTTP $code (ctype=$ctype) bytes=[string length $body]"
  122.   return [list $code $ctype $body]
  123. }
  124.  
  125. proc ::trumptruthrss::html_decode {s} {
  126.   set s [string map {
  127.     "&amp;"  "&"
  128.     "&lt;"   "<"
  129.     "&gt;"   ">"
  130.     "&quot;" "\""
  131.     "&#34;"  "\""
  132.     "&#39;"  "'"
  133.     "&apos;" "'"
  134.     "&nbsp;" " "
  135.   } $s]
  136.   return $s
  137. }
  138.  
  139. proc ::trumptruthrss::strip_html {html} {
  140.   set s $html
  141.   regsub -all -nocase {<br\s*/?>} $s "\n" s
  142.   regsub -all -nocase {</p>\s*<p>} $s "\n" s
  143.   regsub -all {<[^>]+>} $s "" s
  144.   set s [::trumptruthrss::html_decode $s]
  145.   regsub -all {[[:space:]]+} $s " " s
  146.   return [string trim $s]
  147. }
  148.  
  149. proc ::trumptruthrss::rss_gettag {block tag} {
  150.   set open "<$tag"
  151.   set close "</$tag>"
  152.  
  153.   set i [string first $open $block]
  154.   if {$i < 0} { return "" }
  155.  
  156.   set gt [string first ">" $block $i]
  157.   if {$gt < 0} { return "" }
  158.  
  159.   set j [string first $close $block $gt]
  160.   if {$j < 0} { return "" }
  161.  
  162.   set val [string range $block [expr {$gt+1}] [expr {$j-1}]]
  163.   set val [string trim $val]
  164.  
  165.         if {[string first {<![CDATA[} $val] == 0} {
  166.           set end [string last {]]>} $val]
  167.           if {$end > 9} {
  168.                 set val [string range $val 9 [expr {$end-1}]]
  169.                 set val [string trim $val]
  170.           }
  171.             }
  172. }
  173.  
  174. proc ::trumptruthrss::parse_feed {xml} {
  175.   set head [string tolower [string range $xml 0 200]]
  176.   if {[string first "<!doctype" $head] >= 0 || [string first "<html" $head] >= 0} {
  177.     putlog "trumptruthrss: Got HTML instead of RSS/XML."
  178.     return {}
  179.   }
  180.  
  181.   if {([string first "<?xml" $head] < 0) && ([string first "<rss" $head] < 0) && ([string first "<feed" $head] < 0)} {
  182.     putlog "trumptruthrss: Response doesn't look like RSS/XML. Head: [string range $xml 0 120]"
  183.     return {}
  184.   }
  185.  
  186.   set items {}
  187.   set pos 0
  188.   while {1} {
  189.     set s [string first "<item" $xml $pos]
  190.     if {$s < 0} break
  191.     set e [string first "</item>" $xml $s]
  192.     if {$e < 0} break
  193.     lappend items [string range $xml $s [expr {$e+6}]]
  194.     set pos [expr {$e+7}]
  195.   }
  196.  
  197.   if {[llength $items] == 0} {
  198.     putlog "trumptruthrss: No <item> elements found."
  199.     return {}
  200.   }
  201.  
  202.   set out {}
  203.   foreach it $items {
  204.     set guid [rss_gettag $it "guid"]
  205.     set link [rss_gettag $it "link"]
  206.     set title [rss_gettag $it "title"]
  207.     set desc  [rss_gettag $it "description"]
  208.     set enc   [rss_gettag $it "content:encoded"]
  209.     set pub   [rss_gettag $it "pubDate"]
  210.  
  211.     set text ""
  212.     if {$enc ne ""} {
  213.       set text $enc
  214.     } elseif {$desc ne ""} {
  215.       set text $desc
  216.     } else {
  217.       set text $title
  218.     }
  219.  
  220.     if {$guid eq ""} {
  221.       if {$link ne ""} { set guid $link } else { set guid $title }
  222.     }
  223.  
  224.     lappend out [dict create guid $guid link $link text $text pubDate $pub]
  225.   }
  226.   return $out
  227. }
  228.  
  229. proc ::trumptruthrss::format_item {item} {
  230.   variable cfg
  231.   set text [::trumptruthrss::strip_html [dict get $item text]]
  232.   regsub -all {[[:space:]]+} $text " " text
  233.   set text [string trim $text]
  234.  
  235.   if {$cfg(strip_non_ascii)} {
  236.     regsub -all {[^\x00-\x7F]} $text "" text
  237.   }
  238.  
  239.   if {[string length $text] > $cfg(max_chars)} {
  240.     set text "[string range $text 0 [expr {$cfg(max_chars)-4}]]..."
  241.   }
  242.  
  243.   set pub [dict get $item pubDate]
  244.   set link [dict get $item link]
  245.  
  246.   set msg "Orange Truth: $text"
  247.   if {$pub ne ""}  { append msg " | $pub" }
  248.   if {$link ne ""} { append msg " | $link" }
  249.   return $msg
  250. }
  251.  
  252. proc ::trumptruthrss::announce_items {items} {
  253.   set chans [::trumptruthrss::enabled_channels]
  254.   if {[llength $chans] == 0} { return }
  255.   foreach it $items {
  256.     set msg [::trumptruthrss::format_item $it]
  257.     foreach c $chans {
  258.       putserv "PRIVMSG $c :$msg"
  259.     }
  260.   }
  261. }
  262.  
  263. proc ::trumptruthrss::schedule {} {
  264.   variable cfg
  265.   variable timer_id
  266.   if {$timer_id ne ""} { catch {killutimer $timer_id} }
  267.   set timer_id [utimer $cfg(poll_seconds) ::trumptruthrss::poll]
  268. }
  269.  
  270. proc ::trumptruthrss::poll {} {
  271.   variable cfg
  272.   variable last_guid
  273.   variable in_progress
  274.  
  275.   ::trumptruthrss::schedule
  276.   if {$in_progress} { return }
  277.   set in_progress 1
  278.  
  279.   if {[llength [::trumptruthrss::enabled_channels]] == 0} {
  280.     set in_progress 0
  281.     return
  282.   }
  283.  
  284.   lassign [::trumptruthrss::curl_fetch $cfg(feed_url)] code ctype body
  285.   if {$code != 200 || $body eq ""} {
  286.     putlog "trumptruthrss: fetch failed (HTTP=$code ctype=$ctype)"
  287.     set in_progress 0
  288.     return
  289.   }
  290.  
  291. set items [::trumptruthrss::parse_feed $body]
  292.  
  293. if {[info exists cfg(exclude_retruths)] && $cfg(exclude_retruths)} {
  294.   set filtered {}
  295.   foreach it $items {
  296.     if {[::trumptruthrss::is_retruth $it]} { continue }
  297.     lappend filtered $it
  298.   }
  299.   set items $filtered
  300. }
  301.  
  302. if {[llength $items] == 0} { set in_progress 0; return }
  303.  
  304.   if {$last_guid eq ""} {
  305.     set last_guid [dict get [lindex $items 0] guid]
  306.     ::trumptruthrss::save_state
  307.     set in_progress 0
  308.     return
  309.   }
  310.  
  311.   set new {}
  312.   set found 0
  313.   foreach it $items {
  314.     set g [dict get $it guid]
  315.     if {$g eq $last_guid} { set found 1; break }
  316.     lappend new $it
  317.   }
  318.  
  319.   if {[llength $new] == 0} { set in_progress 0; return }
  320.  
  321.   if {[llength $new] > $cfg(max_new_announce)} {
  322.     set new [lrange $new 0 [expr {$cfg(max_new_announce)-1}]]
  323.   }
  324.  
  325.   ::trumptruthrss::announce_items [lreverse $new]
  326.  
  327.   set last_guid [dict get [lindex $items 0] guid]
  328.   ::trumptruthrss::save_state
  329.   set in_progress 0
  330. }
  331.  
  332. proc ::trumptruthrss::cmd_latest {nick host hand chan text} {
  333.   variable cfg
  334.   if {![::trumptruthrss::chan_enabled $chan]} { return }
  335.  
  336.   lassign [::trumptruthrss::curl_fetch $cfg(feed_url)] code ctype body
  337.   if {$code != 200 || $body eq ""} {
  338.     putserv "PRIVMSG $chan :Orange Truth: couldn't fetch feed (HTTP=$code)."
  339.     return
  340.   }
  341.  
  342.   set items [::trumptruthrss::parse_feed $body]
  343.   if {[llength $items] == 0} {
  344.     putserv "PRIVMSG $chan :Orange Truth: couldn't parse feed."
  345.     return
  346.   }
  347.  
  348.   putserv "PRIVMSG $chan :[::trumptruthrss::format_item [lindex $items 0]]"
  349. }
  350.  
  351. proc ::trumptruthrss::cmd_sync {nick host hand chan text} {
  352.   variable cfg
  353.   variable last_guid
  354.   if {![::trumptruthrss::chan_enabled $chan]} { return }
  355.  
  356.   lassign [::trumptruthrss::curl_fetch $cfg(feed_url)] code ctype body
  357.   if {$code != 200 || $body eq ""} {
  358.     putserv "PRIVMSG $chan :Orange Truth: couldn't sync (HTTP=$code)."
  359.     return
  360.   }
  361.  
  362.   set items [::trumptruthrss::parse_feed $body]
  363.   if {[llength $items] == 0} {
  364.     putserv "PRIVMSG $chan :Orange Truth: couldn't parse feed."
  365.     return
  366.   }
  367.  
  368.   # Collect items newer than last_guid
  369.   set new {}
  370.   if {$last_guid ne ""} {
  371.     foreach it $items {
  372.       set g [dict get $it guid]
  373.       if {$g eq $last_guid} break
  374.       lappend new $it
  375.     }
  376.   } else {
  377.     # empty, treat everything in feed as "new"
  378.     set new $items
  379.   }
  380.  
  381.   if {[llength $new] == 0} {
  382.     putserv "PRIVMSG $chan :Orange Truth: no new posts."
  383.     return
  384.   }
  385.  
  386.   # Announce oldest to newest, paced
  387.   set new [lreverse $new]
  388.   set delay 0
  389.   foreach it $new {
  390.     set msg [::trumptruthrss::format_item $it]
  391.     utimer $delay [list putserv "PRIVMSG $chan :$msg"]
  392.     incr delay 2
  393.   }
  394.  
  395.   # Update pointer to newest item in feed
  396.   set last_guid [dict get [lindex $items 0] guid]
  397.   ::trumptruthrss::save_state
  398.  
  399.   putserv "PRIVMSG $chan :Orange Truth: synced (+[llength $new] announced)."
  400. }
  401.  
  402. catch {unbind pub - "!truth"     ::trumptruthrss::cmd_latest}
  403. catch {unbind pub - "!truthsync" ::trumptruthrss::cmd_sync}
  404. bind pub - "!truth"     ::trumptruthrss::cmd_latest
  405. bind pub - "!truthsync" ::trumptruthrss::cmd_sync
  406.  
  407. ::trumptruthrss::load_state
  408. ::trumptruthrss::schedule
  409. putlog "trumptruths.tcl loaded - Enable with: .chanset #chan +enabletruth"

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.