diff --git a/.env.example b/.env.example index c09d40b..fba7942 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,11 @@ S3_KEEP=your_info_here LIVE_MODE=true # Digital Ocean Droplet Admin -# DO_API_KEY=xxxxxxxxx +DO_API_KEY=your_info_here + +# Discord Authentication +DISCORD_TOKEN=your_info_here +DISCORD_CLIENT_ID=your_info_here ############################################################ # SOCKET_KEY_ID diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..851ff35 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: ruby +rvm: +- 2.1 diff --git a/Gemfile b/Gemfile index 2b0318d..7d12ffc 100644 --- a/Gemfile +++ b/Gemfile @@ -14,12 +14,16 @@ gem 'fog' gem 'whenever', require: false gem 'net-ssh', ['>= 2.3.0', '<= 2.5.2'] gem 'excon', '~> 0.17.0' -gem 'mail', '~> 2.5.0' +gem 'mail' + +# Discord +gem 'discordrb' +gem 'rest-client', '~> 2.0.2' # Web -gem 'sinatra' -gem 'sinatra-reloader' +gem 'sinatra', '< 2.0.0' gem 'sinatra-websocket' +gem 'sinatra-reloader' gem 'thin' gem 'haml' gem 'sass' @@ -34,9 +38,10 @@ gem 'cinchize' gem 'chronic' gem 'chronic_duration' gem 'tweetstream' -gem 'google-api-client', '<0.9' +gem 'google-api-client' gem 'htmlentities' gem 'cinch-identify' +gem 'cinch-cooldown', '>= 1.2.0' gem 'stopwords', '0.2' gem 'droplet_kit' gem 'nokogiri', '>= 1.6.8' @@ -61,10 +66,11 @@ group :development do gem 'dm-sqlite-adapter' gem 'foreman' gem 'rb-fsevent' +# gem 'irbtools-more', '~> 2.0' + gem 'did_you_mean', '~> 0.9' + gem 'faraday', '0.9.2' end group :test do gem 'rack-test', require: false end - - diff --git a/Gemfile.lock b/Gemfile.lock index 62fc694..2359280 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,16 +4,13 @@ GEM activesupport (3.2.16) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) - addressable (2.3.8) - autoparse (0.3.3) - addressable (>= 2.3.1) - extlib (>= 0.9.15) - multi_json (>= 1.0.0) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - backports (3.6.8) + backports (3.8.0) backup (3.4.0) open4 (~> 1.3.0) thor (>= 0.15.4, < 2) @@ -21,11 +18,14 @@ GEM bcrypt-ruby (3.1.5) bcrypt (>= 3.1.3) buftok (0.2.0) - builder (3.2.2) + builder (3.2.3) chronic (0.10.2) chronic_duration (0.10.6) numerizer (~> 0.1.1) - cinch (2.3.2) + cinch (2.3.3) + cinch-cooldown (1.2.0) + cinch (~> 2) + time-lord (~> 1.0, >= 1.0.1) cinch-identify (1.7.0) cinch (~> 2.0) cinchize (0.4.2) @@ -36,7 +36,7 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) + coffee-script-source (1.12.2) cookiejar (0.3.3) daemons (1.2.4) data_mapper (1.2.0) @@ -51,9 +51,21 @@ GEM dm-validations (~> 1.2.0) data_objects (0.10.17) addressable (~> 2.1) + declarative (0.0.9) + declarative-option (0.1.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - diff-lcs (1.2.5) + did_you_mean (0.10.0) + interception + diff-lcs (1.3) + discordrb (3.2.1) + discordrb-webhooks (~> 3.2.0.1) + opus-ruby + rbnacl (~> 3.4.0) + rest-client + websocket-client-simple (>= 0.3.0) + discordrb-webhooks (3.2.0.1) + rest-client dm-aggregates (1.2.0) dm-core (~> 1.2.0) dm-constraints (1.2.0) @@ -97,12 +109,12 @@ GEM data_objects (= 0.10.17) do_sqlite3 (0.10.17) data_objects (= 0.10.17) - domain_name (0.5.20160826) + domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) - dotenv (2.1.1) - droplet_kit (1.4.3) - activesupport (> 3.0, < 5.1) - faraday (~> 0.9.1) + dotenv (2.2.1) + droplet_kit (2.2.1) + activesupport (> 3.0, < 6) + faraday (~> 0.9) kartograph (~> 0.2.3) resource_kit (~> 0.1.5) virtus (~> 1.0.3) @@ -125,13 +137,14 @@ GEM addressable (>= 2.1.1) eventmachine (>= 0.12.9) equalizer (0.0.10) - eventmachine (1.2.0.1) + event_emitter (0.2.6) + eventmachine (1.2.5) excon (0.17.0) execjs (2.7.0) - extlib (0.9.16) faraday (0.9.2) multipart-post (>= 1.2, < 3) fastercsv (1.5.5) + ffi (1.9.18) fog (0.7.2) builder excon (>= 0.6.1) @@ -141,20 +154,16 @@ GEM net-ssh (>= 2.1.3) nokogiri (>= 1.4.4) ruby-hmac - foreman (0.82.0) + foreman (0.84.0) thor (~> 0.19.1) formatador (0.2.5) - google-api-client (0.8.7) - activesupport (>= 3.2, < 5.0) - addressable (~> 2.3) - autoparse (~> 0.3) - extlib (~> 0.9) - faraday (~> 0.9) - googleauth (~> 0.3) - launchy (~> 2.4) - multi_json (~> 1.10) - retriable (~> 1.4) - signet (~> 0.6) + google-api-client (0.13.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.5) + httpclient (>= 2.8.1, < 3.0) + mime-types (~> 3.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) googleauth (0.5.1) faraday (~> 0.9) jwt (~> 1.4) @@ -163,7 +172,8 @@ GEM multi_json (~> 1.11) os (~> 0.9) signet (~> 0.7) - haml (4.0.7) + haml (5.0.2) + temple (>= 0.8.0) tilt htmlentities (4.3.4) http (1.0.4) @@ -173,73 +183,92 @@ GEM http_parser.rb (~> 0.6.0) http-cookie (1.0.3) domain_name (~> 0.5) - http-form_data (1.0.1) + http-form_data (1.0.3) http_parser.rb (0.6.0) - httpclient (2.8.2.4) - i18n (0.7.0) + httpclient (2.8.3) + i18n (0.8.6) ice_nine (0.11.2) - json (1.8.3) - json_pure (1.8.3) + interception (0.5) + json (1.8.6) + json_pure (1.8.6) jwt (1.5.6) kartograph (0.2.4) - launchy (2.4.3) - addressable (~> 2.3) - libv8 (3.16.14.15) + libv8 (3.16.14.19) little-plugger (1.1.4) - logging (2.1.0) + logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - memoist (0.15.0) + mail (2.6.6) + mime-types (>= 1.16, < 4) + memoist (0.16.0) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) - mime-types (1.25.1) - mini_portile2 (2.1.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.2.0) multi_json (1.12.1) multipart-post (2.0.0) naught (1.1.0) net-ssh (2.5.2) - nokogiri (1.6.8.1) - mini_portile2 (~> 2.1.0) + netrc (0.11.0) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) numerizer (0.1.1) open4 (1.3.4) + opus-ruby (1.0.1) + ffi os (0.9.6) - polyglot (0.3.5) - rack (1.6.4) + public_suffix (3.0.0) + rack (1.6.8) rack-protection (1.5.3) rack - rack-test (0.6.3) - rack (>= 1.0) - rake (11.3.0) - rb-fsevent (0.9.7) + rack-test (0.7.0) + rack (>= 1.0, < 3) + rake (12.0.0) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rbnacl (3.4.0) + ffi ref (2.0.0) - resource_kit (0.1.5) - addressable (~> 2.3.6) - retriable (1.4.1) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.4) - rspec-support (~> 3.5.0) - rspec-expectations (3.5.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + resource_kit (0.1.6) + addressable (>= 2.3.6, < 3.0.0) + rest-client (2.0.2) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + retriable (3.1.1) + rspec (3.6.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-mocks (3.5.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-support (3.5.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) ruby-hmac (0.4.0) - sass (3.4.22) + sass (3.5.1) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) signet (0.7.3) addressable (~> 2.3) faraday (~> 0.9) jwt (~> 1.5) multi_json (~> 1.10) simple_oauth (0.3.1) - sinatra (1.4.7) + sinatra (1.4.8) rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) @@ -258,26 +287,25 @@ GEM thin (>= 1.3.1, < 2.0.0) stopwords (0.2) stringex (1.5.1) - therubyracer (0.12.2) - libv8 (~> 3.16.14.0) + temple (0.8.0) + therubyracer (0.12.3) + libv8 (~> 3.16.14.15) ref - thin (1.7.0) + thin (1.7.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (0.19.1) - thread_safe (0.3.5) - tilt (2.0.5) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.8) + time-lord (1.0.1) tweetstream (2.6.1) daemons (~> 1.1) em-http-request (>= 1.1.1) em-twitter (~> 0.3) multi_json (~> 1.3) twitter (~> 5.5) - twitter (5.16.0) + twitter (5.17.0) addressable (~> 2.3) buftok (~> 0.2.0) equalizer (= 0.0.10) @@ -288,19 +316,24 @@ GEM memoizable (~> 0.4.0) naught (~> 1.0) simple_oauth (~> 0.3.0) - tzinfo (1.2.2) + tzinfo (1.2.3) thread_safe (~> 0.1) - tzinfo-data (1.2016.7) + tzinfo-data (1.2017.2) tzinfo (>= 1.0.0) + uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.2) + unf_ext (0.0.7.4) uuidtools (2.1.5) virtus (1.0.5) axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) + websocket (1.2.4) + websocket-client-simple (0.3.0) + event_emitter + websocket whenever (0.9.7) chronic (>= 0.6.3) @@ -313,10 +346,13 @@ DEPENDENCIES chronic chronic_duration cinch + cinch-cooldown (>= 1.2.0) cinch-identify cinchize coffee-script data_mapper + did_you_mean (~> 0.9) + discordrb dm-aggregates dm-core dm-is-counter_cacheable @@ -331,21 +367,23 @@ DEPENDENCIES eat excon (~> 0.17.0) execjs + faraday (= 0.9.2) fog foreman - google-api-client (< 0.9) + google-api-client haml htmlentities i18n - mail (~> 2.5.0) + mail net-ssh (>= 2.3.0, <= 2.5.2) nokogiri (>= 1.6.8) rack-test rake rb-fsevent + rest-client (~> 2.0.2) rspec sass - sinatra + sinatra (< 2.0.0) sinatra-reloader sinatra-websocket stopwords (= 0.2) @@ -357,4 +395,4 @@ DEPENDENCIES whenever BUNDLED WITH - 1.12.5 + 1.13.6 diff --git a/Procfile b/Procfile index 54f2843..7fe6dec 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ web: bundle exec thin start -p $PORT -e production irc: ruby showbot_irc.rb network_live +discord: ruby showbot_discord.rb \ No newline at end of file diff --git a/Procfile.local b/Procfile.local index b92e238..631e1ba 100644 --- a/Procfile.local +++ b/Procfile.local @@ -1,2 +1,3 @@ web: bundle exec thin start -p $DEVELOPMENT_PORT -e development irc: ruby showbot_irc.rb network_test +discord: ruby showbot_discord.rb \ No newline at end of file diff --git a/README.md b/README.md index 8b27cd5..05c41a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Stories in Ready](https://badge.waffle.io/rikai/Showbot.png?label=ready&title=Ready)](https://waffle.io/rikai/Showbot) +[![Build Status](https://travis-ci.org/rikai/Showbot.svg?branch=master)](https://travis-ci.org/rikai/Showbot) # JBot A sweet IRC bot with a **web interface** for [Jupiter Broadcasting](http://www.jupiterbroadcasting.com/). @@ -20,7 +21,7 @@ follwing message once it is connected to an IRC network: ### Prerequisites - * [RVM with Ruby 1.9.2 or Greater](https://rvm.io/) + * [RVM with Ruby 2.1.2 or greater](https://rvm.io/) * [Bundler](http://gembundler.com/) * Git (for pulling down source from Github, alternately download a tarball) * SQLite3 (for development) diff --git a/Rakefile b/Rakefile index 4f6da6b..975563a 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require 'bundler/setup' namespace :test do Rake::TestTask.new do |t| t.libs << "test" - t.test_files = FileList['test/*test.rb'] + t.test_files = FileList['test/**/*test.rb'] t.verbose = true end end @@ -104,3 +104,5 @@ namespace :api_key do end end end + +task :default => ["spec:unit"] diff --git a/cinchize.yml.example b/cinchize.yml.example index 8928135..915d288 100644 --- a/cinchize.yml.example +++ b/cinchize.yml.example @@ -94,15 +94,27 @@ servers: network_test: <<: *network_base nick: botname_test + messages_per_second: 1 + server_queue_size: 5 channels: - "#cinch-bots" shared: Bot_Nick: "botname" Backup_Bot_Nick: "botname-backup" + Live_Url: "localhost:5000/live" + Sinatra_Url: "http://localhost:5000" + # This controls Cinch::Cooldown. + :cooldown: + :config: + :linux: + :global: 2 + :user: 4 <<: *test_hosts network_live: <<: *network_base nick: botname + messages_per_second: 1 + server_queue_size: 5 channels: - "#cinch-bots" shared: diff --git a/lib/cinch/plugins/about.rb b/lib/cinch/plugins/about.rb index 09ada6a..5662741 100644 --- a/lib/cinch/plugins/about.rb +++ b/lib/cinch/plugins/about.rb @@ -1,8 +1,12 @@ +require 'cinch/cooldown' + module Cinch module Plugins class About include Cinch::Plugin + enforce_cooldown + match /about/i, :method => :command_about # !about def help diff --git a/lib/cinch/plugins/admin.rb b/lib/cinch/plugins/admin.rb index 6fc0cf0..c95aad9 100644 --- a/lib/cinch/plugins/admin.rb +++ b/lib/cinch/plugins/admin.rb @@ -39,6 +39,7 @@ class Admin def initialize(*args) super *args @data_json = DataJSON.new config[:data_json] + @shows = Shows.new(JSON.parse(File.open(SHOWS_JSON).read)["shows"]) end def command_join(m, channel) @@ -50,14 +51,14 @@ def command_leave(m, channel) end def command_show_list(m) - shows = Shows.shows + shows = @shows.shows shows.each do |show| m.user.send "#{show.title}: !start_show #{show.url}" end end def command_start_show(m, show_slug) - show = Shows.find_show show_slug + show = @shows.find_show show_slug if show.nil? m.user.send 'Sorry, but I couldn\'t find that particular show.' return diff --git a/lib/cinch/plugins/bacon.rb b/lib/cinch/plugins/bacon.rb index 8f97fd4..0d495af 100644 --- a/lib/cinch/plugins/bacon.rb +++ b/lib/cinch/plugins/bacon.rb @@ -1,8 +1,12 @@ +require 'cinch/cooldown' + module Cinch module Plugins class Bacon include Cinch::Plugin + enforce_cooldown + match /bacon$/i, :method => :command_bacon # !bacon match /bacon\s+(\S+)/i, :method => :command_bacon_gift # !bacon diff --git a/lib/cinch/plugins/bittick.rb b/lib/cinch/plugins/bittick.rb index 1c051e8..f1d78aa 100644 --- a/lib/cinch/plugins/bittick.rb +++ b/lib/cinch/plugins/bittick.rb @@ -1,12 +1,15 @@ require 'open-uri' require 'json' require 'openssl' +require 'cinch/cooldown' module Cinch module Plugins class Bittick include Cinch::Plugin + enforce_cooldown + match /bittick/i, :method => :command_btcetick match /btcetick/i, :method => :command_btcetick match /bitavg/i, :method => :command_bitavg diff --git a/lib/cinch/plugins/devtools.rb b/lib/cinch/plugins/devtools.rb index 6aae68f..e4bb9c0 100644 --- a/lib/cinch/plugins/devtools.rb +++ b/lib/cinch/plugins/devtools.rb @@ -1,3 +1,4 @@ +require 'cinch/cooldown' require 'objspace' module Cinch @@ -5,6 +6,8 @@ module Plugins class DevTools include Cinch::Plugin + enforce_cooldown + match /plugins\s*$/i, :method => :command_list_plugins match /resources\s*$/i, :method => :command_all_resources match /resources\s+(.*)\s*$/i, :method => :command_resources diff --git a/lib/cinch/plugins/help.rb b/lib/cinch/plugins/help.rb index 7224ec9..b7cde37 100644 --- a/lib/cinch/plugins/help.rb +++ b/lib/cinch/plugins/help.rb @@ -1,8 +1,12 @@ +require 'cinch/cooldown' + module Cinch module Plugins class Help include Cinch::Plugin + enforce_cooldown + match /help$/i, :method => :command_help # !help match /help\s+(.+)/i, :method => :command_help_command # !help diff --git a/lib/cinch/plugins/links.rb b/lib/cinch/plugins/links.rb index cea51fe..7efd9f0 100644 --- a/lib/cinch/plugins/links.rb +++ b/lib/cinch/plugins/links.rb @@ -4,6 +4,7 @@ # # Gotta link 'em all +require 'cinch/cooldown' require 'addressable/uri' module Cinch @@ -11,6 +12,8 @@ module Plugins class Links include Cinch::Plugin + enforce_cooldown + match /link\s+(.*)/i, :method => :command_link # !link http://example.com/greatest-link-ever def help diff --git a/lib/cinch/plugins/linktitle.rb b/lib/cinch/plugins/linktitle.rb index f1a2fbc..3cd8e0e 100644 --- a/lib/cinch/plugins/linktitle.rb +++ b/lib/cinch/plugins/linktitle.rb @@ -1,12 +1,15 @@ require 'nokogiri' require 'eat' require 'openssl' +require 'cinch/cooldown' module Cinch module Plugins class LinkTitle include Cinch::Plugin + enforce_cooldown + match /(https?\:\/\/.*?)(\s|$)/, use_prefix: false # Default list of URL regexps to ignore. diff --git a/lib/cinch/plugins/quotes.rb b/lib/cinch/plugins/quotes.rb index 9aec17d..710db2a 100644 --- a/lib/cinch/plugins/quotes.rb +++ b/lib/cinch/plugins/quotes.rb @@ -1,8 +1,12 @@ +require 'cinch/cooldown' + module Cinch module Plugins class Quotes include Auth::AdminPlugin + enforce_cooldown + match /quote\s+(.+)/i, :method => :command_quote match /([^\s]+)/i, :method => :command_quote [{ @@ -37,7 +41,20 @@ class Quotes def initialize(*args) super - @quote_list = QuoteList.new(config) + if !config[:quotes_file].nil? + puts File.dirname(__FILE__) + @can_save = true + begin + @quotes_path = File.join(File.dirname(__FILE__), "../../#{config[:quotes_file]}") + quotes = YAML.load_file(@quotes_path) + @quote_list = QuoteList.new(quotes) + rescue Errno::ENOENT + @quote_list = QuoteList.new(nil) + end + else + @can_save = false + @quote_list = QuoteList.new(nil) + end end def command_quote(m, name) @@ -65,6 +82,14 @@ def command_alias_del(m, original, alias_name) @quote_list.del_alias(original, alias_name) m.reply("Alias removed!") end + + private + + def save_to_disk + if @can_save + File.open(@quotes_path, "w") {|file| file.write(@quote_list.get_yaml)} + end + end end end end diff --git a/lib/cinch/plugins/schedule.rb b/lib/cinch/plugins/schedule.rb index b41f199..9b61862 100644 --- a/lib/cinch/plugins/schedule.rb +++ b/lib/cinch/plugins/schedule.rb @@ -1,3 +1,4 @@ +require 'cinch/cooldown' require 'optparse' require 'tzinfo' @@ -9,6 +10,8 @@ module Plugins class Schedule include Cinch::Plugin + enforce_cooldown + timer 600, :method => :refresh_calendar listen_to :connect, :method => :on_connect @@ -27,7 +30,7 @@ def help def help_next [ '!next - When\'s the next live show?', - 'Usage: !next [show]' + 'Usage: !next [show] [-t ]' ].join "\n" end diff --git a/lib/cinch/plugins/servers.rb b/lib/cinch/plugins/servers.rb index 287b852..9299c5f 100644 --- a/lib/cinch/plugins/servers.rb +++ b/lib/cinch/plugins/servers.rb @@ -1,8 +1,12 @@ +require 'cinch/cooldown' + module Cinch module Plugins class Servers include Cinch::Plugin + enforce_cooldown + match /server$/i, :method => :command_server # !server match /server\s+(.+)/i, :method => :command_server_service # !server match /irc/i, :method => :command_irc # !irc diff --git a/lib/cinch/plugins/shoutcast.rb b/lib/cinch/plugins/shoutcast.rb index 8a5012a..f674f2a 100644 --- a/lib/cinch/plugins/shoutcast.rb +++ b/lib/cinch/plugins/shoutcast.rb @@ -1,6 +1,7 @@ # Get the title of a Shoutcast stream +require 'cinch/cooldown' require 'uri' require 'net/http' @@ -9,6 +10,8 @@ module Plugins class Shoutcast include Cinch::Plugin + enforce_cooldown + HEADERS = { "Icy-MetaData" => '1' } diff --git a/lib/cinch/plugins/stickynick.rb b/lib/cinch/plugins/stickynick.rb index 1041afc..d97f700 100644 --- a/lib/cinch/plugins/stickynick.rb +++ b/lib/cinch/plugins/stickynick.rb @@ -1,8 +1,12 @@ +require 'cinch/cooldown' + module Cinch module Plugins class StickyNick include Cinch::Plugin + enforce_cooldown + timer 300, :method => :fix_nick # Called every 5 minutes to attempt to fix the bots name. diff --git a/lib/cinch/plugins/twitter.rb b/lib/cinch/plugins/twitter.rb index 5612020..90964ec 100644 --- a/lib/cinch/plugins/twitter.rb +++ b/lib/cinch/plugins/twitter.rb @@ -1,9 +1,13 @@ # A Cinch plugin for broadcasting Twitter updates to an IRC channel +require 'cinch/cooldown' + module Cinch module Plugins class Twitter include Cinch::Plugin + enforce_cooldown + listen_to :connect, :method => :on_connect timer 60, :method => :poll_for_statuses diff --git a/lib/cinch/plugins/uptime.rb b/lib/cinch/plugins/uptime.rb index 3b05a8e..08ea2b2 100644 --- a/lib/cinch/plugins/uptime.rb +++ b/lib/cinch/plugins/uptime.rb @@ -1,10 +1,13 @@ require 'chronic_duration' +require 'cinch/cooldown' module Cinch module Plugins class Uptime include Cinch::Plugin + enforce_cooldown + match /uptime/i, :method => :command_uptime # !uptime def help diff --git a/lib/models/calendar.rb b/lib/models/calendar.rb index 66d70ad..8ed566d 100644 --- a/lib/models/calendar.rb +++ b/lib/models/calendar.rb @@ -1,4 +1,4 @@ -require 'google/api_client' +require 'google/apis/calendar_v3' require 'chronic_duration' require 'tzinfo' @@ -42,44 +42,53 @@ module Calendar # and is embedded in your calendar's URLs. The api_key must be obtained from # Google through their API Console at https://code.google.com/apis/console # - # TODO: Right now, this errors out on missing/invalid config. It should - # instead return a NullCalendar object that returns no events. def self.new(config = {}) - google_config = { - :app_name => config[:app_name], - :app_version => config[:app_version], - :calendar_id => config[:calendar_id], - :api_key => config[:api_key] - } - - google_client = Google::APIClient.new( - :application_name => google_config[:app_name], - :application_version => google_config[:app_version], - :key => google_config[:api_key], - :authorization => nil - ) - - google_api = google_client.discovered_api('calendar', 'v3') - - GoogleCalendar.new(google_config, google_client, google_api) + # Return a NullCalendar if the config doesn't have all of the keys that are + # necessary to create a GoogleCalendar + return NullCalendar.new unless (config.has_key? :api_key and + config.has_key? :calendar_id and + config.has_key? :app_name and + config.has_key? :app_version) + + # Configure the client options of this Google API Client. Specifically, set + # the app name and version as per the configuration hash. + client_options = Google::Apis::ClientOptions.default.dup + client_options.application_name = config[:app_name] + client_options.application_version = config[:app_version] + + # Configure the request options of this Google API Client. Specifically, + # this is where we'll set up OAuth, if we ever implement it. For now, this + # can stay commented out -- it just does the default as it is. + request_options = Google::Apis::RequestOptions.default.dup + # request_options.authorization = nil + + # Create a CalendarService API client to read a Google Calendar. + cal_service = Google::Apis::CalendarV3::CalendarService.new + # Configure the API key + cal_service.key = config[:api_key] + # And the client and request options from above + cal_service.client_options = client_options + cal_service.request_options = request_options + + GoogleCalendar.new(config[:calendar_id], cal_service) + end + + # A NullCalendar that returns no events, for use when an invalid configuration + # is given to this module's constructor. + class NullCalendar + def events + return [] + end end # The Calendar::GoogleCalendar class provides the default backend for Calendar class GoogleCalendar - # param config: (hash) A config hash with the keys: - # - # app_name: The name of your application - # app_version: The version of your application - # calendar_id: The Google Calendar ID of the calendar you want to use - # api_key: Your API key for the Google Calendar API - # - # param client: (Google::APIClient) A Google API client object - # param api: (Google::APIClient::API) A Google API object, obtained from - # calling the `Google::APIClient#discovered_api` method. - def initialize(config, client, api) - @config = config - @client = client - @calendar = api + # param calendar_id: The Google Calendar ID of the calendar you want to use + # param cal_service: (Google::Apis::CalendarV3::CalendarService) A Google + # Calendar API (V3) service object + def initialize(calendar_id, cal_service) + @calendar_id = calendar_id + @cal_service = cal_service end # Get live events for the next 7 days @@ -88,18 +97,14 @@ def initialize(config, client, api) # Events are ordered by start time, ascending # The "LIVE: " prefix is stripped from the event summary def events - results = @client.execute( - :api_method => @calendar.events.list, - :authenticated => false, - :parameters => { - 'calendarId' => @config[:calendar_id], - 'fields' => 'items(start,end,summary)', - 'singleEvents' => true, - 'orderBy' => 'startTime', - 'timeMin' => DateTime.now.to_s, - 'timeMax' => (DateTime.now + 7).to_s, - 'q' => 'LIVE' - } + results = @cal_service.list_events( + @calendar_id, + order_by: 'startTime', + q: 'LIVE', + single_events: true, + time_max: (DateTime.now + 7).to_s, + time_min: DateTime.now.to_s, + fields: 'items(start,end,summary)', ) results.data.items.map do |event| diff --git a/lib/models/quotelist.rb b/lib/models/quotelist.rb index 18ce2b3..fbb250c 100644 --- a/lib/models/quotelist.rb +++ b/lib/models/quotelist.rb @@ -1,60 +1,92 @@ class QuoteList - def initialize(config) - unless config[:quotes_file].nil? - @quotes_path = File.join File.dirname(__FILE__), "../../#{config[:quotes_file]}" - @can_save = true - begin - @quotes = YAML.load_file @quotes_path - rescue Errno::ENOENT - save_to_disk true - end - else - warn "Quotes file was nil. QUOTE CHANGES WILL NOT BE SAVED!" - @can_save = false - end + attr_reader :quotes, :can_save + + def initialize(quotes) + quotes ||= Hash.new + @quotes = quotes end + # Returns a random quote by a person's name or alias; + # otherwise, returns an empty string. + # TODO(thefirstofthe300): this should probably be renamed for better semantics def quote_for(name) canonical_name = canonicalize name - return '' if !canonical_name + + if !canonical_name + return '' + end + @quotes[canonical_name][:quotes].sample end + # Adds a quote to a person's quote array; otherwise, returns and empty string. + # + # This function will create the specified person if they don't already exist. def add(name, quote) name.downcase! + @quotes[name] ||= {aliases:[], quotes:[]} - if @quotes[name][:quotes].select {|q| q.downcase == quote.downcase} == [] - @quotes[name][:quotes] << quote - save_to_disk - else - '' + + if @quotes[name][:quotes].select {|q| q.downcase == quote.downcase}.length > 0 + return "" end + + @quotes[name][:quotes] << quote end + # Deletes a quote from a person's quote array if they exist; otherwise, + # returns an empty string. + # + # Note: if the specified person has no more quotes after the deletion, the + # person object will be deleted, even if they still have aliases. def del(name, quote) - canonical_name = canonicalize name - return '' if !canonical_name + canonical_name = canonicalize(name) + + if !canonical_name + return "" + end + @quotes[canonical_name][:quotes].delete(quote) - @quotes.delete(canonical_name) if @quotes[canonical_name][:quotes].length == 0 - save_to_disk + + if @quotes[canonical_name][:quotes].length == 0 + @quotes.delete(canonical_name) + end end + # Adds an alias to the specified person; returns, an empty string if the + # person does not exist. def add_alias(name, a) - canonical_name = canonicalize name - return '' if !canonical_name + canonical_name = canonicalize(name) + + if !canonical_name + return '' + end + @quotes[canonical_name][:aliases] << a - save_to_disk end + # Deletes an alias for the specified person; returns, an empty string if the + # person does not exist. def del_alias(name, a) - canonical_name = canonicalize name - return '' if !canonical_name + canonical_name = canonicalize(name) + + if !canonical_name + return '' + end + @quotes[canonical_name][:aliases].delete(a) - save_to_disk + end + + # Gets the equivalent yaml for the quotes attribute + # Used by the quotes plugin to write the data to file + def get_yaml + @quotes.to_yaml end private + # Take the given name and return the name used to access information in the + # quotes hash def canonicalize(name) name.downcase! @@ -63,17 +95,16 @@ def canonicalize(name) end.keys.first end + # Helper function used by canonicalize def match_canonical?(canonical, name) - canonical.downcase == name + canonical == name end + # Helper function used by canonicalize to match case-insensitively against the + # given array of aliases def in_aliases?(aliases, name) Array(aliases).map(&:downcase).any? do |element| element.downcase == name end end - - def save_to_disk(skeleton = false) - File.open(@quotes_path, "w") {|file| file.write(skeleton ? "# See quotes.yml.example for what goes here." : @quotes.to_yaml)} if @can_save - end end diff --git a/lib/models/shows.rb b/lib/models/shows.rb index fa5ec67..4b25363 100644 --- a/lib/models/shows.rb +++ b/lib/models/shows.rb @@ -4,49 +4,61 @@ require './lib/models/show.rb' -SHOWS_JSON = "#{Dir.pwd}/public/shows.json" - class Shows - # The array of shows loaded from the SHOWS_JSON file - def self.shows - if not defined? @@shows_array - # Define the static @@shows_array variable if it doesn't exist - @@shows_array = [] - show_hashes = JSON.parse(File.open(SHOWS_JSON).read)["shows"] - show_hashes.each do |show_hash| - @@shows_array.push Show.new(show_hash) - end - end + attr_reader :shows - @@shows_array + def initialize(show_hashes) + @shows = [] + if show_hashes.length > 0 + self.load(show_hashes) + end + end + + # Adds the provided shows to the shows array. + def load(show_hashes) + show_hashes.each do |show_hash| + @shows.push Show.new(show_hash) + end end - # Find a show by keyword (slug or part of the title) - def self.find_show(keyword) - if keyword - self.shows.each do |show| - if show.matches? keyword - return show - end + def remove(show) + @shows.delete(find_show(show)) + end + + # Returns the first show that matches the given keyword; + # otherwise, returns nil. + def find_show(keyword) + if !keyword or keyword == '' + return nil + end + + @shows.each do |show| + if show.matches? keyword + return show end end - - return nil # No show found, return nil + + return nil end - # Find a show title by keyword, or returns the keyword if the show doesn't exist - def self.find_show_title(keyword) - show = self.find_show(keyword) + # Returns the title of the show that matches keyword; otherwise, returns nil. + # TODO(thefirstofthe300): Rename this. get_show_title is probably better. + # Also, it's probably more semantic to return nil + def find_show_title(keyword) + show = find_show(keyword) + if show return show.title - else - return keyword end + + return keyword end - # Get the live show slug + # Returns the live show slug. # TODO: Figure out how to deal with this properly if it is nil instead of erroring out. - def self.fetch_live_show_slug + # TODO(thefirstofthe300): Write tests for these two functions. Probably will + # require mocking? + def fetch_live_show_slug slug = nil begin @@ -64,8 +76,8 @@ def self.fetch_live_show_slug end # Returns the show object for the live show - def self.fetch_live_show - self.find_show(fetch_live_show_slug) + def fetch_live_show + find_show(fetch_live_show_slug) end end diff --git a/showbot_discord.rb b/showbot_discord.rb new file mode 100644 index 0000000..9f6bace --- /dev/null +++ b/showbot_discord.rb @@ -0,0 +1,32 @@ +require File.join(File.dirname(__FILE__), 'environment') + +require 'discordrb' +require 'rest-client' + +# This statement creates a bot with the specified token and application ID. After this line, you can add events to the +# created bot, and eventually run it. +# +# If you don't yet have a token and application ID to put in your .env, you will need to create a bot account here: +# https://discordapp.com/developers/applications/me +# If you're wondering about what redirect URIs and RPC origins, you can ignore those for now. If that doesn't satisfy +# you, look here: https://github.com/meew0/discordrb/wiki/Redirect-URIs-and-RPC-origins +# After creating the bot, simply copy the token (*not* the OAuth2 secret) and the client ID and place it into the +# your .env file under "DISCORD_TOKEN" and "DISCORD_CLIENT_ID". + +bot = Discordrb::Bot.new token: ENV['DISCORD_TOKEN'], client_id: ENV['DISCORD_CLIENT_ID'] + +# Here we output the invite URL to the console so the bot account can be invited to the channel. This only has to be +# done once, afterwards, you can remove this part if you want + +puts "This bot's invite URL is #{bot.invite_url}." +puts 'Click on it to invite it to your server.' + +# This is a simple example of adding the '!bacon' command from IRC to discord. + +bot.message(with_text: "!bacon") do |event| + event.respond "*Gives #{event.user.name} a strip of delicious bacon!*" +end + +# This method call has to be put at the end of your script, it is what makes the bot actually connect to Discord. If you +# leave it out (try it!) the script will simply stop and the bot will not appear online. +bot.run diff --git a/showbot_irc.rb b/showbot_irc.rb index e50d510..40cbd09 100644 --- a/showbot_irc.rb +++ b/showbot_irc.rb @@ -20,6 +20,8 @@ options = Options.dup +SHOWS_JSON = File.expand_path(File.join(File.dirname(__FILE__), "public", "shows.json")) unless defined? SHOWS_JSON + Auth::AdminPlugin.init(YAML.load_file(Options[:local_config])['auth_admin']['admins']) daemon = Cinchize::Cinchize.new *Cinchize.config(options, ARGV.first) diff --git a/showbot_web.rb b/showbot_web.rb index 624dbb2..f40f8b4 100644 --- a/showbot_web.rb +++ b/showbot_web.rb @@ -21,7 +21,7 @@ class ShowbotWeb < Sinatra::Base configure do set :public_folder, "#{File.dirname(__FILE__)}/public" set :views, "#{File.dirname(__FILE__)}/views" - set :shows, Shows.new { SHOWS_JSON } + set :shows, Shows.new(JSON.parse(File.open(SHOWS_JSON).read)["shows"]) set :live_mode_enabled, ENV['LIVE_MODE'] == "true" set :socket_key_id_enabled, ENV['SOCKET_KEY_ID'] == "true" end diff --git a/test/data/shows.json b/test/data/shows.json new file mode 100644 index 0000000..14d4f1f --- /dev/null +++ b/test/data/shows.json @@ -0,0 +1,13 @@ +{ + "shows": [ + { + "rss":"http://feeds.feedburner.com/linuxunvid", + "url": "lu", + "title": "Linux Unplugged", + "aliases": [ + "lup", + "lun" + ] + } + ] +} diff --git a/test/minitest/lib/models/quotelist_test.rb b/test/minitest/lib/models/quotelist_test.rb new file mode 100644 index 0000000..669eadd --- /dev/null +++ b/test/minitest/lib/models/quotelist_test.rb @@ -0,0 +1,86 @@ +require 'minitest/autorun' + +require File.join Dir.pwd, 'lib/models/quotelist.rb' + + +class TestQuotelist < Minitest::Test + def setup + @quotes = { + 'test' => { + :aliases => ['tester', 'testivus'], + :quotes => [ + 'A test tests a test of tests.', + 'Testing tests tests nothing but testing.' + ] + } + } + @quote_list = QuoteList.new(@quotes) + end + + def test_initialization? + quote_list = QuoteList.new(nil) + assert_equal Hash.new, quote_list.quotes + + quote_list = QuoteList.new(nil) + assert_equal Hash.new, quote_list.quotes + + quote_list = QuoteList.new(@quotes) + assert_equal ["test"], quote_list.quotes.keys + assert_equal [:aliases, :quotes], quote_list.quotes["test"].keys + assert_equal ['A test tests a test of tests.', 'Testing tests tests nothing but testing.'], + quote_list.quotes["test"][:quotes] + assert_equal ['tester', 'testivus'], quote_list.quotes["test"][:aliases] + end + + def test_quote_for? + quote = @quote_list.quote_for("Test") + assert quote == "A test tests a test of tests." || + quote == "Testing tests tests nothing but testing." + quote = @quote_list.quote_for("testivus") + assert quote == "A test tests a test of tests." || + quote == "Testing tests tests nothing but testing." + quote = @quote_list.quote_for("failure") + assert quote == '' + end + + def test_add? + @quote_list.add("Test", "Tested tests can be testy.") + @quote_list.add("Mr. Testy", "Testing tests tests my patience.") + assert_equal(["test", "mr. testy"], @quote_list.quotes.keys) + assert_equal(["Testing tests tests my patience."], @quote_list.quotes["mr. testy"][:quotes]) + assert_equal(["A test tests a test of tests.", "Testing tests tests nothing but testing.", "Tested tests can be testy.", ], @quote_list.quotes["test"][:quotes]) + end + + def test_del? + @quote_list.del("Test", "A test tests a test of tests.") + assert_equal(["Testing tests tests nothing but testing."], @quote_list.quotes["test"][:quotes]) + @quote_list.del("Test", "Testing tests tests nothing but testing.") + assert_equal(Hash.new, @quote_list.quotes) + end + + def test_add_alias? + @quote_list.add_alias("Test", "McTest") + assert_equal(["tester", "testivus", "McTest"], @quote_list.quotes["test"][:aliases]) + end + + def test_del_alias? + @quote_list.del_alias("test", "testivus") + assert_equal(["tester"], @quote_list.quotes["test"][:aliases]) + end + + def test_canonicalize? + assert_equal("test", @quote_list.send(:canonicalize, "Test")) + assert_equal("test", @quote_list.send(:canonicalize, "testivus")) + end + + def test_match_canonical? + assert @quote_list.send(:match_canonical?, "Test", "Test") + refute @quote_list.send(:match_canonical?, "test", "testy") + end + + def test_match_aliases? + assert @quote_list.send(:in_aliases?, @quote_list.quotes["test"][:aliases], "tester") + assert @quote_list.send(:in_aliases?, @quote_list.quotes["test"][:aliases], "tester") + refute @quote_list.send(:in_aliases?, @quote_list.quotes["test"][:aliases], "hello") + end +end \ No newline at end of file diff --git a/test/minitest/lib/models/show_test.rb b/test/minitest/lib/models/show_test.rb new file mode 100644 index 0000000..5e70fe8 --- /dev/null +++ b/test/minitest/lib/models/show_test.rb @@ -0,0 +1,35 @@ +require 'minitest/autorun' + +require File.join Dir.pwd, 'lib/models/show.rb' + +class TestShow < MiniTest::Test + def setup + @show = Show.new({ + "title" => "Linux Action Show", + "url" => "las", + "rss" => "las.com/rss", + "aliases" => ["las", "linas"] + }) + end + + def test_matches? + assert @show.matches?("las") + refute @show.matches?("lup") + end + + def test_matches_url? + assert @show.send(:matches_url?, "las") + refute @show.send(:matches_url?, "lup") + end + + def test_matches_title? + assert @show.send(:matches_title?, "action sh") + refute @show.send(:matches_title?, "unpl") + end + + def test_matches_alias? + assert @show.send(:matches_alias?, "linas") + refute @show.send(:matches_alias?, "lup") + end +end + \ No newline at end of file diff --git a/test/minitest/lib/models/shows_test.rb b/test/minitest/lib/models/shows_test.rb new file mode 100644 index 0000000..224c4a4 --- /dev/null +++ b/test/minitest/lib/models/shows_test.rb @@ -0,0 +1,54 @@ +require 'minitest/autorun' + +require File.join Dir.pwd, 'lib/models/shows.rb' + +class ShowsTest < Minitest::Test + def setup + show_hashes = JSON.parse(File.open("test/data/shows.json").read)["shows"] + @shows = Shows.new(show_hashes) + @returned_show = Show.new({ + "rss" => "http://feeds.feedburner.com/linuxunvid", + "url" => "lu", + "title" => "Linux Unplugged", + "aliases" => [ + "lup" + ] + }) + end + + #def test_load? + # shows = Shows.new([]) + + # test_hash = [{ + # "rss" => "http://feeds.feedburner.com/linuxunvid", + # "url" => "lu", + # "title" => "Linux Unplugged", + # "aliases" => ["lup"] + # }] + # + # shows.load(test_hash) + # + # shows_array = shows.get_shows + # show = shows_array[0] + # puts show.inspect + # + # assert_equal(shows_array.length, 1) + # assert_equal(show.(@rss), test_hash.rss) + # assert_equal(show.send(@url), test_hash.url) + # assert_equal(show.send(@title), test_hash.title) + # assert_equal(show.send(@aliases), test_hash.aliases) + #end + + def test_find_show? + assert @returned_show.rss == @shows.find_show("lu").rss + assert @returned_show.url == @shows.find_show("lu").url + assert @returned_show.title == @shows.find_show("lu").title + assert @returned_show.aliases == @shows.find_show("lu").aliases + end + + def test_find_show_title? + assert_equal("Linux Unplugged", @shows.find_show_title("lu")) + assert_equal("Action Show", @shows.find_show_title("Action Show")) + end + +end \ No newline at end of file diff --git a/test/unit/lib/models/calendar_spec.rb b/test/unit/lib/models/calendar_spec.rb index e818e6d..26f988f 100644 --- a/test/unit/lib/models/calendar_spec.rb +++ b/test/unit/lib/models/calendar_spec.rb @@ -4,16 +4,9 @@ describe Calendar::GoogleCalendar do before(:example) do - @google_config = { - :app_name => 'TestBot', - :app_version => '1.0.0', - :calendar_id => 'googlecalendarid', - :api_key => 'googlecalendarapikey' - } - @google_client = double('Google API Client') - @google_api = double('Google Calendar API') - - @google_calendar = Calendar::GoogleCalendar.new(@google_config, @google_client, @google_api) + @calendar_id = 'googlecalendarid' + @google_calendar_service = double('Google Calendar Service') + @google_calendar = Calendar::GoogleCalendar.new(@calendar_id, @google_calendar_service) end it 'gets events from the Google Calendar API' do @@ -27,8 +20,7 @@ }) ])) - expect(@google_client).to receive(:execute) { api_results } - allow(@google_api).to receive(:events) { double(:list => nil) } + allow(@google_calendar_service).to receive(:list_events) { api_results } events = @google_calendar.events expect(events.length).to eq 1 @@ -38,6 +30,16 @@ end end +describe Calendar::NullCalendar do + before(:example) do + @null_calendar = Calendar::NullCalendar.new + end + + it 'returns an empty list of events' do + expect(@null_calendar.events).to be_empty + end +end + describe Calendar::CalendarEvent do before(:context) do @event = Calendar::CalendarEvent.new( diff --git a/test/unit/lib/models/quotelist_spec.rb b/test/unit/lib/models/quotelist_spec.rb index 6e11d7c..2e365fd 100644 --- a/test/unit/lib/models/quotelist_spec.rb +++ b/test/unit/lib/models/quotelist_spec.rb @@ -5,7 +5,7 @@ describe QuoteList do before(:context) do @quotes = { - 'Test' => { + 'test' => { :aliases => ['tester', 'testivus'], :quotes => [ 'A test tests a test of tests.', @@ -17,20 +17,20 @@ end it 'matches a name' do - expect(@quote_list.quote_for 'Test').to satisfy do |value| - @quotes['Test'][:quotes].include? value + expect(@quote_list.quote_for 'test').to satisfy do |value| + @quotes['test'][:quotes].include? value end end it 'matches a name case insensitively' do expect(@quote_list.quote_for 'tesT').to satisfy do |value| - @quotes['Test'][:quotes].include? value + @quotes['test'][:quotes].include? value end end it 'matches an alias' do expect(@quote_list.quote_for 'teSter').to satisfy do |value| - @quotes['Test'][:quotes].include? value + @quotes['test'][:quotes].include? value end end diff --git a/test/unit/lib/models/shows_spec.rb b/test/unit/lib/models/shows_spec.rb new file mode 100644 index 0000000..f435d25 --- /dev/null +++ b/test/unit/lib/models/shows_spec.rb @@ -0,0 +1,65 @@ +require 'rspec' + +require File.join Dir.pwd, 'lib/models/shows.rb' + +describe Shows do + before(:example) do + show_hashes = JSON.parse(File.open("test/data/shows.json").read)["shows"] + @shows = Shows.new(show_hashes) + @returned_show = Show.new({ + "rss" => "http://feeds.feedburner.com/linuxunvid", + "url" => "lu", + "title" => "Linux Unplugged", + "aliases" => [ + "lun", + "lup", + ] + }) + end + + it 'initializes properly' do + expect(@shows.shows[0].rss).to eq @returned_show.rss + expect(@shows.shows[0].title).to eq @returned_show.title + expect(@shows.shows[0].url).to eq @returned_show.url + expect(@shows.shows[0].aliases).to match_array(@returned_show.aliases) + end + + it 'loads shows inflight' do + new_show = [{ + "rss" => "las.com/las", + "url" => "las", + "title" => "Linux Action Show", + "aliases" => [ + "linuxas", + "lashow", + ] + }] + @shows.load(new_show) + expect(@shows.shows.length).to eq 2 + expect(@shows.shows[1].class).to eq Show + expect(@shows.shows[1]).to have_attributes(:rss => new_show[0]['rss'], :url => new_show[0]['url'], :title => new_show[0]['title'], :aliases => ["linuxas", "lashow"]) + end + + it 'matches a show using a known keyword' do + expect(@shows.find_show('lun').class).to eq Show + expect(@shows.find_show('lun').title).to eq "Linux Unplugged" + end + + it 'does not match a show with an unknown keyword' do + expect(@shows.find_show('las')).to eq nil + end + + it 'does not match a show if given an empty string' do + expect(@shows.find_show('')).to eq nil + end + + it 'deletes a show' do + @shows.remove('lu') + expect(@shows.shows.length).to eq 0 + end + + it 'does not delete a show if it does not match' do + @shows.remove('las') + expect(@shows.shows.length).to eq 1 + end +end \ No newline at end of file