From f182fb24bf1d648b100cb9ef2eaf2ff4d23d22d8 Mon Sep 17 00:00:00 2001 From: zgrep Date: Sat, 19 Jun 2021 14:28:46 -0400 Subject: [PATCH] The state is _everywhere_. --- happybot/act-ignore.txt | 22 ++ happybot/act.sh | 24 +-- happybot/autojoin.sh | 26 +++ happybot/common.sh | 16 +- happybot/crontabguru.js | 444 ++++++++++++++++++++++++++++++++++++++ happybot/crontabguru.sh | 9 + happybot/def-limit.py | 61 ++++++ happybot/define.sh | 41 ++++ happybot/dowork/dowork.sh | 118 ++++++++++ happybot/ichi.py | 24 +++ happybot/ichi.sh | 9 + happybot/topic-diff.sh | 48 +++++ ii/ii.c | 105 ++++++--- launch | 30 ++- sortixlaunch | 22 ++ 15 files changed, 945 insertions(+), 54 deletions(-) create mode 100644 happybot/autojoin.sh create mode 100644 happybot/crontabguru.js create mode 100755 happybot/crontabguru.sh create mode 100644 happybot/def-limit.py create mode 100755 happybot/define.sh create mode 100755 happybot/dowork/dowork.sh create mode 100644 happybot/ichi.py create mode 100755 happybot/ichi.sh create mode 100755 happybot/topic-diff.sh create mode 100644 sortixlaunch diff --git a/happybot/act-ignore.txt b/happybot/act-ignore.txt index fed3023..0cae2eb 100644 --- a/happybot/act-ignore.txt +++ b/happybot/act-ignore.txt @@ -1,2 +1,24 @@ time tz +reload +rm +boom +destroy +wipe +timer +at +slapping +slapme +autoslap +doc +noslap +temp +temperature +weather +define +crontab +group +groups +tr +char +unicode diff --git a/happybot/act.sh b/happybot/act.sh index 2539224..f2749d5 100644 --- a/happybot/act.sh +++ b/happybot/act.sh @@ -3,15 +3,18 @@ . /home/zgrep/offtopiabday/happybot/common.sh irc | while read -r n m; do - m=$(var "$m" | sed 's/[^][A-Za-z0-9\\\-{}^|_` ]//g'); + if hreg '^happybot[:,]\s+[STst][lL]=' "$m"; then + continue + fi + m=$(var "$m" | sed 's/[^][A-Za-z0-9\\{}^|_` -]//g'); var "$m"; if reg '^(?:[hH][aA][pP][pP][yY]|[hH][aA][tT][eE])[bB][oO][tT] (\S+) (\S+)$' "$m" && ! grep -Fxq -- "$(m 1 | tr 'A-Z' 'a-z')" happybot/act-ignore.txt; then echo "$n requested that I do '$(m 1)' to '$(m 2)'."; - a=$(var "$(m 1)" | tr 'A-Z' 'a-z' | sed 's/[^a-z ]//g') - if [ -z "$a" ]; then - var "$n: You're not using me properly. :(" | zwsp | say; - elif [ "$a" == "zwsp" ]; then - echo "Ignoring zwsp request."; + who="$n" + a="$(m 1)" + fa=$(var "$a" | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9 ]//g') + if [ -z "$fa" ]; then + var "$n: That's not how I work. :(" | zwsp | say; else case "$(m 2)" in "happybot" | "hatebot" | "yourself" | "you" | "thyself" ) @@ -30,19 +33,16 @@ irc | while read -r n m; do "everybody" | "everyone" | "all") n="$chan" ;; - "errybody" | "erryone") - n="erry" - ;; *) n="$(m 2)"; ;; esac; - f="happybot/msgs/$a.sh"; + f="happybot/msgs/$fa.sh"; if [ -e "$f" ]; then - sh happybot/msgs/$a.sh "$n" "$chan" > /tmp/tmpact.txt; + sh happybot/msgs/$fa.sh "$n" "$chan" "$serv" > /tmp/tmpact.txt; f="/tmp/tmpact.txt"; else - f="happybot/msgs/$a.txt"; + f="happybot/msgs/$fa.txt"; if [ ! -e "$f" ]; then f="happybot/msgs/_.txt"; fi; diff --git a/happybot/autojoin.sh b/happybot/autojoin.sh new file mode 100644 index 0000000..cfa8a36 --- /dev/null +++ b/happybot/autojoin.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env ash + +. /home/zgrep/offtopiabday/happybot/common.sh + +chanlist="${chanlist:-}" +pwfile="${pwfile:-}" + +if [ -z "$chanlist" ]; then + echo "Need channel list." + exit 1 +fi + +# - Read logs. +# - Print when we get message 376. + +tail -n 0 -f "$serv/out" | while read -r l; do + if [ -z "$(var "$l" | sed -n '/^\S\S* \S\S* \[376\]/p')" ]; then + continue + fi + + if ! [ -z "$pwfile" ]; then + cat "$pwfile" > '/home/zgrep/offtopiabday/'"$serv"'/in' + sleep 0.5 + fi + echo '/JOIN '"$chanlist" > '/home/zgrep/offtopiabday/'"$serv"'/in' +done diff --git a/happybot/common.sh b/happybot/common.sh index 475ed06..822c7d3 100644 --- a/happybot/common.sh +++ b/happybot/common.sh @@ -1,7 +1,7 @@ #!/usr/bin/env ash chan="$1"; -serv="irc.freenode.net"; +serv="${serv:-irc.freenode.net}"; cd "/home/zgrep/offtopiabday" || exit 1; var() { printf '%s\n' "$1"; } @@ -10,7 +10,7 @@ alias safe="sed 's%^/%//%'" log="$serv/$chan/out"; irc() { - tail -0 -f "$serv/$chan/out" | while read -r l; do + tail -n 0 -f "$serv/$chan/out" | while read -r l; do var "$l" | sed 's/^[^<]*//'; done; } @@ -26,6 +26,18 @@ m() { var "$r" | sed -n "$1p"; } zwsp=$(python3 -c 'print("\u200b",end="")'); zwsp() { cat - | sed 's%^\(/m \)\?%\1'"$zwsp"'%'; } +x01=$(python3 -c 'print("\x01",end="")'); + +zirc() { + irc | while read -r n l; do + l="$(var "$l" | grep -vE "^${zwsp}|^${x01}ACTION ${zwsp}")" + if [ -z "$l" ]; then + continue + else + var "$n $l" + fi + done +} nth() { case "$1" in diff --git a/happybot/crontabguru.js b/happybot/crontabguru.js new file mode 100644 index 0000000..2796289 --- /dev/null +++ b/happybot/crontabguru.js @@ -0,0 +1,444 @@ +// From crontab.guru. + +// 10 +let describe = (function (e, t, n) { + 'use strict'; + function u(e) { + var t = parseInt(e); + switch (20 < t ? t % 10 : t) { + case 1: + return e + 'st'; + case 2: + return e + 'nd'; + case 3: + return e + 'rd'; + default: + return e + 'th' + } + } + function i(e, t, n, r) { + return '*' === e ? 'every ' + t : function (e, t, n, r) { + var o = e.match(/\d+|./g).map(function (e) { + var t = Number(e); + return isNaN(t) ? e : t + }), + a = o[0]; + if (Number.isInteger(a)) { + if (1 === o.length) return '' + (n[a] || a); + if (3 === o.length && '/' === o[1] && Number.isInteger(o[2])) return 'every ' + u(o[2]) + ' ' + t + ' from ' + (n[a] || a) + ' through ' + (n[r] || r); + if (3 === o.length && '-' === o[1] && Number.isInteger(o[2]) && o[2] >= a) return 'every ' + t + ' from ' + (n[a] || a) + ' through ' + (n[o[2]] || o[2]); + if (5 === o.length && '-' === o[1] && Number.isInteger(o[2]) && o[2] >= a && '/' === o[3] && Number.isInteger(o[4]) && 1 <= o[4]) return 'every ' + u(o[4]) + ' ' + t + ' from ' + (n[a] || a) + ' through ' + (n[o[2]] || o[2]) + } else if (3 === o.length && '/' === o[1] && Number.isInteger(o[2]) && '*' === o[0]) return 'every ' + u(o[2]) + ' ' + t; + return '' + }(e, t, n, r) + } + function h(e, t, n, r, o) { + var a = e.split(','); + return ((o ? '' : t + ' ') + function (e) { + switch (e.length) { + case 0: + return ''; + case 1: + return e[0]; + case 2: + return e[0] + ' and ' + e[1]; + default: + return e.slice(0, e.length - 1).join(', ') + ', and ' + e[e.length - 1] + } + }(a.map(function (e) { + return i(e, t, n, r) + }))).replace('every 1st', 'every').replace(t + ' every', 'every').replace(', ' + t, ', ').replace(', and ' + t, ', and ') + } + var v = [ + null, + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + var y = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday' + ]; + var b = /^0*\d\d?$/; + var g = 'After rebooting.'; + return function (e) { + if ('@reboot' === e.originalParts[0]) return { + full: g, + special: g + }; + var t, + n, + r, + o = e.parts, + a = '*' === (r = o[2]) ? '' : 'on ' + h(r, 'day-of-month', { + }, 31), + u = '*' === (n = o[3]) ? '' : 'in ' + h(n, 'month', v, 12, !0), + i = '*' === (t = o[4]) ? '' : 'on ' + h(t, 'day-of-week', y, 7, !0), + s = ''; + a && i && (s = e.daysAnded ? 'if it\'s' : 'and'); + var c, + l, + d = (c = o[0], l = o[1], b.test(c) && b.test(l) ? [ + ('0' + c).slice( - 2), + ('0' + l).slice( - 2) + ] : null); + if (d) return { + start: 'At', + minutes: d[0], + hours: d[1], + isTime: !0, + dates: a || null, + datesWeekdays: s || null, + weekdays: i || null, + months: u || null, + end: '.', + full: ('At ' + d[1] + ':' + d[0] + ' ' + a + ' ' + s + ' ' + i + ' ' + u).replace(/ +/g, ' ').trim() + '.' + }; + var m, + f = h(o[0], 'minute', { + }, 59), + p = '*' === (m = o[1]) ? '' : 'past ' + h(m, 'hour', { + }, 23); + return { + start: 'At', + minutes: f || null, + hours: p || null, + dates: a || null, + datesWeekdays: s || null, + weekdays: i || null, + months: u || null, + end: '.', + full: ('At ' + f + ' ' + p + ' ' + a + ' ' + s + ' ' + i + ' ' + u).replace(/ +/g, ' ').trim() + '.' + } + }; +})(); + +// 13 +let normalize = ( + function (e, t, n) { + 'use strict'; + function h(e, t) { + return e - t + } + function v(e) { + return e.reduce(function (e, t) { + return e.indexOf(t) < 0 && e.push(t), + e + }, [ + ]) + } + function r(e) { + return e.reduce(function (e, t) { + return e.concat(Array.isArray(t) ? r(t) : t) + }, [ + ]) + } + function o(e, t, n) { + for (var r = [ + ], o = e; o <= t; o += n) r.push(o); + return r + } + var a = /(^|[,-/])\*($|[,-/])/g; + function y(e, t) { + var n = '$1' + t + '$2'; + return e.replace(a, n).replace(a, n) + } + function b(e, t) { + var n = e.split(',').map(function (e) { + return function (e, t) { + var n = e ? e.match(/\d+|./g).map(function (e) { + var t = Number(e); + return isNaN(t) ? e : t + }) : [ + ], + r = n[0]; + if (Number.isInteger(r)) { + if (1 === n.length) return { + list: [ + r + ] + }; + if (3 === n.length && '/' === n[1] && Number.isInteger(n[2]) && 1 <= n[2]) return { + list: o(r, t, n[2]), + warnings: [ + 'nonstandard' + ] + }; + if (3 === n.length && '-' === n[1] && Number.isInteger(n[2]) && n[2] >= r) return { + list: o(r, n[2], 1) + }; + if (5 === n.length && '-' === n[1] && Number.isInteger(n[2]) && n[2] >= r && '/' === n[3] && Number.isInteger(n[4]) && 1 <= n[4]) return { + list: o(r, n[2], n[4]) + } + } + return { + errors: [ + 'invalid part' + ] + } + }(e, t) + }); + return { + list: v(r(n.map(function (e) { + return e.list || [ + ] + }))).sort(h).filter(function (e) { + return !isNaN(e) + }), + errors: v(r(n.map(function (e) { + return e.errors || [ + ] + }))), + warnings: v(r(n.map(function (e) { + return e.warnings || [ + ] + }))) + } + } + function g(e, t, n) { + return e.length && (e[0] < t || e[e.length - 1] > n) + } + var w = /[^\d\-\/\,]/i; + return function (e) { + var t = e.parts.map(function (e) { + return e.slice(0) + }).map(function (e) { + return e.replace(/\*\/1(?!\d)/g, '*') + }); + if (0 === t.length && e.originalParts.length) return { + }; + var n = { + errors: [ + ], + warnings: [ + ] + }; + if (void 0 !== e.daysAnded && (n.daysAnded = e.daysAnded), 5 !== t.length && n.errors.push('fields'), t[0] && t[0].length) { + var r = y(t[0], '0-59'), + o = b(r, 59); + n.minutes = o.list, + (o.errors.length || g(n.minutes, 0, 59) || w.test(r)) && (n.minutes = [ + ], n.errors.push('minutes')), + o.warnings.length && n.warnings.push('minutes') + } else void 0 === t[0] && n.errors.push('minutes'); + if (t[1] && t[1].length) { + var a = y(t[1], '0-23'), + u = b(a, 23); + n.hours = u.list, + (u.errors.length || g(n.hours, 0, 23) || w.test(a)) && (n.hours = [ + ], n.errors.push('hours')), + u.warnings.length && n.warnings.push('hours') + } else void 0 === t[1] && n.errors.push('hours'); + if (t[2] && t[2].length) { + var i = y(t[2], '1-31'), + s = b(i, 31); + n.dates = s.list, + (s.errors.length || g(n.dates, 1, 31) || w.test(i)) && (n.dates = [ + ], n.errors.push('dates')), + s.warnings.length && n.warnings.push('dates') + } else void 0 === t[2] && n.errors.push('dates'); + if (t[3] && t[3].length) { + var c = y(t[3], '1-12'), + l = e.originalParts[3], + d = b(c, 12); + n.months = d.list, + (d.errors.length || g(n.months, 1, 12) || w.test(c)) && (n.months = [ + ], n.errors.push('months')), + (d.warnings.length || l && t[3] !== l && 3 < l.length && /\D/.test(l)) && n.warnings.push('months') + } else void 0 === t[3] && n.errors.push('months'); + if (t[4] && t[4].length) { + var m = y(t[4], '0-6'), + f = e.originalParts[4], + p = b(m, 7); + n.weekdays = v(p.list.map(function (e) { + return 7 === e ? 0 : e + })).sort(h), + (p.errors.length || g(n.weekdays, 0, 6) || w.test(m)) && (n.weekdays = [ + ], n.errors.push('weekdays')), + (p.warnings.length || p.list.includes(7) || f && t[4] !== f && 3 < f.length && /\D/.test(f)) && n.warnings.push('weekdays') + } else void 0 === t[4] && n.errors.push('weekdays'); + return n.errors.length || delete n.errors, + n.warnings.length || delete n.warnings, + n + } + } +)(); + +// 15 +let parse = (function (e, t, n) { + 'use strict'; + function a(e, i) { + return Object.keys(i).reduce(function (e, t) { + return n = e, + o = i[r = t], + a = new RegExp('(^|[ ,-/])' + r + '($|[ ,-/])', 'gi'), + u = '$1' + o + '$2', + n.replace(a, u).replace(a, u); + var n, + r, + o, + a, + u + }, e) + } + var u = { + sun: '0', + mon: '1', + tue: '2', + wed: '3', + thu: '4', + fri: '5', + sat: '6' + }; + var i = { + jan: '1', + feb: '2', + mar: '3', + apr: '4', + may: '5', + jun: '6', + jul: '7', + aug: '8', + sep: '9', + oct: '10', + nov: '11', + dec: '12' + }; + var s = { + '@yearly': [ + '0', + '0', + '1', + '1', + '*' + ], + '@annually': [ + '0', + '0', + '1', + '1', + '*' + ], + '@monthly': [ + '0', + '0', + '1', + '*', + '*' + ], + '@weekly': [ + '0', + '0', + '*', + '*', + '0' + ], + '@daily': [ + '0', + '0', + '*', + '*', + '*' + ], + '@midnight': [ + '0', + '0', + '*', + '*', + '*' + ], + '@hourly': [ + '0', + '*', + '*', + '*', + '*' + ] + }; + return function (e) { + var t = e.trim().split(/\s+/).filter(function (e) { + return e + }); + if (1 === t.length && '@reboot' === t[0]) return { + originalParts: t, + parts: [ + ] + }; + var n, + r, + o = (1 === t.length ? (n = t[0], r = s[n], void 0 !== r ? r : [ + n + ]) : t).map(function (e, t) { + switch (t) { + case 3: + return a(e, i); + case 4: + return a(e, u); + default: + return e + } + }); return { + originalParts: t, + parts: o, + daysAnded: !!o[2] && '*' === o[2][0] || !!o[4] && '*' === o[4][0] + } + } + })(); + +let parsed, schedule; + +try { + parsed = parse(process.argv[2]); +} catch (error) { + console.log("Could not parse crontab (#1)."); + process.exit(1); +} + +try { + schedule = normalize(parsed); + if (schedule.errors) { + if (schedule.errors.includes('fields')) { + console.log('Errors with crontab fields. Should be: minute hour day-of-month month day-of-week'); + } else { + let copy = parsed.parts; + let r = [ + 'minutes', + 'hours', + 'dates', + 'months', + 'weekdays' + ]; + for (let f of schedule.errors) { + let c = r.indexOf(f); + copy[c] = '\x0304' + copy[c] + '\x03'; + } + console.log('Error with crontab: ' + copy.join(' ') + ' (' + schedule.errors.join(', ') + ')') + } + process.exit(0); + } +} catch (error) { + console.log("Could not parse crontab (#2)."); + process.exit(1); +} + +try { + console.log(describe(parsed).full); +} catch (error) { + console.log("Could not describe crontab."); + process.exit(1); +} diff --git a/happybot/crontabguru.sh b/happybot/crontabguru.sh new file mode 100755 index 0000000..9417489 --- /dev/null +++ b/happybot/crontabguru.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env ash + +. /home/zgrep/offtopiabday/happybot/common.sh + +irc | while read -r n m; do + if hreg '^happybot[:,] crontab (.+)$' "$m"; then + node happybot/crontabguru.js "$(m 1)" | zwsp | say; + fi +done diff --git a/happybot/def-limit.py b/happybot/def-limit.py new file mode 100644 index 0000000..4a1dedc --- /dev/null +++ b/happybot/def-limit.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import sys +from json import loads as dejson +from itertools import zip_longest + +if len(sys.argv) != 4: + print('Wrong number of arguments. Argue either more, or less.') + exit(1) + +_, say, defs, extra = sys.argv + +say = say.strip() +if say: + say += ' ' + +kmap = { + 'crossReference': 'x-ref', + 'abbreviation': 'abbr.', +} + +defs = dejson(defs) +rdefs = {} +order = [] +for d in defs: + for k, v in d.items(): + k = kmap.get(k, k) + if k not in order: + order.append(k) + rdefs[k] = rdefs.get(k, []) + [ v ] + +for k in order: + rdefs[k] = [ v for t in zip_longest(*rdefs[k], [], fillvalue='') for v in t if v ] + +defs = [ [ (k, v) for v in rdefs[k] ] for k in order ] +defs = [ v for l in zip(*defs) for v in l ] + +length = 450 +length -= len(say) +length -= len(extra) + +def pprint(thing): + return ' '.join('\x02[' + p + ']\x0f ' + ' / '.join(ds + (['…'] if len(rdefs[p]) > len(ds) else [])) for p, ds in thing) + +prevstr = '' + +m = {} +thing = [] +for k, v in defs: + if k in m: + thing[m[k]][1].append(v) + else: + m[k] = len(thing) + thing.append((k, [v])) + newstr = pprint(thing) + if len(newstr) <= length: + prevstr = newstr + else: + break + +print(say + prevstr + extra) diff --git a/happybot/define.sh b/happybot/define.sh new file mode 100755 index 0000000..7690e4d --- /dev/null +++ b/happybot/define.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env ash + +. /home/zgrep/offtopiabday/happybot/common.sh + +irc | while read -r n m; do + if hreg '^happybot[:,] define( -w)?( \S+)? (\S+)$' "$m"; then + lang="$(m 2)" + word="$(m 3)" + wikt="$(m 1)" + elif reg '^#define( -w)?( \S+)? (\S+)$' "$m"; then + lang="$(m 2)" + word="$(m 3)" + wikt="$(m 1)" + else + continue; + fi + if [ -z "$lang" ]; then + lang="en" + fi + if [ -z "$wikt" ]; then + result="$(curl -s -g -G 'https://api.dictionaryapi.dev/api/v2/entries/'"$lang"'/'"$word")" + else + result="{}" + fi + if [ "$(var "$result" | jq -r 'type')" == "object" ]; then + result="$(python3 wiktionary/wiktionary.py "$lang" "$word")" + var "$result" | if ! grep -q '^\['; then + var "$result" | zwsp | say; + else + phonetics="$(var "$result" | jq -r '.[0]|unique|join(" ")')" + definitions="$(var "$result" | jq -r '.[1]')" + extra="$(var "$result" | jq -r '.[2]')" + python3 happybot/def-limit.py "$phonetics" "$definitions" "$extra" | zwsp | say + fi + else + phonetics="$(var "$result" | jq -r 'map(.phonetics | map(.text)) | flatten | unique | join(" ")')" + definitions="$(var "$result" | jq -r 'map(.meanings | map( {(.partOfSpeech): (.definitions|map(.definition))} )) | flatten')" + extra="" + python3 happybot/def-limit.py "$phonetics" "$definitions" "$extra" | zwsp | say + fi +done diff --git a/happybot/dowork/dowork.sh b/happybot/dowork/dowork.sh new file mode 100755 index 0000000..06e19e7 --- /dev/null +++ b/happybot/dowork/dowork.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env ash + +. /home/zgrep/offtopiabday/happybot/common.sh + +d="happybot/dowork/people" +delay="300" +pause="7200" + +irc | while read -r n m; do + n="$(var "$n" | tr -d './')" + if hreg '^happybot[:,] (?:noslap|(?:autoslap|slap(?:ping|me)) (?:pause|delay|wait|0?\.5|1/2|½))(?: ([0-9]+))?' "$m"; then + pse="$(m 1)" + if [ -z "$pse" ]; then + pse="$pause" + fi + if [ -e "$d/$n.delay" ]; then + dly="$(cat "$d/$n.delay")" + else + dly="$delay" + fi + rpause="$(echo "$pse - $dly" | bc)" + echo "$(dateadd -f '%s' 'now' "${rpause}s")" > "$d/$n.slap"; + echo "${zwsp}Automatic slapping for $n paused until about ${pse}s from now." | say + continue + + elif hreg '^happybot[:,] (autoslap|slap(ping|me)) (public|loud)(ly)?' "$m"; then + grep -Fvxe "$n" "${d}_private.txt" > "${d}_private.2" + mv "${d}_private.2" "${d}_private.txt" + echo "${zwsp}I will automatically slap $n in public." | say; + continue + elif hreg '^happybot[:,] (autoslap|slap(ping|me)) (priv(msg|ate)?|secret(ive)?|quiet)(ly)?' "$m"; then + echo "$n" >> "${d}_private.txt" + sort -u "${d}_private.txt" > "${d}_private.2" + mv "${d}_private.2" "${d}_private.txt" + echo "${zwsp}I will automatically slap $n privately." | say; + continue + + elif hreg '^happybot[:,] (autoslap|slap(ping|me)) (stop|off|disable|no|0)' "$m"; then + grep -Fvxe "$n" "$d.txt" > "$d.2" + mv "$d.2" "$d.txt" + if [ -e "$d/$n.slap" ]; then + rm "$d/$n.slap"; + fi + if [ -e "$d/$n.delay" ]; then + rm "$d/$n.delay"; + fi + echo "${zwsp}I will no longer automatically slap $n." | say + continue + + elif hreg '^happybot[:,] (?:autoslap|slap(?:ping|me)) (?:start|on|enable|yes|1|set)(?: ([0-9]+))?' "$m"; then + dly="$(m 1)" + if [ ! -z "$dly" ]; then + echo "$dly" > "$d/$n.delay"; + else + dly="$delay"; + fi + echo "$n" >> "$d.txt" + sort -u "$d.txt" > "$d.2" + mv "$d.2" "$d.txt" + echo "${zwsp}I will slap $n every time they speak, at most once every ${dly}s." | say + continue + + elif hreg '^happybot[:,] (?:autoslap|slap(?:ping|me)) status(?: (\S+))?' "$m"; then + newn="$(m 1)" + if [ ! -z "$newn" ]; then + n="$newn" + fi + if grep -Fxqe "$n" "${d}_private.txt"; then + slaptype="private"; + else + slaptype="public"; + fi + if grep -Fxqe "$n" "$d.txt"; then + delta="$(datediff -i '%s' -f '%S' "$(cat "$d/$n.slap")" 'now')" + if [ -e "$d/$n.delay" ]; then + dly="$(cat "$d/$n.delay")" + else + dly="$delay" + fi + if [ "$delta" -le "$dly" ]; then + delta="$(echo "$dly - $delta" | bc)" + pstat="Not slapping for another ${delta}s." + else + pstat="Currently primed to slap." + fi + echo "${zwsp}I'm supposed to ${slaptype}ly slap ${n} at most once every ${dly}s. ${pstat}" | say; + else + echo "${zwsp}No automatic ${slaptype} slapping for ${n} yet." | say; + fi + continue + + elif [ -s "$d.txt" ]; then + if grep -Fxqe "$n" "$d.txt"; then + if [ -e "$d/$n.slap" ]; then + delta="$(datediff -i '%s' -f '%S' "$(cat "$d/$n.slap")" 'now')" + if [ -e "$d/$n.delay" ]; then + dly="$(cat "$d/$n.delay")" + else + dly="$delay" + fi + if [ "$delta" -le "$dly" ]; then + continue + fi + fi + echo "$(date +%s)" > "$d/$n.slap" + if [ -e "$d/$n.txt" ]; then + obj="$(rand "$d/$n.txt")" + else + obj="$(rand "${d}_generic.txt")" + fi + if grep -Fxqe "$n" "${d}_private.txt"; then + echo "/PRIVMSG ${n} :${x01}ACTION ${zwsp}slaps you with $obj.${x01}" | say; + else + echo "/m ${zwsp}slaps $n with $obj." | say; + fi + fi; + fi; +done; diff --git a/happybot/ichi.py b/happybot/ichi.py new file mode 100644 index 0000000..5f6bdb1 --- /dev/null +++ b/happybot/ichi.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import requests as rq +from lxml import html + +from sys import argv +if len(argv) != 2: + print('Not enough arguments. Bug zgrep.') + exit(1) + +q = argv[1] + +r = rq.get('http://ichi.moe/cl/qr/', params={'q': q}) + +if r.status_code != 200: + print('Non-200 status code. Bug zgrep.') + exit() + +# tree +t = html.fromstring(r.content) + +ichi = ''.join(t.xpath('//span[@class="ds-text"]//text()')) + +print(ichi) diff --git a/happybot/ichi.sh b/happybot/ichi.sh new file mode 100755 index 0000000..4c2575d --- /dev/null +++ b/happybot/ichi.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env ash + +. /home/zgrep/offtopiabday/happybot/common.sh + +irc | while read -r n m; do + if reg '^(happy|hate)bot[:,] ichi (.*)' "$m"; then + python3 happybot/ichi.py "$(m 2)" | say; + fi; +done; diff --git a/happybot/topic-diff.sh b/happybot/topic-diff.sh new file mode 100755 index 0000000..77cf5e2 --- /dev/null +++ b/happybot/topic-diff.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +. /home/zgrep/offtopiabday/happybot/common.sh + +segment() { + sed 's/[a-zA-Z][a-zA-Z]*:\/\/\S*\|./&\n/g' | sed -n '/./p' +} + +oldtopic="$( + tac "$serv/$chan/out" \ + | awk '{if($3" "$5$6$7=="-!- changedtopicto"){print;exit}}' \ + | sed 's/^[^"]*"//;s/"$//' +)" + +tail -n 0 -f "$serv/$chan/out" | gawk '{if($3" "$5$6$7=="-!- changedtopicto"){gsub(/^[^"]*"/,"");gsub(/"$/,"");print;fflush("/dev/stdout")}}' | while read -r t; do + if [[ "$t" != "$oldtopic" ]]; then + diff="(diff) " + mode="_" + diff="$(diff -a -d -U 1024 <(var "$oldtopic" | segment) <(var "$t" | segment) \ + | tail -n+4 | sed 's/^ /_/;s/^./& _/;s/$/_/' \ + | while read -r d l; do + l="$(var "$l" | sed 's/^_//;s/_$//')" + p="" + if [[ "$mode" != "$d" ]]; then + if [[ "$d" == "-" ]]; then + if [[ "$mode" == "+" ]]; then + p="$(echo -ne "\x0305,99")" + else + p="$(echo -ne "\x1f\x0305,99")" + fi + elif [[ "$d" == "+" ]]; then + if [[ "$mode" == "-" ]]; then + p="$(echo -ne "\x0303,99")" + else + p="$(echo -ne "\x1f\x0303,99")" + fi + elif [[ "$d" == "_" ]]; then + p="$(echo -ne "\x0f")" + fi + mode="$d" + fi + diff="$diff$p$l" + var "$diff" + done | tail -n1)" + var "$diff" | zwsp | say + oldtopic="$t" + fi +done diff --git a/ii/ii.c b/ii/ii.c index 0a83690..119fd02 100644 --- a/ii/ii.c +++ b/ii/ii.c @@ -18,12 +18,23 @@ #include #include #include +#include +#include +#include #ifndef PIPE_BUF /* For OS that doesn't includes PIPE_BUF in limits.h, FreeBSD? */ #define PIPE_BUF _POSIX_PIPE_BUF #endif #define PING_TIMEOUT 300 #define SERVER_PORT 6667 +#define SSL_SERVER_PORT 6697 +#define WRITE(con, mes, len) (use_ssl ? SSL_write(irc->sslHandle, mes, len) : write(con->irc, mes, len)) +#define READ(fd, buf, size) (from_server && use_ssl ? SSL_read(irc->sslHandle, buf, size) : read(fd, buf, size)) +typedef struct { + int irc; + SSL *sslHandle; + SSL_CTX *sslContext; +} conn; enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST }; typedef struct Channel Channel; @@ -33,7 +44,8 @@ struct Channel { Channel *next; }; -static int irc; +conn *irc; +static int use_ssl; static time_t last_response; static Channel *channels = NULL; static char *host = "irc.freenode.net"; @@ -46,7 +58,7 @@ static void usage() { fputs("ii - irc it - " VERSION "\n" "(C)opyright MMV-MMVI Anselm R. Garbe\n" "(C)opyright MMV-MMXI Nico Golde\n" - "usage: ii [-i ] [-s ] [-p ]\n" + "usage: ii [-i ] [-s ] [-p ] [-e ssl]\n" " [-n ] [-k ] [-f ]\n", stderr); exit(EXIT_FAILURE); } @@ -149,11 +161,12 @@ static void login(char *key, char *fullname) { nick, nick, host, fullname ? fullname : nick); else snprintf(message, PIPE_BUF, "NICK %s\r\nUSER %s localhost %s :%s\r\n", nick, nick, host, fullname ? fullname : nick); - write(irc, message, strlen(message)); /* login */ + WRITE(irc, message, strlen(message)); /* login */ } -static int tcpopen(unsigned short port) { +conn *tcpopen(unsigned short port) { int fd; + conn *c; struct sockaddr_in sin; struct hostent *hp = gethostbyname(host); @@ -173,7 +186,22 @@ static int tcpopen(unsigned short port) { perror("ii: cannot connect to host"); exit(EXIT_FAILURE); } - return fd; + c = malloc(sizeof(conn)); + c->irc = fd; + if(use_ssl) { + c->sslHandle = NULL; + c->sslContext = NULL; + SSL_load_error_strings(); + SSL_library_init(); + c->sslContext = SSL_CTX_new(SSLv23_client_method()); + if(c->sslContext == NULL) + ERR_print_errors_fp(stderr); + c->sslHandle = SSL_new(c->sslContext); + if(!SSL_set_fd(c->sslHandle, c->irc) + || (SSL_connect(c->sslHandle) != 1)) + ERR_print_errors_fp(stderr); + } + return c; } static size_t tokenize(char **result, size_t reslen, char *str, char delim) { @@ -220,7 +248,7 @@ static void proc_channels_privmsg(char *channel, char *buf) { snprintf(message, PIPE_BUF, "<%s> %s", nick, buf); print_out(channel, message); snprintf(message, PIPE_BUF, "PRIVMSG %s :%s\r\n", channel, buf); - write(irc, message, strlen(message)); + WRITE(irc, message, strlen(message)); } static void proc_channels_input(Channel *c, char *buf) { @@ -275,7 +303,7 @@ static void proc_channels_input(Channel *c, char *buf) { else snprintf(message, PIPE_BUF, "PART %s :byeeeee\r\n", c->name); - write(irc, message, strlen(message)); + WRITE(irc, message, strlen(message)); close(c->fd); /*create_filepath(infile, sizeof(infile), c->name, "in"); unlink(infile); */ @@ -311,7 +339,7 @@ static void proc_channels_input(Channel *c, char *buf) { } if (message[0] != '\0') - write(irc, message, strlen(message)); + WRITE(irc, message, strlen(message)); } static void proc_server_cmd(char *buf) { @@ -359,67 +387,71 @@ static void proc_server_cmd(char *buf) { tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' '); - if(!argv[TOK_CMD] || !strncmp("PONG", argv[TOK_CMD], 5)) { + if(!argv[TOK_CMD]) { return; + } else if(!strncmp("PONG", argv[TOK_CMD], 5)) { + snprintf(message, PIPE_BUF, "-!- pong %s", argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); } else if(!strncmp("PING", argv[TOK_CMD], 5)) { snprintf(message, PIPE_BUF, "PONG %s\r\n", argv[TOK_TEXT]); - write(irc, message, strlen(message)); + WRITE(irc, message, strlen(message)); return; } else if(!argv[TOK_NICKSRV] || !argv[TOK_USER]) { /* server command */ - snprintf(message, PIPE_BUF, "%s%s", argv[TOK_ARG] ? argv[TOK_ARG] : "", argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + snprintf(message, PIPE_BUF, "[%s] %s%s", argv[TOK_CMD], argv[TOK_ARG] ? argv[TOK_ARG] : "", argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); print_out(0, message); return; - } else if(!strncmp("ERROR", argv[TOK_CMD], 6)) + } else if(!strncmp("ERROR", argv[TOK_CMD], 6)) { snprintf(message, PIPE_BUF, "-!- error %s", argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown"); - else if(!strncmp("JOIN", argv[TOK_CMD], 5) && (argv[TOK_CHAN] || argv[TOK_TEXT])) { + } else if(!strncmp("JOIN", argv[TOK_CMD], 5) && (argv[TOK_CHAN] || argv[TOK_TEXT])) { if (argv[TOK_TEXT] != NULL) argv[TOK_CHAN] = argv[TOK_TEXT]; snprintf(message, PIPE_BUF, "-!- %s(%s) has joined %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); } else if(!strncmp("PART", argv[TOK_CMD], 5) && argv[TOK_CHAN]) { snprintf(message, PIPE_BUF, "-!- %s(%s) has left %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); - } else if(!strncmp("MODE", argv[TOK_CMD], 5)) + } else if(!strncmp("MODE", argv[TOK_CMD], 5)) { snprintf(message, PIPE_BUF, "-!- %s changed mode/%s -> %s %s", argv[TOK_NICKSRV], argv[TOK_CMD + 1] ? argv[TOK_CMD + 1] : "" , argv[TOK_CMD + 2]? argv[TOK_CMD + 2] : "", argv[TOK_CMD + 3] ? argv[TOK_CMD + 3] : ""); - else if(!strncmp("QUIT", argv[TOK_CMD], 5)) + } else if(!strncmp("QUIT", argv[TOK_CMD], 5)) { snprintf(message, PIPE_BUF, "-!- %s(%s) has quit \"%s\"", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); - else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] && !strcmp(_nick, argv[TOK_TEXT])) { + } else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] && !strcmp(_nick, argv[TOK_TEXT])) { snprintf(nick, sizeof(nick), "%s", _nick); snprintf(message, PIPE_BUF, "-!- changed nick to \"%s\"", nick); print_out(NULL, message); - } else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT]) + } else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT]) { snprintf(message, PIPE_BUF, "-!- %s changed nick to %s", argv[TOK_NICKSRV], argv[TOK_TEXT]); - else if(!strncmp("TOPIC", argv[TOK_CMD], 6)) + } else if(!strncmp("TOPIC", argv[TOK_CMD], 6)) { snprintf(message, PIPE_BUF, "-!- %s changed topic to \"%s\"", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); - else if(!strncmp("KICK", argv[TOK_CMD], 5) && argv[TOK_ARG]) + } else if(!strncmp("KICK", argv[TOK_CMD], 5) && argv[TOK_ARG]) { snprintf(message, PIPE_BUF, "-!- %s kicked %s (\"%s\")", argv[TOK_NICKSRV], argv[TOK_ARG], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); - else if(!strncmp("NOTICE", argv[TOK_CMD], 7)) - snprintf(message, PIPE_BUF, "-!- \"%s\")", argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); - else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8)) + } else if(!strncmp("NOTICE", argv[TOK_CMD], 7)) { + snprintf(message, PIPE_BUF, "[%s] %s", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8)) { snprintf(message, PIPE_BUF, "<%s> %s", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); - else + } else { return; /* can't read this message */ + } - if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick))) + if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick))) { print_out(argv[TOK_NICKSRV], message); - else + } else { print_out(argv[TOK_CHAN], message); + } } -static int read_line(int fd, size_t res_len, char *buf) { +static int read_line(int fd, size_t res_len, char *buf, int from_server) { size_t i = 0; char c = 0; do { - if(read(fd, &c, sizeof(char)) != sizeof(char)) + if(READ(fd, &c, sizeof(char)) != sizeof(char)) { return -1; + } buf[i++] = c; - } - while(c != '\n' && i < res_len); + } while (c != '\n' && i < res_len); buf[i - 1] = 0; /* eliminates '\n' */ return 0; } static void handle_channels_input(Channel *c) { static char buf[PIPE_BUF]; - if(read_line(c->fd, PIPE_BUF, buf) == -1) { + if(read_line(c->fd, PIPE_BUF, buf, 0) == -1) { close(c->fd); int fd = open_channel(c->name); if(fd != -1) @@ -433,7 +465,7 @@ static void handle_channels_input(Channel *c) { static void handle_server_output() { static char buf[PIPE_BUF]; - if(read_line(irc, PIPE_BUF, buf) == -1) { + if(read_line(irc->irc, PIPE_BUF, buf, 1) == -1) { perror("ii: remote host closed connection"); exit(EXIT_FAILURE); } @@ -450,8 +482,8 @@ static void run() { snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host); for(;;) { FD_ZERO(&rd); - maxfd = irc; - FD_SET(irc, &rd); + maxfd = irc->irc; + FD_SET(irc->irc, &rd); for(c = channels; c; c = c->next) { if(maxfd < c->fd) maxfd = c->fd; @@ -471,10 +503,10 @@ static void run() { print_out(NULL, "-!- ii shutting down: ping timeout"); exit(EXIT_FAILURE); } - write(irc, ping_msg, strlen(ping_msg)); + WRITE(irc, ping_msg, strlen(ping_msg)); continue; } - if(FD_ISSET(irc, &rd)) { + if(FD_ISSET(irc->irc, &rd)) { handle_server_output(); last_response = time(NULL); } @@ -508,10 +540,13 @@ int main(int argc, char *argv[]) { case 'p': port = strtol(argv[++i], NULL, 10); break; case 'n': snprintf(nick,sizeof(nick),"%s", argv[++i]); break; case 'k': key = getenv(argv[++i]); break; + case 'e': use_ssl = 1; ++i; break; case 'f': fullname = argv[++i]; break; default: usage(); break; } } + if(use_ssl) + port = port == SERVER_PORT ? SSL_SERVER_PORT : port; irc = tcpopen(port); if(!snprintf(path, sizeof(path), "%s/%s", prefix, host)) { fputs("ii: path to irc directory too long\n", stderr); diff --git a/launch b/launch index a09df7e..982738c 100644 --- a/launch +++ b/launch @@ -1,6 +1,26 @@ #!/usr/bin/env sh -nick="happybot" -if [ -e '/home/zgrep/offtopiabday/hateweekfile' ]; then - nick="hatebot"; -fi; -ii/ii -i ~/offtopiabday -s irc.freenode.net -p 6667 -n "$nick" -f 'Happy birthday! By which I mean existential crisis.' +initn="1" +maxn="3600" + +olddate="$(date +%s)" +n="$initn" + +while true; do + nick="happybot" + if [ -e '/home/zgrep/offtopiabday/hateweekfile' ]; then + nick="hatebot"; + fi; + ii/ii -i ~/offtopiabday -e ssl -s irc.libera.chat -p 6697 -n "$nick" -f 'Happy birthday! By which I mean existential crisis.' + + echo "[$(date)] Oh no, ii died." + if [ "$(date +%s)" -lt "$(($olddate + $n + 60))" ]; then + n="$((n * 7 / 2))" + if [ "$n" -gt "$maxn" ]; then + n="$maxn" + fi + else + n="$initn" + fi + olddate="$(date +%s)" + sleep "$n" +done diff --git a/sortixlaunch b/sortixlaunch new file mode 100644 index 0000000..7b6d67a --- /dev/null +++ b/sortixlaunch @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +initn="1" +maxn="3600" + +olddate="$(date +%s)" +n="$initn" + +while true; do + ii/ii -i ~/offtopiabday -s irc.sortix.org -e ssl -p 6697 -n "happybot" -f 'Happy birthday! By which I mean existential crisis.' + echo "[$(date)] Oh no, ii died." + if [ "$(date +%s)" -lt "$(($olddate + $n + 60))" ]; then + n="$((n * 7 / 2))" + if [ "$n" -gt "$maxn" ]; then + n="$maxn" + fi + else + n="$initn" + fi + olddate="$(date +%s)" + sleep "$n" +done