ticking_away 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 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: []