coffeeoutside 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yaml +25 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +5 -13
- data/Guardfile +5 -7
- data/README.md +0 -3
- data/Rakefile +11 -6
- data/bin/coffeeoutsidebot +2 -2
- data/bin/console +3 -3
- data/coffeeoutside.gemspec +17 -17
- data/config.example.yaml +1 -1
- data/lib/coffeeoutside/dispatchers/dispatcher.rb +2 -2
- data/lib/coffeeoutside/dispatchers/ical.rb +7 -7
- data/lib/coffeeoutside/dispatchers/json.rb +3 -3
- data/lib/coffeeoutside/dispatchers/rss.rb +11 -11
- data/lib/coffeeoutside/dispatchers/stdout.rb +1 -1
- data/lib/coffeeoutside/event_time.rb +26 -0
- data/lib/coffeeoutside/locations.rb +35 -34
- data/lib/coffeeoutside/version.rb +1 -1
- data/lib/coffeeoutside/weather.rb +22 -10
- data/lib/coffeeoutside.rb +17 -36
- data/locations.schema.yaml +3 -0
- data/locations.yaml +59 -34
- metadata +7 -18
- data/lib/coffeeoutside/dispatchers/twitter.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95c0295fadc19a7ab1d107e9b28560cc1228cb54dd2de613563cc00b44402700
|
4
|
+
data.tar.gz: 24d8a3f2dd2fb3227228b21e7f9ba12e72d56b0c126a3b8cd32e04c9526c145d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdd636bb7d5b8708e847b9e0782c5cc3ad6b0a09fe3133c16e2fe6135128aac974d90ff768f0cb641a7284039ec8273a4f4b68e45c3aa727c9462cca1070eeab
|
7
|
+
data.tar.gz: bee8a49d1eb6181c7dc1222b4478e48570e5f9ae4231dc4cf7eb94b4640d5e9e0e454767b927a795bdbe445a42c21c55c307b514048a99522ea0a4682001364c
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: Ruby
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
branches: [ main ]
|
5
|
+
pull_request:
|
6
|
+
branches: [ main ]
|
7
|
+
permissions:
|
8
|
+
contents: read
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
env:
|
13
|
+
BUNDLE_WITH: development
|
14
|
+
strategy:
|
15
|
+
matrix:
|
16
|
+
ruby-version: ['2.5.1', '3.0', '3.1']
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v3
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby-version }}
|
23
|
+
bundler-cache: true
|
24
|
+
- name: Run tests
|
25
|
+
run: bundle exec rake
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/Gemfile
CHANGED
@@ -1,24 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source "https://rubygems.org"
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
7
|
group :serverhack, optional: true do
|
8
|
-
# TODO better handling of this
|
9
|
-
gem
|
8
|
+
# TODO: better handling of this
|
9
|
+
gem "http", "= 4.0.0"
|
10
10
|
end
|
11
11
|
|
12
12
|
group :development, optional: true do
|
13
13
|
# TODO: submit Ruby 3.0 fixes upstream
|
14
|
-
gem
|
15
|
-
gem
|
16
|
-
gem 'kwalify', '= 0.7.2'
|
17
|
-
gem 'minitest'
|
18
|
-
gem 'rake'
|
19
|
-
|
20
|
-
# soon
|
21
|
-
gem 'rubocop'
|
22
|
-
# gem 'guard-rubocop'
|
23
|
-
# gem 'rubocop-minitest'
|
14
|
+
gem "dc-devtools", "~> 0.5"
|
15
|
+
gem "dc-kwalify", "~> 1.0.0"
|
24
16
|
end
|
data/Guardfile
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
directories
|
4
|
-
.select { |d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist") }
|
3
|
+
directories(%w[lib test].select { |d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist") })
|
5
4
|
|
6
|
-
guard :
|
7
|
-
watch(%r{^test/test_(.*)\.rb$})
|
8
|
-
watch(%r{^lib/coffeeoutside/(.*)\.rb$})
|
9
|
-
watch(%r{^lib/coffeeoutside\.rb$})
|
10
|
-
watch(%r{^test/helper\.rb$}) { 'test' }
|
5
|
+
guard :rake, task: "default" do
|
6
|
+
watch(%r{^test/test_(.*)\.rb$})
|
7
|
+
watch(%r{^lib/coffeeoutside/(.*)\.rb$})
|
8
|
+
watch(%r{^lib/coffeeoutside\.rb$})
|
11
9
|
end
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "dc_rake"
|
5
6
|
|
6
7
|
Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs <<
|
8
|
-
t.libs <<
|
9
|
-
t.test_files = FileList[
|
8
|
+
t.libs << "test"
|
9
|
+
t.libs << "lib"
|
10
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
11
|
end
|
11
12
|
|
12
|
-
task
|
13
|
+
task :kwalify do
|
14
|
+
sh "kwalify -f locations.schema.yaml locations.yaml"
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: %i[test rubocop kwalify]
|
data/bin/coffeeoutsidebot
CHANGED
data/bin/console
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "coffeeoutside"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -11,5 +11,5 @@ require 'coffeeoutside'
|
|
11
11
|
# require "pry"
|
12
12
|
# Pry.start
|
13
13
|
|
14
|
-
require
|
14
|
+
require "irb"
|
15
15
|
IRB.start(__FILE__)
|
data/coffeeoutside.gemspec
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "lib/coffeeoutside/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
6
|
+
spec.name = "coffeeoutside"
|
7
7
|
spec.version = CoffeeOutside::VERSION
|
8
|
-
spec.authors = [
|
8
|
+
spec.authors = ["David Crosby"]
|
9
9
|
|
10
|
-
spec.summary =
|
11
|
-
spec.description =
|
12
|
-
spec.homepage =
|
13
|
-
spec.license =
|
14
|
-
spec.required_ruby_version =
|
10
|
+
spec.summary = "The CoffeeOutside bot"
|
11
|
+
spec.description = "The CoffeeOutside bot helps choose a coffee location based on weather and other inputs"
|
12
|
+
spec.homepage = "https://coffeeoutside.bike"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.5.0"
|
15
15
|
|
16
|
-
spec.metadata[
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
17
|
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/yycbike/coffeeoutside"
|
20
20
|
|
21
21
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
23
23
|
end
|
24
|
-
spec.bindir =
|
24
|
+
spec.bindir = "exe"
|
25
25
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
26
|
-
spec.require_paths = [
|
26
|
+
spec.require_paths = ["lib"]
|
27
27
|
|
28
|
-
spec.add_dependency
|
29
|
-
spec.add_dependency
|
30
|
-
spec.add_dependency
|
31
|
-
spec.
|
28
|
+
spec.add_dependency "icalendar", "= 2.7.1"
|
29
|
+
spec.add_dependency "openweathermap", "= 0.2.3"
|
30
|
+
spec.add_dependency "rss", "= 0.2.7"
|
31
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
32
32
|
end
|
data/config.example.yaml
CHANGED
@@ -23,7 +23,7 @@ module CoffeeOutside
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def notify_production
|
26
|
-
raise
|
26
|
+
raise "notify_production must be overridden"
|
27
27
|
end
|
28
28
|
|
29
29
|
def debug_method
|
@@ -33,7 +33,7 @@ module CoffeeOutside
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def notify_debug
|
36
|
-
raise
|
36
|
+
raise "notify_production must be overridden"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require
|
3
|
+
require_relative "dispatcher"
|
4
|
+
require "icalendar"
|
5
5
|
|
6
6
|
module CoffeeOutside
|
7
7
|
class IcalDispatcher < DispatcherBase
|
8
8
|
def generate_ical_string
|
9
|
-
format =
|
10
|
-
tzid =
|
9
|
+
format = "%Y%m%dT%H%M%S"
|
10
|
+
tzid = "America/Edmonton"
|
11
11
|
|
12
12
|
# Create a calendar with an event (standard method)
|
13
13
|
cal = Icalendar::Calendar.new
|
14
14
|
cal.event do |e|
|
15
|
-
e.dtstart = Icalendar::Values::DateTime.new @start_time.strftime(format),
|
16
|
-
e.dtend = Icalendar::Values::DateTime.new @end_time.strftime(format),
|
15
|
+
e.dtstart = Icalendar::Values::DateTime.new @start_time.strftime(format), "tzid" => tzid
|
16
|
+
e.dtend = Icalendar::Values::DateTime.new @end_time.strftime(format), "tzid" => tzid
|
17
17
|
e.summary = "CoffeeOutside - #{@location.name}"
|
18
18
|
e.location = @location.name
|
19
19
|
end
|
@@ -21,7 +21,7 @@ module CoffeeOutside
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def notify_production
|
24
|
-
i = File.open(
|
24
|
+
i = File.open("yyc.ics", "w")
|
25
25
|
i.write(generate_ical_string)
|
26
26
|
end
|
27
27
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require
|
3
|
+
require_relative "dispatcher"
|
4
|
+
require "json"
|
5
5
|
|
6
6
|
module CoffeeOutside
|
7
7
|
class JsonDispatcher < DispatcherBase
|
@@ -14,7 +14,7 @@ module CoffeeOutside
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def notify_production
|
17
|
-
i = File.open(
|
17
|
+
i = File.open("yyc.json", "w")
|
18
18
|
i.write(generate_json_blob)
|
19
19
|
end
|
20
20
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require
|
3
|
+
require_relative "dispatcher"
|
4
|
+
require "rss"
|
5
5
|
|
6
6
|
module CoffeeOutside
|
7
7
|
class RssDispatcher < DispatcherBase
|
@@ -13,18 +13,18 @@ module CoffeeOutside
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def generate_rss_string
|
16
|
-
RSS::Maker.make(
|
17
|
-
maker.channel.language =
|
18
|
-
maker.channel.author =
|
16
|
+
RSS::Maker.make("2.0") do |maker|
|
17
|
+
maker.channel.language = "en"
|
18
|
+
maker.channel.author = "CoffeeOutsideBot"
|
19
19
|
maker.channel.updated = Time.now.to_s
|
20
|
-
maker.channel.about =
|
21
|
-
maker.channel.link =
|
22
|
-
maker.channel.description =
|
23
|
-
maker.channel.title =
|
20
|
+
maker.channel.about = "https://coffeeoutside.bike/yyc.rss"
|
21
|
+
maker.channel.link = "https://coffeeoutside.bike/yyc.rss"
|
22
|
+
maker.channel.description = "CoffeeOutside is a weekly meetup where Calgarians bike/walk/run/rollerblade to a location, drink coffee/tea/some hot or cold beverage, and shoot the breeze" # rubocop:disable Layout/LineLength
|
23
|
+
maker.channel.title = "CoffeeOutside"
|
24
24
|
|
25
25
|
maker.items.new_item do |item|
|
26
26
|
item.link = @location.url if @location.url
|
27
|
-
item.title = "Location for #{@start_time.strftime(
|
27
|
+
item.title = "Location for #{@start_time.strftime("%Y-%m-%d")}: #{@location.name}"
|
28
28
|
item.description = generate_description
|
29
29
|
item.updated = Time.now.to_s
|
30
30
|
end
|
@@ -32,7 +32,7 @@ module CoffeeOutside
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def notify_production
|
35
|
-
i = File.open(
|
35
|
+
i = File.open("yyc.rss", "w")
|
36
36
|
i.write(generate_rss_string)
|
37
37
|
end
|
38
38
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CoffeeOutside
|
4
|
+
module EventTime
|
5
|
+
def self.next_friday
|
6
|
+
# TODO: this is gross...
|
7
|
+
@next_friday ||= Date.today + [5, 4, 3, 2, 1, 7, 6][Date.today.wday]
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.start_time
|
11
|
+
DateTime.new(
|
12
|
+
next_friday.year, next_friday.month, next_friday.day,
|
13
|
+
7, 30, 0,
|
14
|
+
"-07:00"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.end_time
|
19
|
+
DateTime.new(
|
20
|
+
next_friday.year, next_friday.month, next_friday.day,
|
21
|
+
8, 30, 0,
|
22
|
+
"-07:00"
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,26 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "yaml"
|
4
4
|
|
5
5
|
module CoffeeOutside
|
6
6
|
class Location
|
7
|
-
attr_reader :name, :address, :url, :nearby_coffee
|
7
|
+
attr_reader :name, :location_hint, :address, :url, :nearby_coffee
|
8
8
|
|
9
9
|
def initialize(params)
|
10
|
-
if params[
|
11
|
-
@name = params[
|
10
|
+
if params["name"]
|
11
|
+
@name = params["name"]
|
12
12
|
else
|
13
|
-
raise
|
13
|
+
raise "Location class requires name key"
|
14
14
|
end
|
15
|
-
@paused = params[
|
16
|
-
@nearby_coffee = params[
|
17
|
-
@url = params[
|
18
|
-
@address = params[
|
15
|
+
@paused = params["paused"] || false
|
16
|
+
@nearby_coffee = params["nearby_coffee"] || []
|
17
|
+
@url = params["url"] if params["url"]
|
18
|
+
@address = params["address"] if params["address"]
|
19
|
+
@location_hint = params["location_hint"] if params["location_hint"]
|
19
20
|
|
20
21
|
# Forecast related
|
21
|
-
@rainy_day = params[
|
22
|
-
@high_limit = params[
|
23
|
-
@low_limit = params[
|
22
|
+
@rainy_day = params["rainy_day"] || false
|
23
|
+
@high_limit = params["high_limit"] if params["high_limit"]
|
24
|
+
@low_limit = params["low_limit"] if params["low_limit"]
|
24
25
|
|
25
26
|
# Save params for any dispatcher-specific values
|
26
27
|
@params = params
|
@@ -32,13 +33,10 @@ module CoffeeOutside
|
|
32
33
|
|
33
34
|
def weather_appropriate?(forecast)
|
34
35
|
# TODO: stderr reasons?
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
elsif @high_limit && (forecast.temperature > @high_limit)
|
40
|
-
return false
|
41
|
-
end
|
36
|
+
|
37
|
+
return false if (forecast.rainy? && !@rainy_day) ||
|
38
|
+
(@low_limit && (forecast.temperature < @low_limit)) ||
|
39
|
+
(@high_limit && (forecast.temperature > @high_limit))
|
42
40
|
|
43
41
|
true
|
44
42
|
end
|
@@ -51,20 +49,20 @@ module CoffeeOutside
|
|
51
49
|
class LocationFile
|
52
50
|
attr_reader :locations
|
53
51
|
|
54
|
-
def initialize(filename =
|
52
|
+
def initialize(filename = "./locations.yaml")
|
55
53
|
y = YAML.load_file(filename)
|
56
54
|
@locations = []
|
57
55
|
y.each do |l|
|
58
56
|
@locations.append Location.new(l)
|
59
57
|
end
|
60
|
-
@locations
|
58
|
+
@locations # rubocop:disable Lint/Void
|
61
59
|
end
|
62
60
|
end
|
63
61
|
|
64
62
|
class OverrideFile
|
65
63
|
attr_reader :location
|
66
64
|
|
67
|
-
def initialize(filename =
|
65
|
+
def initialize(filename = "./override.yaml")
|
68
66
|
@filename = filename
|
69
67
|
if ::File.exist? @filename
|
70
68
|
@override = true
|
@@ -87,7 +85,7 @@ module CoffeeOutside
|
|
87
85
|
class LocationChooser
|
88
86
|
attr_reader :location
|
89
87
|
|
90
|
-
def initialize(destructive
|
88
|
+
def initialize(forecast, destructive: false)
|
91
89
|
@location = nil
|
92
90
|
of = OverrideFile.new
|
93
91
|
plf = PriorLocationsFile.new
|
@@ -103,29 +101,32 @@ module CoffeeOutside
|
|
103
101
|
# Remove paused locations
|
104
102
|
locations.delete_if(&:paused?)
|
105
103
|
|
106
|
-
# Remove previously selected locations
|
107
|
-
prior_locations = plf.previous_locations
|
108
|
-
locations.delete_if { |l| prior_locations.include? l.name }
|
109
|
-
|
110
104
|
# Delete locations that don't meet forecast criteria
|
111
105
|
locations.keep_if { |l| l.weather_appropriate? forecast }
|
112
106
|
|
113
107
|
# Raise if no locations remaining
|
114
|
-
raise
|
108
|
+
raise "No locations remaining!" if locations.empty?
|
109
|
+
|
110
|
+
# Remove previously selected locations
|
111
|
+
prior_locations = plf.previous_locations.dup
|
112
|
+
while !prior_locations.empty? && locations.count > 1
|
113
|
+
pl = prior_locations.pop(locations.count - 1)
|
114
|
+
locations.delete_if { |l| pl.include? l.name }
|
115
|
+
end
|
115
116
|
|
116
|
-
# Pick random location
|
117
|
+
# Pick random location if more than one remaining
|
117
118
|
@location = locations.sample
|
118
119
|
end
|
119
120
|
|
120
121
|
# Append to prior locations list
|
121
122
|
plf.append_location @location if destructive
|
122
123
|
|
123
|
-
@location
|
124
|
+
@location # rubocop:disable Lint/Void
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
128
|
class PriorLocationsFile
|
128
|
-
def initialize(filename =
|
129
|
+
def initialize(filename = "./prior_locations.yaml")
|
129
130
|
@filename = filename
|
130
131
|
@locations = if File.exist? filename
|
131
132
|
YAML.load_file(filename) || []
|
@@ -134,13 +135,13 @@ module CoffeeOutside
|
|
134
135
|
end
|
135
136
|
end
|
136
137
|
|
137
|
-
def previous_locations
|
138
|
-
@locations
|
138
|
+
def previous_locations
|
139
|
+
@locations
|
139
140
|
end
|
140
141
|
|
141
142
|
def append_location(location)
|
142
143
|
@locations.append location.name
|
143
|
-
f = File.open(@filename,
|
144
|
+
f = File.open(@filename, "w")
|
144
145
|
f.write(YAML.dump(@locations))
|
145
146
|
end
|
146
147
|
end
|
@@ -1,24 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "openweathermap"
|
4
4
|
|
5
5
|
module CoffeeOutside
|
6
6
|
class OWM
|
7
|
-
|
8
|
-
@city_id = config['city_id']
|
9
|
-
@api_key = config['api_key']
|
7
|
+
attr_accessor :result
|
10
8
|
|
11
|
-
|
9
|
+
def initialize(config, time = DateTime.now)
|
10
|
+
@city_id = config["city_id"]
|
11
|
+
@api_key = config["api_key"]
|
12
|
+
@start_time = time
|
12
13
|
end
|
13
14
|
|
14
15
|
def api_call
|
15
|
-
api = OpenWeatherMap::API.new(@api_key,
|
16
|
-
api.forecast(@city_id)
|
16
|
+
api = OpenWeatherMap::API.new(@api_key, "en", "metric")
|
17
|
+
@result = api.forecast(@city_id)
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
#
|
21
|
-
|
20
|
+
def closest_forecast
|
21
|
+
# Doing this as 'The Price Is Right' rules, forecast with the closest
|
22
|
+
# time to the start without going over wins.
|
23
|
+
@result.forecast.reject! { |x| x.time.to_datetime > @start_time }
|
24
|
+
@result.forecast.last
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_owm_datestring(str)
|
28
|
+
DateTime.strptime(str, "%Y-%m-%d %H-%M-%S")
|
29
|
+
end
|
30
|
+
|
31
|
+
def forecast
|
32
|
+
api_call
|
33
|
+
fc = closest_forecast
|
22
34
|
Forecast.new(humidity: fc.humidity, temperature: fc.temperature)
|
23
35
|
end
|
24
36
|
end
|
data/lib/coffeeoutside.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
3
|
+
require "yaml"
|
4
|
+
require "date"
|
5
|
+
require_relative "coffeeoutside/version"
|
6
|
+
require_relative "coffeeoutside/locations"
|
7
|
+
require_relative "coffeeoutside/weather"
|
8
|
+
require_relative "coffeeoutside/event_time"
|
8
9
|
|
9
10
|
# Dispatchers
|
10
|
-
%w[stdout json ical rss
|
11
|
+
%w[stdout json ical rss].each do |d|
|
11
12
|
require_relative "coffeeoutside/dispatchers/#{d}"
|
12
13
|
end
|
13
14
|
|
@@ -17,11 +18,11 @@ module CoffeeOutside
|
|
17
18
|
class Config
|
18
19
|
attr_reader :dispatchers, :openweathermap
|
19
20
|
|
20
|
-
def initialize(config_file =
|
21
|
+
def initialize(config_file = "config.yaml")
|
21
22
|
config = YAML.load_file(config_file)
|
22
|
-
@production = config[
|
23
|
-
@dispatchers = config[
|
24
|
-
@openweathermap = config[
|
23
|
+
@production = config["production"]
|
24
|
+
@dispatchers = config["dispatchers"]
|
25
|
+
@openweathermap = config["openweathermap"]
|
25
26
|
end
|
26
27
|
|
27
28
|
def production?
|
@@ -29,40 +30,21 @@ module CoffeeOutside
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
|
-
def next_friday
|
33
|
-
# TODO: this is gross...
|
34
|
-
@next_friday ||= Date.today + [5, 4, 3, 2, 1, 7, 6][Date.today.wday]
|
35
|
-
end
|
36
|
-
|
37
|
-
def get_start_time
|
38
|
-
DateTime.new(
|
39
|
-
next_friday.year, next_friday.month, next_friday.day,
|
40
|
-
7, 30, 0
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
def get_end_time
|
45
|
-
DateTime.new(
|
46
|
-
next_friday.year, next_friday.month, next_friday.day,
|
47
|
-
8, 30, 0
|
48
|
-
)
|
49
|
-
end
|
50
|
-
|
51
33
|
def main
|
52
34
|
config = Config.new
|
53
35
|
if config.production?
|
54
|
-
owm = OWM.new config.openweathermap
|
55
|
-
forecast = owm.
|
36
|
+
owm = OWM.new config.openweathermap, EventTime.start_time
|
37
|
+
forecast = owm.forecast
|
56
38
|
else
|
39
|
+
# stub a forecast in since OWM is rate-limited
|
57
40
|
forecast = Forecast.new(humidity: 0, temperature: 10)
|
58
41
|
end
|
59
42
|
|
60
|
-
|
61
|
-
location = LocationChooser.new(destructive, forecast).location
|
43
|
+
location = LocationChooser.new(forecast, destructive: config.production?).location
|
62
44
|
|
63
45
|
dispatch = {
|
64
|
-
start_time:
|
65
|
-
end_time:
|
46
|
+
start_time: EventTime.start_time,
|
47
|
+
end_time: EventTime.end_time,
|
66
48
|
forecast: forecast,
|
67
49
|
location: location,
|
68
50
|
production: config.production?
|
@@ -71,6 +53,5 @@ module CoffeeOutside
|
|
71
53
|
JsonDispatcher.new(dispatch).notify
|
72
54
|
RssDispatcher.new(dispatch).notify
|
73
55
|
IcalDispatcher.new(dispatch).notify
|
74
|
-
TwitterDispatcher.new(dispatch.merge(config.dispatchers['twitter'])).notify
|
75
56
|
end
|
76
57
|
end
|
data/locations.schema.yaml
CHANGED
@@ -7,6 +7,9 @@ sequence:
|
|
7
7
|
desc: "Name of the meetup location"
|
8
8
|
type: str
|
9
9
|
required: yes
|
10
|
+
"location_hint":
|
11
|
+
desc: "Human readable 'hint' where the meetup is"
|
12
|
+
type: str
|
10
13
|
"url":
|
11
14
|
desc: "URL for the site. Order of preference: City of Calgary link (if park), Cafe website (if inside), Google Maps URL"
|
12
15
|
type: str
|
data/locations.yaml
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
---
|
2
|
-
- name:
|
2
|
+
- name: La Boulangerie
|
3
3
|
address: 2435 4 St SW
|
4
4
|
high_limit: 5
|
5
5
|
rainy_day: true
|
6
6
|
paused: true
|
7
7
|
- high_limit: 5
|
8
|
-
name:
|
9
|
-
url:
|
8
|
+
name: Sidewalk Citizen Bakery (Simmons Building)
|
9
|
+
url: https://sidewalkcitizenbakery.com/
|
10
10
|
rainy_day: true
|
11
11
|
paused: true
|
12
12
|
- url: https://alforno.ca/
|
13
|
-
name:
|
13
|
+
name: Alforno
|
14
14
|
high_limit: 5
|
15
15
|
rainy_day: true
|
16
|
-
paused: true
|
16
|
+
paused: true # Opens 8AM as of 2022-11-17
|
17
17
|
- url: https://iloveyoucoffeeshop.com/
|
18
|
-
name:
|
18
|
+
name: I Love You Coffee Shop
|
19
19
|
high_limit: 5
|
20
20
|
rainy_day: true
|
21
|
-
paused: true
|
21
|
+
paused: true # Opens 8AM as of 2022-11-17
|
22
22
|
- url: http://www.bayaricacafe.ca/
|
23
|
-
name:
|
23
|
+
name: Baya Rica Cafe
|
24
24
|
address: 204 7A Street NE
|
25
25
|
high_limit: 5
|
26
26
|
rainy_day: true
|
27
|
-
paused: true
|
28
27
|
- url: https://www.calgaryheritageroastingco.com/
|
29
28
|
name: Calgary Heritage Roasting Company
|
30
29
|
address: 2020 11 St SE
|
31
30
|
high_limit: 5
|
32
31
|
rainy_day: true
|
33
|
-
paused: true
|
32
|
+
paused: true # Opens 8AM as of 2022-11-17
|
34
33
|
- address: 420 2nd Street SW
|
35
34
|
high_limit: 0
|
36
|
-
name:
|
35
|
+
name: Monogram Coffee
|
37
36
|
rainy_day: true
|
38
37
|
- name: Barb Scott Park
|
38
|
+
location_hint: by 13th Ave tables
|
39
39
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Barb-Scott-Park.aspx
|
40
40
|
low_limit: -15
|
41
41
|
- name: Buckmaster Park
|
@@ -43,32 +43,40 @@
|
|
43
43
|
address: 1629 21 Ave SW
|
44
44
|
- high_limit: 0
|
45
45
|
address: 909 10 St SE
|
46
|
-
name:
|
46
|
+
name: Cafe Gravity
|
47
47
|
rainy_day: true
|
48
48
|
paused: true
|
49
49
|
- high_limit: -15
|
50
50
|
address: 1613 9th Street SW
|
51
51
|
name: Caffe Beano
|
52
52
|
rainy_day: true
|
53
|
-
paused: false
|
54
53
|
- name: Ca'puccini
|
55
54
|
paused: true
|
56
55
|
- name: CNIB Fragrant Garden
|
57
56
|
address: 10 11A St NE
|
58
|
-
low_limit:
|
59
|
-
url:
|
60
|
-
paused: true
|
57
|
+
low_limit: 10
|
58
|
+
url: https://goo.gl/maps/2x2rRGgpdCdBG7am8
|
61
59
|
- name: Central Memorial Park
|
62
60
|
low_limit: -15
|
63
61
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Central-Memorial-Park.aspx
|
64
62
|
- name: Connaught Park
|
65
63
|
low_limit: -15
|
66
64
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Connaught-Park.aspx
|
67
|
-
- name:
|
65
|
+
- name: Gladstone Pocket Park
|
68
66
|
address: TODO
|
69
|
-
low_limit: -
|
67
|
+
low_limit: -1
|
70
68
|
url: TODO
|
71
69
|
paused: true
|
70
|
+
- name: Central Library Park
|
71
|
+
location_hint: 3rd St 9th Ave corner park
|
72
|
+
address: 800 3 Street SE
|
73
|
+
low_limit: -1
|
74
|
+
url: https://calgarylibrary.ca/your-library/locations/cent/
|
75
|
+
rainy_day: true
|
76
|
+
- name: Containr Park
|
77
|
+
address: 1020 2 Ave NW
|
78
|
+
low_limit: -15
|
79
|
+
url: https://springboardperformance.com/containr
|
72
80
|
- name: East Village Music Pavilion
|
73
81
|
url: https://www.evexperience.com/music-pavilion
|
74
82
|
low_limit: -10
|
@@ -76,7 +84,6 @@
|
|
76
84
|
- name: Delta Park
|
77
85
|
url: https://www.google.com/maps/place/Delta+Garden/@51.0521029,-114.0786464,17z/
|
78
86
|
low_limit: -10
|
79
|
-
rainy_day: false
|
80
87
|
- name: DeVille Luxury Coffee
|
81
88
|
paused: true
|
82
89
|
- name: Enmax Stage on Prince's Island
|
@@ -89,17 +96,19 @@
|
|
89
96
|
- name: Harley Hotchkiss Gardens
|
90
97
|
low_limit: -5
|
91
98
|
address: 611 4 St SW
|
92
|
-
- name: Harvie Passage
|
99
|
+
- name: Harvie Passage Lookout
|
100
|
+
location_hint: by the weir entrance
|
93
101
|
low_limit: 0
|
94
|
-
url: https://
|
102
|
+
url: https://goo.gl/maps/jZG2saNkY1AnhbY29
|
95
103
|
- name: Haultain Park
|
96
104
|
low_limit: -15
|
97
105
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Haultain-Park.aspx
|
98
|
-
- name: High Park
|
106
|
+
- name: High Park
|
107
|
+
location_hint: walk your bike up
|
99
108
|
low_limit: 0
|
100
109
|
url: https://www.beltlineyyc.ca/highparkyyc
|
101
110
|
- name: Horsy Park
|
102
|
-
low_limit:
|
111
|
+
low_limit: 15
|
103
112
|
url: https://goo.gl/maps/ido1KAcqdJGtkeVN8
|
104
113
|
- name: Humpy Hollow Park
|
105
114
|
url: https://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Humpy-Hollow-Park.aspx
|
@@ -109,17 +118,23 @@
|
|
109
118
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Olympic-Plaza.aspx
|
110
119
|
low_limit: -15
|
111
120
|
nearby_coffee: Ca'Puccini
|
112
|
-
- name:
|
121
|
+
- name: Harmony Park
|
113
122
|
low_limit: -5
|
114
|
-
|
123
|
+
address: 115 4 Ave SW
|
124
|
+
url: https://www.calgary.ca/parks/harmony-park.html
|
115
125
|
- name: Lougheed House Beaulieu Gardens
|
116
|
-
low_limit: -
|
126
|
+
low_limit: -2
|
117
127
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Beaulieu-Gardens.aspx
|
118
|
-
- name:
|
119
|
-
|
120
|
-
|
121
|
-
|
128
|
+
- name: Rosedale Dog Park
|
129
|
+
location_hint: benches at top of bluff
|
130
|
+
address: 1103 10 St NW
|
131
|
+
low_limit: 5
|
132
|
+
url: https://goo.gl/maps/Rpf5WTGQzyMkWSYY9
|
122
133
|
- name: Munro Park
|
134
|
+
address: 425 18 Ave NE
|
135
|
+
url: https://www.calgary.ca/csps/parks/locations/ne-parks/munro-park.html
|
136
|
+
low_limit: 20
|
137
|
+
- name: City Hall municipal building
|
123
138
|
url: TODO
|
124
139
|
paused: true
|
125
140
|
- low_limit: 10
|
@@ -140,7 +155,8 @@
|
|
140
155
|
- name: Reader Rock Garden
|
141
156
|
low_limit: 10
|
142
157
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/SE-parks/Reader-Rock-Garden.aspx
|
143
|
-
- name: Riley Park
|
158
|
+
- name: Riley Park
|
159
|
+
location_hint: Stage
|
144
160
|
url: https://www.calgary.ca/csps/parks/locations/nw-parks/riley-park.html
|
145
161
|
low_limit: -10
|
146
162
|
rainy_day: true
|
@@ -168,18 +184,27 @@
|
|
168
184
|
high_limit: 5
|
169
185
|
address: 314 10th St NW
|
170
186
|
rainy_day: true
|
171
|
-
|
187
|
+
- name: Royal Sunalta Park
|
188
|
+
location_hint: by lower swings
|
189
|
+
low_limit: 5
|
190
|
+
address: 302 Sharon Ave SW
|
191
|
+
url: https://goo.gl/maps/JENurv2NNhDrk6Mu6
|
172
192
|
- name: Societe Coffee Lounge
|
173
193
|
high_limit: -3
|
174
194
|
address: 1223 11 Ave SW
|
175
195
|
rainy_day: true
|
176
|
-
paused: true
|
177
196
|
- name: Sought X Found Coffee
|
178
197
|
url: https://www.soughtxfound.coffee
|
179
198
|
high_limit: -3
|
180
199
|
address: 916 Centre Street North
|
181
200
|
rainy_day: true
|
182
|
-
paused: true
|
201
|
+
paused: true # Opens 8:30AM as of 2022-11-17
|
202
|
+
- name: Muze Coffeehouse
|
203
|
+
url: TODO
|
204
|
+
high_limit: -3
|
205
|
+
address: TODO
|
206
|
+
rainy_day: true
|
207
|
+
paused: true # "temporarily closed" in Google as of 2022-11-17
|
183
208
|
- low_limit: 10
|
184
209
|
url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/SW-parks/Stanley-Park.aspx
|
185
210
|
name: Stanley Park Flower Garden
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coffeeoutside
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Crosby
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: icalendar
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - '='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.2.7
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: twitter
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - '='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 7.0.0
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - '='
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 7.0.0
|
69
55
|
description: The CoffeeOutside bot helps choose a coffee location based on weather
|
70
56
|
and other inputs
|
71
57
|
email:
|
@@ -73,7 +59,9 @@ executables: []
|
|
73
59
|
extensions: []
|
74
60
|
extra_rdoc_files: []
|
75
61
|
files:
|
62
|
+
- ".github/workflows/ruby.yaml"
|
76
63
|
- ".gitignore"
|
64
|
+
- ".rubocop.yml"
|
77
65
|
- CODE_OF_CONDUCT.md
|
78
66
|
- Gemfile
|
79
67
|
- Guardfile
|
@@ -91,7 +79,7 @@ files:
|
|
91
79
|
- lib/coffeeoutside/dispatchers/json.rb
|
92
80
|
- lib/coffeeoutside/dispatchers/rss.rb
|
93
81
|
- lib/coffeeoutside/dispatchers/stdout.rb
|
94
|
-
- lib/coffeeoutside/
|
82
|
+
- lib/coffeeoutside/event_time.rb
|
95
83
|
- lib/coffeeoutside/locations.rb
|
96
84
|
- lib/coffeeoutside/version.rb
|
97
85
|
- lib/coffeeoutside/weather.rb
|
@@ -106,6 +94,7 @@ metadata:
|
|
106
94
|
allowed_push_host: https://rubygems.org
|
107
95
|
homepage_uri: https://coffeeoutside.bike
|
108
96
|
source_code_uri: https://github.com/yycbike/coffeeoutside
|
97
|
+
rubygems_mfa_required: 'true'
|
109
98
|
post_install_message:
|
110
99
|
rdoc_options: []
|
111
100
|
require_paths:
|
@@ -121,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
110
|
- !ruby/object:Gem::Version
|
122
111
|
version: '0'
|
123
112
|
requirements: []
|
124
|
-
rubygems_version: 3.
|
113
|
+
rubygems_version: 3.3.26
|
125
114
|
signing_key:
|
126
115
|
specification_version: 4
|
127
116
|
summary: The CoffeeOutside bot
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: false
|
2
|
-
|
3
|
-
require_relative 'dispatcher'
|
4
|
-
require 'json'
|
5
|
-
require 'twitter'
|
6
|
-
|
7
|
-
module CoffeeOutside
|
8
|
-
class TwitterDispatcher < DispatcherBase
|
9
|
-
def notify_production
|
10
|
-
# Configure client
|
11
|
-
client = Twitter::REST::Client.new do |config|
|
12
|
-
config.consumer_key = @params['consumer_key']
|
13
|
-
config.consumer_secret = @params['consumer_secret']
|
14
|
-
config.access_token = @params['token']
|
15
|
-
config.access_token_secret = @params['token_secret']
|
16
|
-
end
|
17
|
-
|
18
|
-
# Send location tweet
|
19
|
-
t = client.update location_tweet_msg
|
20
|
-
# puts t
|
21
|
-
|
22
|
-
# Send followup tweets
|
23
|
-
# client.update('test', { in_reply_to_status_id: t.id }) if t.id
|
24
|
-
end
|
25
|
-
|
26
|
-
def notify_debug
|
27
|
-
puts "consumer_key = #{@params['consumer_key']}"
|
28
|
-
puts "consumer_secret = #{@params['consumer_secret']}"
|
29
|
-
puts "access_token = #{@params['token']}"
|
30
|
-
puts "access_token_secret = #{@params['token_secret']}"
|
31
|
-
puts location_tweet_msg
|
32
|
-
puts "\n"
|
33
|
-
end
|
34
|
-
|
35
|
-
def location_tweet_msg
|
36
|
-
str = "This week's #CoffeeOutside: #{@location.name}"
|
37
|
-
str << " #{@location.url}" if @location.url
|
38
|
-
str << " (#{@location.address})" if @location.address
|
39
|
-
str << ', see you there! #yycbike'
|
40
|
-
str
|
41
|
-
end
|
42
|
-
|
43
|
-
def weather_tweet_msg
|
44
|
-
# TODO
|
45
|
-
end
|
46
|
-
|
47
|
-
def nearby_locations_tweet_msg
|
48
|
-
# TODO
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|