twitchbot 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|