Compare commits

..

No commits in common. "0df41a7aae23246d198ec18e6c42c3dc37b03680" and "f6839fe42ae14623f924f65ca52bc9b8e5b42c21" have entirely different histories.

21 changed files with 164 additions and 470 deletions

View File

@ -1,9 +0,0 @@
#!/usr/bin/env ruby
require 'discordrb'
require_relative '../lib/ika'
require_relative '../lib/console_handler'
ika = Ika.for(type: ConsoleHandler)
ika.start!

View File

@ -1,13 +0,0 @@
#!/usr/bin/env ruby
require 'discordrb'
require_relative '../lib/ika'
require_relative '../lib/discord_handler'
token = File.read("./token")
bot = Discordrb::Bot.new(token:)
ika = Ika.for(type: DiscordHandler, bot:)
ika.start!

8
ika.rb Normal file
View File

@ -0,0 +1,8 @@
require 'discordrb'
require_relative './lib/ika'
token = File.read("./token")
ika = Ika.new Discordrb::Bot.new(token:)
ika.start!

4
lib/commands.rb Normal file
View File

@ -0,0 +1,4 @@
require_relative "commands/command"
require_relative "commands/help_command"
require_relative "commands/lesson_command"
require_relative "commands/stop_lesson_command"

46
lib/commands/command.rb Normal file
View File

@ -0,0 +1,46 @@
module Commands
@@commands = []
def self.register_command(command_class)
@@commands << command_class
end
def self.registered_commands
@@commands
end
class Command
def self.inherited(klass)
Commands.register_command klass
end
def self.for(ika, message)
Commands.registered_commands.find do |command_class|
command_class.matches? message.message.content
end&.new(ika, message)
end
def self.matches?(message_text)
message_text == "!#{command_string}"
end
def self.help
"!#{command_string} - #{description}"
end
def self.description
"blah blah blah"
end
attr_reader :ika, :message
def initialize(ika, message)
@ika = ika
@message = message
end
def execute
puts "This is a generic command! I can't execute anything!"
end
end
end

View File

@ -0,0 +1,16 @@
module Commands
class Help < Command
def self.command_string
"help"
end
def self.description
"Shows you a list of available commands"
end
def execute
message.respond ["I'm Ika! Here are the commands I know:",
*Commands.registered_commands.map(&:help)].join("\n")
end
end
end

View File

@ -0,0 +1,18 @@
module Commands
class LessonCommand < Command
def self.command_string
"lesson"
end
def self.description
"Starts timers for a lesson"
end
def execute
return message.respond("There's already a lesson running!") if ika.current_lesson_running?
message.respond("Starting a lesson now!")
ika.start_lesson! message
end
end
end

View File

@ -0,0 +1,18 @@
module Commands
class StopLessonCommand < Command
def self.command_string
"stoplesson"
end
def self.description
"Stops the current lesson, if one is running"
end
def execute
return message.respond("There's no lesson running") unless ika.current_lesson_running?
message.respond("Ending the current lesson!")
ika.stop_current_lesson!
end
end
end

View File

@ -1,19 +0,0 @@
require_relative './ika'
class ConsoleHandler < Ika
def start!
@handling_messages = true
while @handling_messages
handle_message Message::Console.new(responder(nil), gets.chomp)
end
end
def stop!
@handling_messages = false
end
def responder(_)
lambda { |response| puts "<Ika> #{response}" }
end
end

View File

@ -1,28 +0,0 @@
require_relative './ika'
class DiscordHandler < Ika
attr_reader :bot
def initialize(bot:)
super()
@bot = bot
end
def start!
bot.remove_handler @message_handler if @message_handler
@message_handler = bot.message do |message_event|
handle_message Message::Discord.new(responder(message_event.channel), message_event.message)
end
bot.run # you can run this in the background, idk
end
def stop!
bot.remove_handler @message_handler if @message_handler
end
def responder(channel)
lambda { |response| channel.send_message(response) }
end
end

View File

@ -1,48 +0,0 @@
class Drill
COMMANDS = %w[
init
start
end
]
attr_reader :channel
def initialize(channel)
@channel = channel
@exists = false
@started = false
end
def init!
@exists = true
end
def start!
@started = true
end
def end!
@started = false
end
def handle_message(message)
message.command_message ? handle_command(message) : handle_normal_message(message)
end
def handle_command(message)
command = message.command
if COMMANDS.includes?(command)
send("handle_#{command}", message)
else
puts "[Drill] Unknown command #{command}"
end
end
def started?
@started
end
def exists?
@exists
end
end

View File

