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 +7 -0
- data/lib/ticking_away/bot.rb +84 -0
- data/lib/ticking_away/chat_bot.rb +45 -0
- data/lib/ticking_away/cinch/plugins/time_info.rb +80 -0
- data/lib/ticking_away/custom_errors.rb +7 -0
- data/lib/ticking_away/json_file_storage.rb +55 -0
- data/lib/ticking_away/world_time.rb +47 -0
- data/lib/ticking_away.rb +6 -0
- metadata +133 -0
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,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
|
data/lib/ticking_away.rb
ADDED
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: []
|