ticking_away 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e696ea9fcd6af36dfc8a9bd7be89923a35fdf70eb4d05bb9fe3c3003606de6f
4
+ data.tar.gz: 6d22e898217913ac1b7bb349f62c83d220c2d83be92bf2b5c4976e382cc4a90e
5
+ SHA512:
6
+ metadata.gz: 487b5fc04e9f0994bd8e082e09d389496457bb7357a3f770076b83b441f10caa76a839835e89e2a9b2e3afadc072c4dd034c3c6b754e5f2f0793dc789bcf203f
7
+ data.tar.gz: 3dbe408e1701b270c3494c1701ee3dbc97f395e1b83e3b38994dc5edd96b603d4af7b34a806bc190b14b97d76359a738b42101ce0cfea00212b7f674f3bb4c9c
@@ -0,0 +1,84 @@
1
+ module TickingAway
2
+ class Bot
3
+ TIMEAT_CMD = '!timeat '.freeze
4
+ TIMEPOPULARITY_CMD = '!timepopularity '.freeze
5
+ DEFAULT_TIME_API = 'http://worldtimeapi.org/api'.freeze
6
+ EXCUSES = [
7
+ 'Time is an illusion',
8
+ 'What is time, really?',
9
+ 'The linear progression of time is currently on hold'
10
+ ].freeze
11
+
12
+ attr_reader :storage, :time_api
13
+
14
+ # Allow the caller to pass in the storage method via
15
+ # primitive DI. Other storage methods must implement
16
+ # increment_stat(<tz_info>) and get_stat(<tz_info or prefix>)
17
+ def initialize(storage: nil, time_api: nil)
18
+ @storage = storage || TickingAway::JSONFileStorage.new
19
+ @time_api = ENV['TIME_API'] || time_api || DEFAULT_TIME_API
20
+ end
21
+
22
+ # Send a string to the Bot in the format of
23
+ # <user_name>: <message>
24
+ # Only !timeat <tz_info> and !timepopularity <tz_info or prefix>
25
+ # commands will return a string response
26
+ def chat(msg)
27
+ message = strip_username(msg)
28
+
29
+ case message.partition(' ')[0]
30
+ when TIMEAT_CMD.strip
31
+ time_check(message)
32
+ when TIMEPOPULARITY_CMD.strip
33
+ stat_check(message)
34
+ end
35
+ end
36
+
37
+ # Check time for the timezone provided against the
38
+ # provided time api.
39
+ def time_check(msg)
40
+ tz_info = parse_message(msg, TIMEAT_CMD.length)
41
+
42
+ puts "Event: Checking Time for timezone: #{tz_info}"
43
+
44
+ time_message(tz_info)
45
+ end
46
+
47
+ # Return the statistic for the provided tz_info or prefix
48
+ def stat_check(msg)
49
+ storage.get_stat(parse_message(msg, TIMEPOPULARITY_CMD.length))
50
+ end
51
+
52
+ private
53
+
54
+ def strip_username(msg)
55
+ return msg unless msg.include?(':')
56
+
57
+ msg.partition(':')[2].strip
58
+ end
59
+
60
+ # Parse the message for the string after the command.
61
+ # Requires the command length (including the ! and space)
62
+ # to know where to start the substring
63
+ def parse_message(msg, cmd_length)
64
+ msg[cmd_length..msg.length]
65
+ end
66
+
67
+ # Generate the time message, returning "unknown location"
68
+ # for any unrecognized time zones and logging any uncaught
69
+ # errors before returning an excuse at random.
70
+ # Stats will only be incremented if the api call was successful
71
+ def time_message(tz_info)
72
+ time = TickingAway::WorldTime.time_at(time_api, tz_info)
73
+
74
+ storage.increment_stat(tz_info)
75
+ time.strftime('%e %b %Y %H:%M')
76
+ rescue TickingAway::Errors::UnrecognizedTimeZone => e
77
+ puts e.message
78
+ 'unknown timezone'
79
+ rescue => e
80
+ puts e.message
81
+ EXCUSES.sample
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,45 @@
1
+ require 'cinch'
2
+
3
+ # This provides a default Chat Bot for running
4
+ # the TickingAway::TimeInfo Cinch plugin. A new
5
+ # Chatbot can be started by calling
6
+ # bot = TickingAway::ChatBot.new(<server>, <channel>)
7
+ # bot.start
8
+ module TickingAway
9
+ class ChatBot
10
+
11
+ def initialize(server, channels)
12
+ @server = server
13
+ @channels = channels
14
+ @bot = nil
15
+ end
16
+
17
+ def start
18
+ # Required for dealing with scope.
19
+ # The block provided when instantiating the
20
+ # bot and the configuration block only have the
21
+ # scope of the start method while the start
22
+ # method has access to the class's instance vars
23
+ # The block cannot access the class's instance vars
24
+ # unless they're assigned to a var in the method's scope
25
+ # Good target for some refactoring
26
+ server = @server
27
+ channels = @channels
28
+
29
+ @bot = Cinch::Bot.new do
30
+ configure do |c|
31
+ c.server = server
32
+ c.channels = channels
33
+ c.nick = 'TickingAwayBot'
34
+ c.plugins.plugins = [TickingAway::TimeInfo]
35
+ end
36
+ end
37
+
38
+ @bot.start
39
+ end
40
+
41
+ def stop
42
+ @bot.stop
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,80 @@
1
+ # = Cinch TickingAway::TimeInfo plugin
2
+ # This plugin enables Cinch to respond to !timeat <tz_info> and
3
+ # !timepopularity <tz_info or prefix> commands.
4
+ #
5
+ # !timeat <tz_info> will respond with the current time for the
6
+ # provided timezone according to the World Time Api or any other
7
+ # provided time Api that conforms to the spec.
8
+ #
9
+ # !timepopularity <tz_info or prefix> will respond with the number
10
+ # of times !timeat was called successfully for the provided tz_info or
11
+ # prefix. For instance, "!timepopularity America" will return the
12
+ # number of times "!timeat America" was called, but it will also
13
+ # count the number of times "!timeat America/Los_Angeles" or
14
+ # "!timeat America/New_York" was called because they both contain
15
+ # the prefix "America"
16
+ #
17
+ #
18
+ # == Configuration
19
+ # Add the following to your bot’s configure.do stanza:
20
+ #
21
+ # config.plugins.options[TickingAway::TimeInfo] = {
22
+ # :time_api => 'https://worldtimeapi.org/api'
23
+ # }
24
+ #
25
+ # [time_api ('https://worldtimeapi.org/api')]
26
+ # The time is retreived from a time Api. It requires the spec to match
27
+ # the World Time Api. If this is not specified either through the config,
28
+ # or by providing the ENV var TIME_API, it will default to https://worldtimeapi.org/api
29
+ #
30
+ # == Author
31
+ # Jeff Wood
32
+ #
33
+ # == License
34
+ # A time info plugin for Cinch.
35
+ # Copyright © 2021 Jeff Wood
36
+ #
37
+ # This program is free software: you can redistribute it and/or modify
38
+ # it under the terms of the GNU Lesser General Public License as published by
39
+ # the Free Software Foundation, either version 3 of the License, or
40
+ # (at your option) any later version.
41
+ #
42
+ # This program is distributed in the hope that it will be useful,
43
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
44
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45
+ # GNU Lesser General Public License for more details.
46
+ #
47
+ # You should have received a copy of the GNU Lesser General Public License
48
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
49
+ require 'cinch'
50
+
51
+ module TickingAway
52
+ class TimeInfo
53
+ include ::Cinch::Plugin
54
+
55
+ match (/timeat */), method: :timeat
56
+ match (/timepopularity */), method: :timepopularity
57
+
58
+ listen_to :connect, method: :on_connect
59
+
60
+ # Instantiate bot with JSON file storage when the bot connects
61
+ # to the IRC server. I'd like storage to be configurable
62
+ # through the Cinch configs eventually
63
+ def on_connect(*)
64
+ @storage = TickingAway::JSONFileStorage.new
65
+ @ta_bot = config[:time_api] ? TickingAway::Bot.new(storage: @storage, time_api: config[:time_api]) : TickingAway::Bot.new(storage: @storage)
66
+ end
67
+
68
+ # Check time for the timezone provided against the
69
+ # provided time api by asking the TickingAway Bot
70
+ def timeat(msg)
71
+ msg.reply @ta_bot.time_check(msg.params[1])
72
+ end
73
+
74
+ # Return the statistic for the provided tz_info or prefix
75
+ # by asking the TickingAway Bot
76
+ def timepopularity(msg)
77
+ msg.reply @ta_bot.stat_check(msg.params[1])
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,7 @@
1
+ module TickingAway
2
+ module Errors
3
+ # Custom Errors
4
+ class UnrecognizedTimeZone < StandardError; end
5
+ class ApiUrlNotFound < StandardError; end
6
+ end
7
+ end
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+
3
+ module TickingAway
4
+ # Class to store !timeat stats as a JSON file in local storage
5
+ # and return !timepopularity stats for a provided !timeat call
6
+ class JSONFileStorage
7
+ attr_reader :filename
8
+ attr_accessor :stats
9
+
10
+ def initialize(filename = 'ticking_away_stats.json')
11
+ @filename = filename
12
+ @stats = read_from_file
13
+ end
14
+
15
+ # Add 1 to a !timeat <tz_info> stat
16
+ # and save the hash as JSON to a file
17
+ def increment_stat(stat_name)
18
+ if stats[stat_name]
19
+ stats[stat_name] += 1
20
+ else
21
+ stats.merge!({ stat_name => 1 })
22
+ end
23
+ save_stats
24
+ end
25
+
26
+ # Get the number of times !timeat was called for a
27
+ # tz_info or prefix. Partial prefix matches count towards
28
+ # the total.
29
+ # If we didn't want them to, it could check the
30
+ # next char in the key after the .start_with? match. If it's outside the
31
+ # length or a "/", then the prefix or tz_info matches exactly
32
+ def get_stat(stat_name)
33
+ call_count = 0
34
+ stats.each do |key, value|
35
+ call_count += value if key.start_with?(stat_name)
36
+ end
37
+
38
+ call_count
39
+ end
40
+
41
+ def save_stats
42
+ File.write(filename, JSON.dump(stats))
43
+ end
44
+
45
+ def read_file
46
+ File.read(filename)
47
+ end
48
+
49
+ # Get saved stats on instantiation or return an empty hash
50
+ def read_from_file
51
+ return JSON.parse(read_file) if File.file?(filename)
52
+ {}
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ require 'httparty'
2
+ require 'time'
3
+ require 'json'
4
+
5
+ module TickingAway
6
+ # Class to get time from the World Time Api or another Api with the same spec
7
+ class WorldTime
8
+ UNKNOWN_LOCATION_RESPONSE = {
9
+ 'error' => 'unknown location'
10
+ }.freeze
11
+
12
+ # Define methods as (kind of) Class methods since we don't need to store state
13
+ class << self
14
+ def time_at(base_url, tz_info)
15
+ request_url = "#{base_url}/timezone/#{tz_info}"
16
+
17
+ response = HTTParty.get(request_url)
18
+ handle_response(response, request_url)
19
+ rescue => e
20
+ puts "Could not connect to time server #{request_url}"
21
+ raise e
22
+ end
23
+
24
+ def handle_response(response, request_url)
25
+ # Convert JSON response to Hash, handling an empty or nil body
26
+ parsed_response = response.body.nil? || response.body.empty? ? {} : JSON.parse(response.body)
27
+
28
+ case response.code
29
+ when 200
30
+ puts "Event: Retreived current time for #{parsed_response['timezone']}: #{parsed_response['datetime']}"
31
+ when 404
32
+ # Differentiate between an unknown location response and a random 404 by checking the response body
33
+ if parsed_response.eql?(UNKNOWN_LOCATION_RESPONSE)
34
+ raise TickingAway::Errors::UnrecognizedTimeZone, "Error: Unrecognized Time Zone #{request_url}"
35
+ end
36
+
37
+ raise TickingAway::Errors::ApiUrlNotFound, "Error: 404 response for #{request_url}"
38
+ else
39
+ raise "Error: #{response.code} #{parsed_response}"
40
+ end
41
+
42
+ # Convert the time from a RFC3339 formatted string to a Time object
43
+ Time.parse(parsed_response['datetime'])
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ require 'ticking_away/chat_bot'
2
+ require 'ticking_away/cinch/plugins/time_info'
3
+ require 'ticking_away/world_time'
4
+ require 'ticking_away/custom_errors'
5
+ require 'ticking_away/json_file_storage'
6
+ require 'ticking_away/bot'
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ticking_away
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Wood
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cinch
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.3.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.3.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.17.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.17.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.13.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.13.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.9.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.9.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.0
97
+ description:
98
+ email: woodjeffrey2@gmail.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - lib/ticking_away.rb
104
+ - lib/ticking_away/bot.rb
105
+ - lib/ticking_away/chat_bot.rb
106
+ - lib/ticking_away/cinch/plugins/time_info.rb
107
+ - lib/ticking_away/custom_errors.rb
108
+ - lib/ticking_away/json_file_storage.rb
109
+ - lib/ticking_away/world_time.rb
110
+ homepage: https://github.com/woodjeffrey2/ticking_away
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.7.0
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.1.2
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: IRC Chat Bot for time shenanigans
133
+ test_files: []