@ -1,72 +0,0 @@
require_relative "drill"
class DrillPlugin
COMMANDS = %w[
help
]
attr_reader :bot, :drills
def initialize(bot)
@bot = bot
@drills = Hash.new { Drill.new }
end
def handle_message(message)
puts "[#{self.class.name}] Handling \"#{message.content}\" from #{message.author.display_name}"
command = strip_prefix(message.content).split.first
p command
if command.nil? || command == "" || command == "help" || command == "!drill"
handle_help message
elsif COMMANDS.include? command
drill = drills[message.channel.id]
send("handle_#{command}", message, drill)
else
drill = drills[message.channel.id]
drill.handle_message(message)
end
end
def handle_init(message, drill)
if drill.exists?
message.respond "You've already got a drill running in this channel"
else
drills[message.channel.id] = Drill.new message.channel
message.respond "New drill created!"
end
end
def handle_start(message, drill)
if drill.started?
message.respond "This drill has already started!"
elsif drill.exists?
drill.start!
message.respond "Drill started!"
else
message.respond "You have to `!drill init` before you can start the drill"
end
end
def handle_end(message, drill)
if !drill.exists?
message.respond "There's no drill to end"
elsif drill.started?
drills.delete message.channel.id
message.respond "Ending the drill now!"
else
message.respond "You have to `!drill start` before you can end the drill"
end
end
def handle_help(message)
message.respond "Insert help message here"
end
def strip_prefix(content)
if content.start_with? "!drill"
content.sub("!drill ", "")
else
content
end
end
end

View File

@ -1,26 +1,51 @@
require_relative './session'
require_relative './message'
require_relative './drill_plugin'
require_relative './ika/plugin'
require_relative './lesson'
require_relative './commands'
class Ika
attr_reader :plugins
attr_reader :bot
attr_reader :lessons, :sessions
attr_accessor :current_lesson, :current_session
PLUGIN_TYPES = [
Plugin::HelpPlugin,
Plugin::LessonPlugin
]
def self.for(type:, **kwargs)
type.new **kwargs
def initialize(bot)
@bot = bot
@lessons = {}
@sessions = {}
@current_session = nil
@current_lesson = Lesson.new
end
def initialize
@current_session = nil
@plugins = PLUGIN_TYPES.map { |plugin_type| plugin_type.new self }
def start!
bot.remove_handler @message_handler if @message_handler
@message_handler = bot.message do |message|
handle_message message
end
bot.run # you can run this in the background, idk
end
def stop!
bot.remove_handler @message_handler if @message_handler
end
def current_lesson_running?
current_lesson.running?
end
def start_lesson!(message)
current_lesson.start!(message.channel)
end
def stop_current_lesson!
current_lesson.stop!
end
def handle_message(message)
plugins.find { |plugin| plugin.handle_message message }
if command = Commands::Command.for(self, message)
command.execute
end
return current_session&.respond_to message
end
end

View File

@ -1,18 +0,0 @@
require_relative "./plugin/help_plugin"
require_relative "./plugin/lesson_plugin"
class Plugin
attr_reader :ika
def initialize(ika)
@ika = ika
end
def handle_message(message)
can_handle_message? message
end
def can_handle_message?(_)
false
end
end

View File

@ -1,14 +0,0 @@
class Plugin
class HelpPlugin < Plugin
def handle_message(message)
return false unless super
message.respond "I'm beyond help"
end
def can_handle_message?(message)
message.content == "!help"
end
end
end

View File

@ -1,80 +0,0 @@
require_relative "../../lesson"
class Plugin
class LessonPlugin < Plugin
attr_reader :lessons
def initialize(ika)
super
@lessons = {}
end
def handle_message(message)
super
Message.new(self, message).handle
end
def can_handle_message?(message)
return true if message.content == "!lesson"
return true if message.content == "!stoplesson"
return false unless lessons[message.channel.id]
true
end
class Message
attr_reader :plugin, :message
def initialize(plugin, message)
@plugin = plugin
@message = message
end
def content
message.content
end
def responder
message.responder
end
def respond(response)
responder.call response
end
def handle
return handle_lesson if content == "!lesson"
return handle_stoplesson if content == "!stoplesson"
lesson.handle_message(message)
end
def lesson
lsn = plugin.lessons[message.channel.id]
return lsn if lsn
lsn = Lesson.new responder: responder
plugin.lessons[message.channel.id] = lsn
end
def handle_lesson
if lesson.running?
respond "There's already a lesson running!"
else
respond "Starting a lesson now!"
lesson.start!
end
end
def handle_stoplesson
if lesson.running?
respond "Ending the current lesson"
lesson.stop!
else
respond "There's no lesson running"
end
end
end
end
end

View File

