coffeeoutside 0.2.2 → 1.0.0
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 +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
|