From 50057d5149dda340b3b47aca4096f4a6ec66b9ee Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch
Date: Fri, 6 Aug 2021 16:12:15 +0200
Subject: Come up with sillier names for the binaries
I'm not entirely sure, but it looks like some people might not like
jokes about the Holocaust.
On a more serious note, the project has become more serious over
the 7 or so years of its existence.
---
plugins/xB/pomodoro | 502 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 502 insertions(+)
create mode 100755 plugins/xB/pomodoro
(limited to 'plugins/xB/pomodoro')
diff --git a/plugins/xB/pomodoro b/plugins/xB/pomodoro
new file mode 100755
index 0000000..08b87cb
--- /dev/null
+++ b/plugins/xB/pomodoro
@@ -0,0 +1,502 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+#
+# xB pomodoro plugin
+#
+# Copyright 2015 Přemysl Eric Janouch
+# See the file LICENSE for licensing information.
+#
+
+# --- Simple event loop --------------------------------------------------------
+
+# This is more or less a straight-forward port of my C event loop. It's a bit
+# unfortunate that I really have to implement all this in order to get some
+# basic asynchronicity but at least I get to exercise my Ruby.
+
+class TimerEvent
+ attr_accessor :index, :when, :callback
+
+ def initialize (callback)
+ raise ArgumentError unless callback.is_a? Proc
+
+ @index = nil
+ @when = nil
+ @callback = callback
+ end
+
+ def active?
+ @index != nil
+ end
+
+ def until
+ return @when - Time.new
+ end
+end
+
+class IOEvent
+ READ = 1 << 0
+ WRITE = 1 << 1
+
+ attr_accessor :read_index, :write_index, :io, :callback
+
+ def initialize (io, callback)
+ raise ArgumentError unless callback.is_a? Proc
+
+ @read_index = nil
+ @write_index = nil
+ @io = io
+ @callback = callback
+ end
+end
+
+class EventLoop
+ def initialize
+ @running = false
+ @timers = []
+ @readers = []
+ @writers = []
+ @io_to_event = {}
+ end
+
+ def set_timer (timer, timeout)
+ raise ArgumentError unless timer.is_a? TimerEvent
+
+ timer.when = Time.now + timeout
+ if timer.index
+ heapify_down timer.index
+ heapify_up timer.index
+ else
+ timer.index = @timers.size
+ @timers.push timer
+ heapify_up timer.index
+ end
+ end
+
+ def reset_timer (timer)
+ raise ArgumentError unless timer.is_a? TimerEvent
+ remove_timer_at timer.index if timer.index
+ end
+
+ def set_io (io_event, events)
+ raise ArgumentError unless io_event.is_a? IOEvent
+ raise ArgumentError unless events.is_a? Numeric
+
+ reset_io io_event
+
+ @io_to_event[io_event.io] = io_event
+ if events & IOEvent::READ
+ io_event.read_index = @readers.size
+ @readers.push io_event.io
+ end
+ if events & IOEvent::WRITE
+ io_event.read_index = @writers.size
+ @writers.push io_event.io
+ end
+ end
+
+ def reset_io (io_event)
+ raise ArgumentError unless io_event.is_a? IOEvent
+
+ @readers.delete_at io_event.read_index if io_event.read_index
+ @writers.delete_at io_event.write_index if io_event.write_index
+
+ io_event.read_index = nil
+ io_event.write_index = nil
+
+ @io_to_event.delete io_event.io
+ end
+
+ def run
+ @running = true
+ while @running do one_iteration end
+ end
+
+ def quit
+ @running = false
+ end
+
+private
+ def one_iteration
+ rs, ws, = IO.select @readers, @writers, [], nearest_timeout
+ dispatch_timers
+ (Array(rs) | Array(ws)).each do |io|
+ @io_to_event[io].callback.call io
+ end
+ end
+
+ def dispatch_timers
+ now = Time.new
+ while not @timers.empty? and @timers[0].when <= now do
+ @timers[0].callback.call
+ remove_timer_at 0
+ end
+ end
+
+ def nearest_timeout
+ return nil if @timers.empty?
+ timeout = @timers[0].until
+ if timeout < 0 then 0 else timeout end
+ end
+
+ def remove_timer_at (index)
+ @timers[index].index = nil
+ moved = @timers.pop
+ return if index == @timers.size
+
+ @timers[index] = moved
+ @timers[index].index = index
+ heapify_down index
+ end
+
+ def swap_timers (a, b)
+ @timers[a], @timers[b] = @timers[b], @timers[a]
+ @timers[a].index = a
+ @timers[b].index = b
+ end
+
+ def heapify_up (index)
+ while index != 0 do
+ parent = (index - 1) / 2
+ break if @timers[parent].when <= @timers[index].when
+ swap_timers index, parent
+ index = parent
+ end
+ end
+
+ def heapify_down (index)
+ loop do
+ parent = index
+ left = 2 * index + 1
+ right = 2 * index + 2
+
+ lowest = parent
+ lowest = left if left < @timers.size and
+ @timers[left] .when < @timers[lowest].when
+ lowest = right if right < @timers.size and
+ @timers[right].when < @timers[lowest].when
+ break if parent == lowest
+
+ swap_timers lowest, parent
+ index = lowest
+ end
+ end
+end
+
+# --- IRC protocol -------------------------------------------------------------
+
+$stdin.set_encoding 'ASCII-8BIT'
+$stdout.set_encoding 'ASCII-8BIT'
+
+$stdin.sync = true
+$stdout.sync = true
+
+$/ = "\r\n"
+$\ = "\r\n"
+
+RE_MSG = /(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(?: +(.*))?$/
+RE_ARGS = /:?((?<=:).*|[^ ]+) */
+
+def parse (line)
+ m = line.match RE_MSG
+ return nil if not m
+
+ nick, user, host, command, args = *m.captures
+ args = if args then args.scan(RE_ARGS).flatten else [] end
+ [nick, user, host, command, args]
+end
+
+def bot_print (what)
+ print "ZYKLONB print :#{what}"
+end
+
+# --- Initialization -----------------------------------------------------------
+
+# We can only read in configuration from here so far
+# To read it from anywhere else, it has to be done asynchronously
+$config = {}
+[:prefix].each do |name|
+ print "ZYKLONB get_config :#{name}"
+ _, _, _, _, args = *parse($stdin.gets.chomp)
+ $config[name] = args[0]
+end
+
+print "ZYKLONB register"
+
+# --- Plugin logic -------------------------------------------------------------
+
+# FIXME: this needs a major refactor as it doesn't make much sense at all
+
+class MessageMeta < Struct.new(:nick, :user, :host, :channel, :ctx, :quote)
+ def respond (message)
+ print "PRIVMSG #{ctx} :#{quote}#{message}"
+ end
+end
+
+class Context
+ attr_accessor :nick, :ctx
+
+ def initialize (meta)
+ @nick = meta.nick
+ @ctx = meta.ctx
+ end
+
+ def == (other)
+ self.class == other.class \
+ and other.nick == @nick \
+ and other.ctx == @ctx
+ end
+
+ alias eql? ==
+
+ def hash
+ @nick.hash ^ @ctx.hash
+ end
+end
+
+class PomodoroTimer
+ def initialize (context)
+ @ctx = context.ctx
+ @nicks = [context.nick]
+
+ @timer_work = TimerEvent.new(lambda { on_work })
+ @timer_rest = TimerEvent.new(lambda { on_rest })
+
+ on_work
+ end
+
+ def inform (message)
+ # FIXME: it tells the nick even in PM's
+ quote = "#{@nicks.join(" ")}: "
+ print "PRIVMSG #{@ctx} :#{quote}#{message}"
+ end
+
+ def on_work
+ inform "work now!"
+ $loop.set_timer @timer_rest, 25 * 60
+ end
+
+ def on_rest
+ inform "rest now!"
+ $loop.set_timer @timer_work, 5 * 60
+ end
+
+ def join (meta)
+ return if @nicks.include? meta.nick
+
+ meta.respond "you have joined their pomodoro"
+ @nicks |= [meta.nick]
+ end
+
+ def part (meta, requested)
+ return if not @nicks.include? meta.nick
+
+ if requested
+ meta.respond "you have stopped your pomodoro"
+ end
+
+ @nicks -= [meta.nick]
+ if @nicks.empty?
+ $loop.reset_timer @timer_work
+ $loop.reset_timer @timer_rest
+ end
+ end
+
+ def status (meta)
+ return if not @nicks.include? meta.nick
+
+ if @timer_rest.active?
+ till = @timer_rest.until
+ meta.respond "working, #{(till / 60).to_i} minutes, " +
+ "#{(till % 60).to_i} seconds until rest"
+ end
+ if @timer_work.active?
+ till = @timer_work.until
+ meta.respond "resting, #{(till / 60).to_i} minutes, " +
+ "#{(till % 60).to_i} seconds until work"
+ end
+ end
+end
+
+class Pomodoro
+ KEYWORD = "pomodoro"
+
+ def initialize
+ @timers = {}
+ end
+
+ def on_help (meta, args)
+ meta.respond "usage: #{KEYWORD} { start | stop | join