@ -8,33 +8,30 @@ DEFAULT_LESSON_PLAN = [
]
class Lesson
attr_reader :lesson_plan, :channel, :running, :scheduler
attr_accessor :responder
attr_reader :lesson_plan, :channel, :thread, :running, :scheduler
def initialize(lesson_plan=DEFAULT_LESSON_PLAN, scheduler: Rufus::Scheduler.new, responder: nil)
def initialize(lesson_plan=DEFAULT_LESSON_PLAN, scheduler: Rufus::Scheduler.new)
@lesson_plan = lesson_plan
@running = false
@scheduler = scheduler
@responder = responder
end
def stop!
@scheduler.shutdown
@running = false
end
def start!
def start!(channel)
lesson_plan.each_with_index do |(time, message), index|
offset = lesson_plan.map(&:first)[0..index - 1].sum(0)
offset = 0 if index == 0
scheduler.in "#{offset}m" do
responder.call "#{time} minutes to #{message}"
channel.send_message "#{time} minutes to #{message}"
end
end
end_offset = lesson_plan.map(&:first).sum(0)
scheduler.in "#{end_offset}m" do
responder.call "Lesson over!"
channel.send_message "Lesson over!"
@running = false
end
@ -44,8 +41,4 @@ class Lesson
def running?
running
end
def handle_message(_)
# TODO: make it possible to advance the lesson or whatever
end
end

View File

@ -1,109 +0,0 @@
class Message
attr_reader :responder
def initialize(responder)
@responder = responder
end
def content
# TODO: figure out how to delegate
discord_message.content
end
def author
# TODO: figure out how to delegate
discord_message.author
end
def channel
# TODO: figure out how to delegate
discord_message.channel
end
def respond(response)
# TODO: figure out how to delegate
discord_message.respond response
end
def message
discord_message
end
def words
content.split(" ")
end
def first_word
words.first
end
def command_message?
first_word.start_with? "!"
end
def command
command_message? ? first_word[1..] : nil
end
def subcommand
command_message? ? words[1] : nil
end
def params
command_message? ? words[2..] : []
end
class Console < Message
attr_reader :content
def initialize(responder, content)
super(responder)
@content = content
end
def author
OpenStruct.new(display_name: "CHANGE ME")
end
def channel
OpenStruct.new(id: nil)
end
def respond(response)
responder.call response
end
end
class Discord < Message
attr_reader :discord_message
def initialize(responder, discord_message)
super(responder)
@discord_message = discord_message
end
def content
# TODO: figure out how to delegate
discord_message.content
end
def author
# TODO: figure out how to delegate
discord_message.author
end
def channel
# TODO: figure out how to delegate
discord_message.channel
end
def respond(response)
# TODO: figure out how to delegate
discord_message.respond response
end
def message
discord_message
end
end
end

View File

@ -1,15 +0,0 @@
require_relative "../lib/discord_handler"
require "minitest/autorun"
class TestDiscordHandler < Minitest::Test
def setup
@bot = Minitest::Mock.new
@ika = DiscordHandler.new bot: @bot
end
def test_start!
@bot.expect(:message, nil)
@bot.expect(:run, nil)
@ika.start!
end
end

View File

@ -1,21 +1,16 @@
require_relative "../lib/ika"
require "minitest/autorun"
class TestHandler < Ika
attr_reader :responses
def initialize
@responses = []
end
def responder
lambda { |response| @responses << response }
end
end
class TestIka < Minitest::Test
def setup
@ika = TestHandler.new
@bot = Minitest::Mock.new
@ika = Ika.new @bot
end
def test_start
@bot.expect(:message, nil)
@bot.expect(:run, nil)
@ika.start!
end
def test_handle_command_message
@ -24,7 +19,6 @@ class TestIka < Minitest::Test
Commands::Command.stub(:for, @command) do |x|
@ika.handle_message("msg")
end
assert_equal [], @ika.responses
end
def test_handle_normal_message
@ -36,6 +30,5 @@ class TestIka < Minitest::Test
@ika.handle_message("msg")
end
end
assert_equal [], @ika.responses
end
end

View File

@ -4,12 +4,10 @@ require "minitest/autorun"
class TestLesson < Minitest::Test
def setup
@scheduler = Minitest::Mock.new
@responses = []
@responder = lambda { |rsp| @responses << rsp }
end
def test_start
@lesson = Lesson.new [[1, "test the thing"], [3, "finish testing"]], scheduler: @scheduler, responder: @responder
@lesson = Lesson.new [[1, "test the thing"], [3, "finish testing"]], scheduler: @scheduler
@scheduler.expect(:in, nil) { |time| time == "0m" }
@scheduler.expect(:in, nil) { |time| time == "1m" }
@scheduler.expect(:in, nil) { |time| time == "4m" }