twitchbot 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/twitchbot.rb +5 -0
- data/lib/twitchbot/bot.rb +74 -0
- data/lib/twitchbot/channel.rb +22 -0
- data/lib/twitchbot/event_handler.rb +50 -0
- data/lib/twitchbot/message.rb +78 -0
- data/lib/twitchbot/message_plugin.rb +47 -0
- data/lib/twitchbot/plugin.rb +55 -0
- data/lib/twitchbot/plugin/auth_plugin.rb +37 -0
- data/lib/twitchbot/plugin/channel_plugin.rb +51 -0
- data/lib/twitchbot/plugin/debug_plugin.rb +34 -0
- data/lib/twitchbot/plugin/message_queue_plugin.rb +24 -0
- data/lib/twitchbot/plugin/ping_plugin.rb +16 -0
- data/lib/twitchbot/timed_plugin.rb +47 -0
- data/lib/twitchbot/user.rb +80 -0
- data/lib/twitchbot/version.rb +3 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4eb24be58e5d670664da6a08c56cf3007ed3477b08c0d3e73bae57d7ee4bac29
|
4
|
+
data.tar.gz: 1ae2d56adbb8bdd41529888c3abb7d7b9f509147beddedc0fe385e54104c5b73
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a1354939320f1d974ab95107db9385e801aebf0f6b3c090d0f6a7d33f9db6c0a17bccd8e94179b6ecf8112d6f0a20623c7d3ce81a74f36c497c7170596dcdb64
|
7
|
+
data.tar.gz: ac3048f9253dcac655ec2a99787057a3ff9e3a8937af159576d11087d784b1a61d2d168fba7d701ff362630eea4294610056bf99e2eb1979bfe27263eceb9b71
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Charles Ray Shisler III
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Twitchbot
|
2
|
+
|
3
|
+
A plugin-based framework for creating Twitch chat bots, written in Ruby and based on `EventMachine` and `Faye::WebSocket`.
|
4
|
+
|
5
|
+
Notes:
|
6
|
+
* Plugins are first class citizens
|
7
|
+
* Helper functions implemented to gate-keep commands
|
8
|
+
* Bot can currently only join one channel (it might stay that way)
|
9
|
+
* Read the source until I get all the documentation completed
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'twitchbot'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install twitchbot
|
26
|
+
|
27
|
+
## Getting Started
|
28
|
+
|
29
|
+
Require Twitchbot
|
30
|
+
|
31
|
+
require 'twitchbot'
|
32
|
+
|
33
|
+
Create Plugins
|
34
|
+
|
35
|
+
# MessagePlugin, which listens for the registered command preceeded by the Bot command_prefix
|
36
|
+
class HiPlugin
|
37
|
+
include Twitchbot::MessagePlugin
|
38
|
+
|
39
|
+
register command: 'hi', method: :say_hi
|
40
|
+
|
41
|
+
def say_hi(message, arg)
|
42
|
+
message.respond 'Hello world!'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# TimedPlugin, which fires off the registered command periodically
|
47
|
+
class ShoutOutPlugin
|
48
|
+
include Twitchbot::TimedPlugin
|
49
|
+
|
50
|
+
register method: :social, interval: 15 # Time in seconds
|
51
|
+
|
52
|
+
def social(handler)
|
53
|
+
handler.send_channel('Hello! Check my social media out!')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Plugin, which listens for registered commands according to the raw IRC command
|
58
|
+
class PutsPlugin
|
59
|
+
include Twitchbot::Plugin
|
60
|
+
|
61
|
+
register command: 'PRIVMSG', method: :put_string
|
62
|
+
|
63
|
+
def put_string(handler)
|
64
|
+
handler.messages.each do |message|
|
65
|
+
puts message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Create and start `Bot`
|
71
|
+
|
72
|
+
bot = Twitchbot::Bot.new do |bot|
|
73
|
+
bot.username = 'bot_name'
|
74
|
+
bot.password = 'oauth:password'
|
75
|
+
bot.channel = 'channel_name'
|
76
|
+
bot.plugins = [HiPlugin, ShoutOutPlugin, PutsPlugin]
|
77
|
+
bot.debug = true
|
78
|
+
# bot.command_prefix = '$' # Default is '!'
|
79
|
+
end
|
80
|
+
|
81
|
+
bot.start
|
82
|
+
|
83
|
+
## Development
|
84
|
+
|
85
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
86
|
+
|
87
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
88
|
+
|
89
|
+
## Contributing
|
90
|
+
|
91
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/craysiii/twitchbot.
|
92
|
+
|
93
|
+
## License
|
94
|
+
|
95
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'twitchbot'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
data/bin/setup
ADDED
data/lib/twitchbot.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'faye/websocket'
|
3
|
+
|
4
|
+
require_relative 'event_handler'
|
5
|
+
require_relative 'channel'
|
6
|
+
require_relative 'plugin/debug_plugin'
|
7
|
+
require_relative 'plugin/auth_plugin'
|
8
|
+
require_relative 'plugin/ping_plugin'
|
9
|
+
require_relative 'plugin/channel_plugin'
|
10
|
+
require_relative 'plugin/message_queue_plugin'
|
11
|
+
|
12
|
+
module Twitchbot
|
13
|
+
# Main Bot class that we pass all bot account information to, as well as any
|
14
|
+
# plugins that should be used
|
15
|
+
class Bot
|
16
|
+
# @return [String] Username of the bot account
|
17
|
+
attr_accessor :username
|
18
|
+
# @return [String] Password of the bot account
|
19
|
+
attr_accessor :password
|
20
|
+
# @return [Channel] The channel that the bot is connected to
|
21
|
+
attr_accessor :channel
|
22
|
+
# @return [Array] The plugins that are in use by the bot
|
23
|
+
attr_accessor :plugins
|
24
|
+
# @return [Boolean] Whether the bot should display debug info to STDOUT
|
25
|
+
attr_accessor :debug
|
26
|
+
# @return [Array] The array of messages that are to be sent to the server
|
27
|
+
attr_accessor :message_queue
|
28
|
+
# @return [String] The prefix to use when defining and parsing commands e.g. +!+
|
29
|
+
attr_accessor :command_prefix
|
30
|
+
|
31
|
+
# The connection URL for Twitch
|
32
|
+
DEFAULT_URL = 'wss://irc-ws.chat.twitch.tv'.freeze
|
33
|
+
# The built-in plugins to be used
|
34
|
+
DEFAULT_PLUGINS = [AuthPlugin,
|
35
|
+
PingPlugin,
|
36
|
+
ChannelPlugin,
|
37
|
+
MessageQueuePlugin,
|
38
|
+
DebugPlugin].freeze
|
39
|
+
# The events that eventmachine dispatches to any plugins in use
|
40
|
+
DEFAULT_EVENTS = %i[error close open message].freeze
|
41
|
+
|
42
|
+
# Create a new Bot instance, passing a block to set the necessary
|
43
|
+
# attributes to have the bot function
|
44
|
+
def initialize
|
45
|
+
@username = ''
|
46
|
+
@password = ''
|
47
|
+
@channel = ''
|
48
|
+
@plugins = []
|
49
|
+
@message_queue = Queue.new
|
50
|
+
@command_prefix = '!'
|
51
|
+
|
52
|
+
yield self
|
53
|
+
|
54
|
+
@channel = Channel.new(@channel)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Start the event loop, initiate the websocket client, and register the
|
58
|
+
# plugins with eventmachine
|
59
|
+
def start
|
60
|
+
EM.run do
|
61
|
+
connection = Faye::WebSocket::Client.new DEFAULT_URL
|
62
|
+
plugins = (@plugins << DEFAULT_PLUGINS).flatten!.reverse!.map! &:new
|
63
|
+
DEFAULT_EVENTS.each do |default_event|
|
64
|
+
connection.on(default_event) do |em_event|
|
65
|
+
handler = EventHandler.new em_event, connection, self
|
66
|
+
plugins.each do |plugin|
|
67
|
+
plugin.send(default_event, handler)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Twitchbot
|
2
|
+
# Class responsible for keeping track of channel attributes such as channel
|
3
|
+
# state and users in the channel
|
4
|
+
#
|
5
|
+
# TODO: Implement channel states e.g. r9k, emote-only, sub-only, slow
|
6
|
+
# TODO: Capture channel id from tags
|
7
|
+
class Channel
|
8
|
+
# @return [String] Name of the channel we have joined
|
9
|
+
attr_reader :name
|
10
|
+
# @return [Hash] Mapping of user objects according to their username
|
11
|
+
attr_accessor :users
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
@users = {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@name
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative 'message'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Class responsible for handling the eventmachine event that is provided
|
5
|
+
# whenever an event is fired
|
6
|
+
class EventHandler
|
7
|
+
# @return [Bot] The bot that the channel is a member of
|
8
|
+
attr_reader :bot
|
9
|
+
# @return [Array] The different messages received
|
10
|
+
attr_reader :messages
|
11
|
+
# @return [Faye::WebSocket::Client] The WebSocket client instance
|
12
|
+
attr_reader :connection
|
13
|
+
|
14
|
+
def initialize(event, connection, bot)
|
15
|
+
@connection = connection
|
16
|
+
@bot = bot
|
17
|
+
|
18
|
+
if event.respond_to? :data
|
19
|
+
@chunks = event.data.split "\n"
|
20
|
+
@messages = []
|
21
|
+
@chunks.each do |chunk|
|
22
|
+
@messages << Message.new(self, chunk.chomp)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add a raw message to the message queue
|
28
|
+
def send_raw(message)
|
29
|
+
@bot.message_queue.push(message)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add a formatted channel message to the message queue
|
33
|
+
def send_channel(message)
|
34
|
+
@bot.message_queue.push("PRIVMSG ##{bot.channel.name} :#{message}")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a whisper to the specified user to the message queue
|
38
|
+
def send_whisper(user, message)
|
39
|
+
@bot.message_queue.push("PRIVMSG jtv :/w #{user} :#{message}")
|
40
|
+
end
|
41
|
+
|
42
|
+
# Method that provides a shortcut to grab the first message in an event
|
43
|
+
# handler. We can typically use this after authenticating, but there is no
|
44
|
+
# guarantee that twitch will not send multiple 'messages' in a single
|
45
|
+
# +:message+ event
|
46
|
+
def message
|
47
|
+
@messages.first
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'user'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Class responsible for parsing messages created in [EventHandler]
|
5
|
+
#
|
6
|
+
# TODO: Clean this up because its ugly
|
7
|
+
# TODO: Parse message-id
|
8
|
+
class Message
|
9
|
+
# @return [Integer] Number of bits that have been included in a message
|
10
|
+
attr_reader :bits
|
11
|
+
# @return [User] User that the message was sent by
|
12
|
+
attr_reader :user
|
13
|
+
# @return [Channel] Channel that the message was sent to
|
14
|
+
attr_reader :channel
|
15
|
+
# @return [String] IRC command of the raw IRC message
|
16
|
+
attr_reader :command
|
17
|
+
# @return [String] Raw IRC message received
|
18
|
+
attr_reader :raw
|
19
|
+
# @return [String] Sender of the IRC message
|
20
|
+
attr_reader :sender
|
21
|
+
# @return [String] Target of the IRC message
|
22
|
+
attr_reader :target
|
23
|
+
# @return [String] Content of the IRC message
|
24
|
+
attr_reader :payload
|
25
|
+
|
26
|
+
def initialize(handler, raw_message)
|
27
|
+
@handler = handler
|
28
|
+
@raw = raw_message
|
29
|
+
msg = @raw.dup
|
30
|
+
@tags = msg.slice! /^\S+/ if tagged?
|
31
|
+
|
32
|
+
msg.lstrip!
|
33
|
+
/^(?<sender>:\S+) (?<command>\S+)( (?<target>\S+))?( (?<payload>.+))?$/ =~ msg
|
34
|
+
@sender = sender
|
35
|
+
@command = command
|
36
|
+
@target = target
|
37
|
+
@payload = payload
|
38
|
+
|
39
|
+
if message?
|
40
|
+
@payload.slice! 0, 1
|
41
|
+
@channel = @handler.bot.channel
|
42
|
+
/bits=(?<bits>\d+)/ =~ @tags
|
43
|
+
@bits = bits.nil? ? 0 : bits.to_i
|
44
|
+
/display-name=(?<display_name>\w+)/ =~ @tags
|
45
|
+
/user-id=(?<user_id>[a-zA-Z0-9\-]+)/ =~ @tags
|
46
|
+
/badges=(?<badges>[a-zA-Z\/,0-9\-]+)/ =~ @tags
|
47
|
+
badges = badges || ''
|
48
|
+
/:(?<user>\w+)/ =~ @sender
|
49
|
+
if @channel.users.key? user
|
50
|
+
@channel.users[user].update_attributes display_name, user_id, badges
|
51
|
+
else
|
52
|
+
@channel.users[user] = User.new user, display_name, user_id, badges
|
53
|
+
end
|
54
|
+
@user = @channel.users[user]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Method to determine if the IRC message includes any tags from the +:twitch.tv/tags+ capability
|
59
|
+
def tagged?
|
60
|
+
@raw.start_with? '@'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Method to determine if the IRC message is an actual message to the [Channel] by a [User]
|
64
|
+
def message?
|
65
|
+
@command.eql? 'PRIVMSG'.freeze
|
66
|
+
end
|
67
|
+
|
68
|
+
# Method to respond to the IRC message target with a private message
|
69
|
+
def respond(message)
|
70
|
+
@handler.bot.message_queue.push("PRIVMSG #{@target} :#{message}")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Method to determine if the IRC message is a PING challenge
|
74
|
+
def ping?
|
75
|
+
@raw.start_with? 'PING'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Twitchbot
|
2
|
+
module MessagePlugin
|
3
|
+
|
4
|
+
COMMANDS = {}
|
5
|
+
|
6
|
+
# Method that can be overriden to react to the eventmachine +:open+ event
|
7
|
+
def open(handler) end
|
8
|
+
|
9
|
+
def message(handler)
|
10
|
+
handler.messages.each do |message|
|
11
|
+
if message.message?
|
12
|
+
prefix = handler.bot.command_prefix
|
13
|
+
_, _command, arguments = message.payload.partition(
|
14
|
+
/#{Regexp.escape prefix}\S+/
|
15
|
+
)
|
16
|
+
command = _command.delete prefix
|
17
|
+
commands = COMMANDS[self.class]
|
18
|
+
if !command.nil? && !commands[command].nil?
|
19
|
+
send(commands[command], message, arguments)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Method that can be overriden to react to the eventmachine +:error+ event
|
26
|
+
def error(handler) end
|
27
|
+
|
28
|
+
# Method that can be overriden to react to the eventmachine +:close+ event
|
29
|
+
def close(handler) end
|
30
|
+
|
31
|
+
# Define a class method called register on each class that includes this
|
32
|
+
# module, which allows the user to add methods to the +COMMANDS+ constant
|
33
|
+
#
|
34
|
+
# def self.register(command:, method:)
|
35
|
+
# COMMANDS[base] = {} if COMMANDS[base].nil?
|
36
|
+
# COMMANDS[base][params[:command]] = params[:method]
|
37
|
+
# end
|
38
|
+
def self.included(klass)
|
39
|
+
klass.instance_eval do
|
40
|
+
define_singleton_method 'register' do |params|
|
41
|
+
COMMANDS[klass] = {} if COMMANDS[klass].nil?
|
42
|
+
COMMANDS[klass][params[:command]] = params[:method]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Twitchbot
|
2
|
+
# Base Plugin module that listens for IRC commands and calls the associated
|
3
|
+
# method of the class that includes it
|
4
|
+
module Plugin
|
5
|
+
|
6
|
+
# The commands that are registered to this base module via classes that
|
7
|
+
# include it
|
8
|
+
#
|
9
|
+
# Example state during runtime:
|
10
|
+
# { AuthPlugin: { '376': :request_caps }, ChannelPlugin: { 'CAP': :join_channel, 'JOIN': :process_join, 'PART': :process_part } }
|
11
|
+
COMMANDS = {}
|
12
|
+
|
13
|
+
# Method that can be overriden to react to the eventmachine +:open+ event
|
14
|
+
def open(handler) end
|
15
|
+
|
16
|
+
# Method that reacts to the eventmachine +:message+ event and processes each
|
17
|
+
# message in the {EventHandler}, calling the appropriate method if available.
|
18
|
+
#
|
19
|
+
# It is not recommended to override this method unless you plan on handling
|
20
|
+
# all logic for reacting to methods yourself.
|
21
|
+
def message(handler)
|
22
|
+
handler.messages.each do |message|
|
23
|
+
command = message&.command
|
24
|
+
# Grab all registered methods of the including class
|
25
|
+
commands = COMMANDS[self.class]
|
26
|
+
if !command.nil? && !commands[command].nil?
|
27
|
+
# Call the including class method and pass the EventHandler to it
|
28
|
+
send(commands[command], handler)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Method that can be overriden to react to the eventmachine +:error+ event
|
34
|
+
def error(handler) end
|
35
|
+
|
36
|
+
# Method that can be overriden to react to the eventmachine +:close+ event
|
37
|
+
def close(handler) end
|
38
|
+
|
39
|
+
# Define a class method called register on each class that includes this
|
40
|
+
# module, which allows the user to add methods to the +COMMANDS+ constant
|
41
|
+
#
|
42
|
+
# def self.register(command:, method:)
|
43
|
+
# COMMANDS[base] = {} if COMMANDS[base].nil?
|
44
|
+
# COMMANDS[base][params[:command]] = params[:method]
|
45
|
+
# end
|
46
|
+
def self.included(klass)
|
47
|
+
klass.instance_eval do
|
48
|
+
define_singleton_method 'register' do |params|
|
49
|
+
COMMANDS[klass] = {} if COMMANDS[klass].nil?
|
50
|
+
COMMANDS[klass][params[:command]] = params[:method]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../plugin'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Plugin to handle authenticating the bot account and authorizing the use of
|
5
|
+
# all available Twitch IRCv3 capabilities
|
6
|
+
class AuthPlugin
|
7
|
+
include Twitchbot::Plugin
|
8
|
+
|
9
|
+
# The capabilities available to request
|
10
|
+
CAPABILITIES = %w(
|
11
|
+
twitch.tv/tags
|
12
|
+
twitch.tv/commands
|
13
|
+
twitch.tv/membership
|
14
|
+
).freeze
|
15
|
+
|
16
|
+
# Send bot account credentials after the connection has opened
|
17
|
+
def open(handler)
|
18
|
+
handler.send_raw "PASS #{handler.bot.password}"
|
19
|
+
handler.send_raw "NICK #{handler.bot.username}"
|
20
|
+
end
|
21
|
+
|
22
|
+
register command: '376', method: :request_caps
|
23
|
+
# Listen for the last message of a successful authentication attempt and
|
24
|
+
# request capabilities
|
25
|
+
#
|
26
|
+
# > :tmi.twitch.tv 001 bot :Welcome, GLHF!
|
27
|
+
# > :tmi.twitch.tv 002 bot :Your host is tmi.twitch.tv
|
28
|
+
# > :tmi.twitch.tv 003 bot :This server is rather new
|
29
|
+
# > :tmi.twitch.tv 004 bot :-
|
30
|
+
# > :tmi.twitch.tv 375 bot :-
|
31
|
+
# > :tmi.twitch.tv 372 bot :You are in a maze of twisty passages, all alike.
|
32
|
+
# > :tmi.twitch.tv 376 bot :>
|
33
|
+
def request_caps(handler)
|
34
|
+
handler.send_raw "CAP REQ :#{CAPABILITIES.join ' '}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../plugin'
|
2
|
+
require_relative '../user'
|
3
|
+
|
4
|
+
module Twitchbot
|
5
|
+
# Plugin to handle joining the specified channel, as well as maintaining a
|
6
|
+
# list of users who have joined or left the channel
|
7
|
+
#
|
8
|
+
# TODO: Handle 353 and 366 (NAMES list)
|
9
|
+
class ChannelPlugin
|
10
|
+
include Twitchbot::Plugin
|
11
|
+
|
12
|
+
register command: 'CAP', method: :join_channel
|
13
|
+
# Listen for the response from AuthPlugin#request_caps and request to join
|
14
|
+
# the channel
|
15
|
+
#
|
16
|
+
# > :tmi.twitch.tv CAP * ACK :twitch.tv/tags twitch.tv/commands twitch.tv/membership
|
17
|
+
def join_channel(handler)
|
18
|
+
handler.send_raw "JOIN ##{handler.bot.channel.name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
register command: 'JOIN', method: :process_join
|
22
|
+
# Listen for any JOIN commands and add the user to the channel user list
|
23
|
+
#
|
24
|
+
# > :<user>!<user>@<user>.tmi.twitch.tv JOIN #<channel>
|
25
|
+
def process_join(handler)
|
26
|
+
channel = handler.bot.channel
|
27
|
+
handler.messages.each do |message|
|
28
|
+
/:(?<sender>\w+)/ =~ message.raw
|
29
|
+
unless channel.users.key? sender
|
30
|
+
channel.users[sender] = User.new sender
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
register command: 'PART', method: :process_part
|
36
|
+
# Listen for any PART commands and remove the user from the channel user
|
37
|
+
# list
|
38
|
+
#
|
39
|
+
# > :<user>!<user>@<user>.tmi.twitch.tv PART #<channel>
|
40
|
+
def process_part(handler)
|
41
|
+
channel = handler.bot.channel
|
42
|
+
handler.messages.each do |message|
|
43
|
+
/:(?<sender>\w+)/ =~ message.raw
|
44
|
+
if channel.users.key? sender
|
45
|
+
channel.users.delete sender
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../plugin'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Plugin to handle displaying debug information such as raw messages received
|
5
|
+
# as well as connection events e.g. open, close, error
|
6
|
+
class DebugPlugin
|
7
|
+
include Twitchbot::Plugin
|
8
|
+
|
9
|
+
# Display to the user that the connection has been opened
|
10
|
+
def open(handler)
|
11
|
+
puts '! Connection established' if handler.bot.debug
|
12
|
+
end
|
13
|
+
|
14
|
+
# Display to the user any raw messages that have been received from the
|
15
|
+
# server
|
16
|
+
def message(handler)
|
17
|
+
if handler.bot.debug
|
18
|
+
handler.messages.each do |line|
|
19
|
+
puts "> #{line.raw}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Display to the user that the connection has encountered an error
|
25
|
+
def error(handler)
|
26
|
+
puts '! Error occurred' if handler.bot.debug
|
27
|
+
end
|
28
|
+
|
29
|
+
# Display to the user that the connection has been closed
|
30
|
+
def close(handler)
|
31
|
+
puts '! Connection closed' if handler.bot.debug
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../timed_plugin'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Plugin to handle sending messages to the server that have been queued from
|
5
|
+
# any plugins running.
|
6
|
+
#
|
7
|
+
# TODO: Implement different levels of rate limiting according to bot status e.g. :mod, :verified, :trusted
|
8
|
+
class MessageQueuePlugin
|
9
|
+
include Twitchbot::TimedPlugin
|
10
|
+
|
11
|
+
# The most privileged bot can only send 7200 messages every 30 seconds
|
12
|
+
register method: :send_message, interval: (30 / 7200)
|
13
|
+
# Pull a message from the message queue if any are available and send to the
|
14
|
+
# server
|
15
|
+
def send_message(handler)
|
16
|
+
queue = handler.bot.message_queue
|
17
|
+
unless queue.empty?
|
18
|
+
message = queue.pop
|
19
|
+
puts "< #{message}" if handler.bot.debug
|
20
|
+
handler.connection.send message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../plugin'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Plugin to handle any PING messages from the server and keep the connection
|
5
|
+
# alive
|
6
|
+
class PingPlugin
|
7
|
+
include Twitchbot::Plugin
|
8
|
+
|
9
|
+
# Listen for any PING messages and respond with PONG
|
10
|
+
#
|
11
|
+
# > PING :tmi.twitch.tv
|
12
|
+
def message(handler)
|
13
|
+
handler.send_raw('PONG :tmi.twitch.tv') if handler.message.ping?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Twitchbot
|
4
|
+
# Base Plugin module that registers methods that should be called periodically
|
5
|
+
module TimedPlugin
|
6
|
+
|
7
|
+
COMMANDS = {}
|
8
|
+
|
9
|
+
# Method that reacts to the eventmachine +:open+ event and creates a new
|
10
|
+
# eventmachine periodic timer for each plugin of this type
|
11
|
+
#
|
12
|
+
# It is not recommended to override this method unless you plan on handling
|
13
|
+
# all logic for managing timed plugins yourself
|
14
|
+
def open(handler)
|
15
|
+
COMMANDS[self.class].each do |command, interval|
|
16
|
+
EM::PeriodicTimer.new(interval) do
|
17
|
+
send(command, handler)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Method that can be overriden to react to the eventmachine +:message+ event
|
23
|
+
def message(event) end
|
24
|
+
|
25
|
+
# Method that can be overriden to react to the eventmachine +:error+ event
|
26
|
+
def error(event) end
|
27
|
+
|
28
|
+
# Method that can be overriden to react to the eventmachine +:close+ event
|
29
|
+
def close(event) end
|
30
|
+
|
31
|
+
# Define a class method called register on each class that includes this
|
32
|
+
# module, which allows the user to add methods to the +COMMANDS+ constant
|
33
|
+
#
|
34
|
+
# def self.register(command:, method:)
|
35
|
+
# COMMANDS[base] = {} if COMMANDS[base].nil?
|
36
|
+
# COMMANDS[base][params[:command]] = params[:method]
|
37
|
+
# end
|
38
|
+
def self.included(klass)
|
39
|
+
klass.instance_eval do
|
40
|
+
define_singleton_method 'register' do |params|
|
41
|
+
COMMANDS[klass] = {} if COMMANDS[klass].nil?
|
42
|
+
COMMANDS[klass][params[:method]] = params[:interval]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Twitchbot
|
2
|
+
# Class responsible for keeping track of a user attributes such as their
|
3
|
+
# display name, id, any badges they have, and implementing helper functions
|
4
|
+
# to determine if different qualities of the user
|
5
|
+
class User
|
6
|
+
# @return [String] Server ID for the user
|
7
|
+
attr_reader :id
|
8
|
+
# @return [Hash] Collection of known badges for the user
|
9
|
+
attr_reader :badges
|
10
|
+
|
11
|
+
def initialize(name, display_name = nil, id = nil, badges = nil)
|
12
|
+
@name = name
|
13
|
+
@display_name = display_name
|
14
|
+
@id = id
|
15
|
+
@badges = badges.nil? ? nil : process_badges(badges)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Method to update the main attributes of a user
|
19
|
+
def update_attributes(display_name, id, badges)
|
20
|
+
@display_name = display_name
|
21
|
+
@user_id = id
|
22
|
+
@badges = process_badges(badges)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Method to grab the best representation of a user
|
26
|
+
def name
|
27
|
+
@display_name || @name
|
28
|
+
end
|
29
|
+
|
30
|
+
# Method to determine if the user is a moderator of the channel
|
31
|
+
def mod?
|
32
|
+
@badges.key? 'moderator'
|
33
|
+
end
|
34
|
+
|
35
|
+
# Method to determine if the user is a subscriber to the channel
|
36
|
+
def sub?
|
37
|
+
@badges.key? 'subscriber'
|
38
|
+
end
|
39
|
+
|
40
|
+
# Method to determine if the user has Twitch Prime
|
41
|
+
def prime?
|
42
|
+
@badges.key? 'premium'
|
43
|
+
end
|
44
|
+
|
45
|
+
# Method to determine if the user has ever donated to the channel
|
46
|
+
def donator?
|
47
|
+
@badges.key? 'bits'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Method to determine if the user is a channel founder
|
51
|
+
def founder?
|
52
|
+
@badges.key? 'founder'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Method to determine if the user is on the leaderboard for subscriber gifts
|
56
|
+
def sub_gift_leader?
|
57
|
+
@badges.key? 'sub-gift-leader'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Method to determine if the user is on the leaderboard for bit gifts
|
61
|
+
def bits_leader?
|
62
|
+
@badges.key? 'bits-leader'
|
63
|
+
end
|
64
|
+
|
65
|
+
# Method to process the string representation of badges into a Hash so that
|
66
|
+
# we can query it for specific badges and levels of the badges
|
67
|
+
def process_badges(badges)
|
68
|
+
badge = {}
|
69
|
+
badges.split(',').each do |_badge|
|
70
|
+
type, value = _badge.split '/'
|
71
|
+
badge[type] = value.to_i
|
72
|
+
end
|
73
|
+
badge
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
name
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twitchbot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Charles Ray Shisler III
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: faye-websocket
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.10.7
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.10.7
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- charles@cray.io
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- bin/console
|
93
|
+
- bin/setup
|
94
|
+
- lib/twitchbot.rb
|
95
|
+
- lib/twitchbot/bot.rb
|
96
|
+
- lib/twitchbot/channel.rb
|
97
|
+
- lib/twitchbot/event_handler.rb
|
98
|
+
- lib/twitchbot/message.rb
|
99
|
+
- lib/twitchbot/message_plugin.rb
|
100
|
+
- lib/twitchbot/plugin.rb
|
101
|
+
- lib/twitchbot/plugin/auth_plugin.rb
|
102
|
+
- lib/twitchbot/plugin/channel_plugin.rb
|
103
|
+
- lib/twitchbot/plugin/debug_plugin.rb
|
104
|
+
- lib/twitchbot/plugin/message_queue_plugin.rb
|
105
|
+
- lib/twitchbot/plugin/ping_plugin.rb
|
106
|
+
- lib/twitchbot/timed_plugin.rb
|
107
|
+
- lib/twitchbot/user.rb
|
108
|
+
- lib/twitchbot/version.rb
|
109
|
+
homepage: https://github.com/craysiii/twitchbot
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubygems_version: 3.0.3
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: A framework for creating Twitch.tv chat bots
|
132
|
+
test_files: []
|