coffeeoutside 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a5744ea07913c3e57048e2e18ac38db972e2f27a58412643ad5c8c21e661092
4
- data.tar.gz: ac683194966a7ad9b8c05ed2580af6c052de9b9a937d970d07f28dc1eb833a95
3
+ metadata.gz: 95c0295fadc19a7ab1d107e9b28560cc1228cb54dd2de613563cc00b44402700
4
+ data.tar.gz: 24d8a3f2dd2fb3227228b21e7f9ba12e72d56b0c126a3b8cd32e04c9526c145d
5
5
  SHA512:
6
- metadata.gz: 20a68ef99969acab09abbeff6b17b869801ea5cb42e98c52376b08d8f994bd2753c545627441a54a8f96e52ec4faa67e52e991e86b73e3700a2bbe3465a2dbed
7
- data.tar.gz: 4a439b69f7698b04d7120446e6f5a364f3ff120df35485f43c48ae88b4bb2332a067d59394939308837b15f2d1556f59fcee4ce218cddaa3ba6f597279b1431a
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
@@ -1,8 +1,11 @@
1
+ *.gem
1
2
  *.ics
2
3
  /.bundle
4
+ /coverage/
3
5
  /vendor
4
6
  Gemfile.lock
5
7
  config.yaml
6
8
  override.yaml
7
9
  prior_locations.yaml
8
10
  yyc.json
11
+ yyc.rss
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ inherit_gem:
2
+ dc-rubocop: ruby25.yml
data/Gemfile CHANGED
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- group :development do
8
- # TODO: submit Ruby 3.0 fixes upstream
9
- gem 'guard'
10
- gem 'guard-minitest'
11
- gem 'kwalify', '= 0.7.2'
12
- gem 'minitest'
13
- gem 'rake'
7
+ group :serverhack, optional: true do
8
+ # TODO: better handling of this
9
+ gem "http", "= 4.0.0"
10
+ end
14
11
 
15
- # soon
16
- gem 'rubocop'
17
- # gem 'guard-rubocop'
18
- # gem 'rubocop-minitest'
12
+ group :development, optional: true do
13
+ # TODO: submit Ruby 3.0 fixes upstream
14
+ gem "dc-devtools", "~> 0.5"
15
+ gem "dc-kwalify", "~> 1.0.0"
19
16
  end
data/Guardfile CHANGED
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- directories %w[lib test] \
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 :minitest do
7
- watch(%r{^test/test_(.*)\.rb$}) { 'test' }
8
- watch(%r{^lib/coffeeoutside/(.*)\.rb$}) { 'test' }
9
- watch(%r{^lib/coffeeoutside\.rb$}) { 'test' }
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
@@ -1,18 +1,17 @@
1
1
  # CoffeeOutsideBot
2
2
 
3
- The CoffeeOutsideBot is designed to pick a location in the city for the
4
- #yycbike crowd to meet and enjoy some hot coffee (or tea!).
3
+ The CoffeeOutsideBot is designed to pick a location in the city
4
+ for the #yycbike crowd to meet and enjoy some hot coffee (or tea!).
5
5
 
6
6
  ## Installation
7
7
 
8
8
  ```
9
9
  git clone https://github.com/yycbike/coffeeoutsidebot.git
10
- go build
10
+ cd coffeeoutsidebot
11
+ bundle install
12
+ bundle exec bin/coffeeoutsidebot
11
13
  ```
12
14
 
13
- ## Twitter integration
14
- You can get the necessary API keys at https://dev.twitter.com/
15
-
16
15
  ## OpenWeatherMap integration
17
16
  You can get an API key at https://openweathermap.org/price
18
17
 
@@ -20,11 +19,15 @@ You can get an API key at https://openweathermap.org/price
20
19
  An .ics file is auto generated. The current bot's version can be found at
21
20
  https://coffeeoutside.bike/yyc.ics
22
21
 
22
+ ## RSS integration
23
+ You can add the bot to your favourite RSS reader with
24
+ https://coffeeoutside.bike/yyc.rss
25
+
23
26
  ## Cron job
24
27
  To have the coffeeoutsidebot fire regularly, set up a cron job
25
28
 
26
29
  ```
27
- 0 17 * * 3 pushd /path/to/coffeeoutsidebot && /path/to/coffeeoutsidebot/coffeeoutsidebot
30
+ 0 17 * * 3 pushd /path/to/coffeeoutsidebot && bundle exec ruby bin/coffeeoutsidebot
28
31
  ```
29
32
 
30
33
  ## Contributing
data/Rakefile CHANGED
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
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 << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/*_test.rb']
8
+ t.libs << "test"
9
+ t.libs << "lib"
10
+ t.test_files = FileList["test/**/test_*.rb"]
10
11
  end
11
12
 
12
- task default: %i[test]
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'coffeeoutside'
5
- include CoffeeOutside
4
+ require "coffeeoutside"
5
+ include CoffeeOutside # rubocop:disable Style/MixinUsage
6
6
 
7
7
  CoffeeOutside.main
data/bin/console CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'bundler/setup'
5
- require 'coffeeoutside'
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 'irb'
14
+ require "irb"
15
15
  IRB.start(__FILE__)
@@ -1,31 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/coffeeoutside/version'
3
+ require_relative "lib/coffeeoutside/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = 'coffeeoutside'
6
+ spec.name = "coffeeoutside"
7
7
  spec.version = CoffeeOutside::VERSION
8
- spec.authors = ['David Crosby']
8
+ spec.authors = ["David Crosby"]
9
9
 
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'
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['allowed_push_host'] = 'https://rubygems.org'
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
17
 
18
- spec.metadata['homepage_uri'] = spec.homepage
19
- spec.metadata['source_code_uri'] = 'https://github.com/yycbike/coffeeoutside'
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 = 'exe'
24
+ spec.bindir = "exe"
25
25
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
- spec.require_paths = ['lib']
26
+ spec.require_paths = ["lib"]
27
27
 
28
- spec.add_dependency 'icalendar', '= 2.7.1'
29
- spec.add_dependency 'openweathermap', '= 0.2.3'
30
- spec.add_dependency 'twitter', '= 7.0.0'
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"
31
32
  end
data/config.example.yaml CHANGED
@@ -1,6 +1,6 @@
1
1
  production: false
2
2
  dispatchers:
3
- twitter:
3
+ service:
4
4
  consumer_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
5
5
  consumer_key: xxxxxxxxxxxxxxxxxxxxxxxxxx
6
6
  token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@@ -23,7 +23,7 @@ module CoffeeOutside
23
23
  end
24
24
 
25
25
  def notify_production
26
- raise 'notify_production must be overridden'
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 'notify_production must be overridden'
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 'dispatcher'
4
- require 'icalendar'
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 = '%Y%m%dT%H%M%S'
10
- tzid = 'America/Edmonton'
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), 'tzid' => tzid
16
- e.dtend = Icalendar::Values::DateTime.new @end_time.strftime(format), 'tzid' => tzid
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('yyc.ics', 'w')
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 'dispatcher'
4
- require 'json'
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('yyc.json', 'w')
17
+ i = File.open("yyc.json", "w")
18
18
  i.write(generate_json_blob)
19
19
  end
20
20
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dispatcher"
4
+ require "rss"
5
+
6
+ module CoffeeOutside
7
+ class RssDispatcher < DispatcherBase
8
+ def generate_description
9
+ items = []
10
+ items.append(["Address: #{@location.address}"]) if @location.address
11
+ items.append(@forecast)
12
+ items.join("\n")
13
+ end
14
+
15
+ def generate_rss_string
16
+ RSS::Maker.make("2.0") do |maker|
17
+ maker.channel.language = "en"
18
+ maker.channel.author = "CoffeeOutsideBot"
19
+ maker.channel.updated = Time.now.to_s
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
+
25
+ maker.items.new_item do |item|
26
+ item.link = @location.url if @location.url
27
+ item.title = "Location for #{@start_time.strftime("%Y-%m-%d")}: #{@location.name}"
28
+ item.description = generate_description
29
+ item.updated = Time.now.to_s
30
+ end
31
+ end
32
+ end
33
+
34
+ def notify_production
35
+ i = File.open("yyc.rss", "w")
36
+ i.write(generate_rss_string)
37
+ end
38
+
39
+ def notify_debug
40
+ puts generate_rss_string
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dispatcher"
4
+
5
+ module CoffeeOutside
6
+ class StdoutDispatcher < DispatcherBase
7
+ def notify_production
8
+ # Since the bot is cron-based, don't write anything to stdout unless
9
+ # there's a problem
10
+ # puts "Chosen location is #{@location.name}"
11
+ end
12
+
13
+ def notify_debug
14
+ puts "Chosen location is #{@location.name}"
15
+ end
16
+ end
17
+ end
@@ -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 'yaml'
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['name']
11
- @name = params['name']
10
+ if params["name"]
11
+ @name = params["name"]
12
12
  else
13
- raise 'Location class requires name key'
13
+ raise "Location class requires name key"
14
14
  end
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']
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['rainy_day'] || false
22
- @high_limit = params['high_limit'] if params['high_limit']
23
- @low_limit = params['low_limit'] if params['low_limit']
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
- if forecast.rainy? && !@rainy_day
36
- return false
37
- elsif @low_limit && (forecast.temperature < @low_limit)
38
- return false
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 = './locations.yaml')
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 = './override.yaml')
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 = false, forecast)
88
+ def initialize(forecast, destructive: false)
91
89
  @location = nil
92
90
  of = OverrideFile.new
93
91
  plf = PriorLocationsFile.new
@@ -103,40 +101,47 @@ 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 'No locations remaining!' if locations.empty?
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 = './prior_locations.yaml')
129
+ def initialize(filename = "./prior_locations.yaml")
129
130
  @filename = filename
130
- @locations = YAML.load_file(filename) || []
131
+ @locations = if File.exist? filename
132
+ YAML.load_file(filename) || []
133
+ else
134
+ []
135
+ end
131
136
  end
132
137
 
133
- def previous_locations(n = 5)
134
- @locations.last n
138
+ def previous_locations
139
+ @locations
135
140
  end
136
141
 
137
142
  def append_location(location)
138
143
  @locations.append location.name
139
- f = File.open(@filename, 'w')
144
+ f = File.open(@filename, "w")
140
145
  f.write(YAML.dump(@locations))
141
146
  end
142
147
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CoffeeOutside
4
- VERSION = '0.1.0'
4
+ VERSION = "1.0.0"
5
5
  end
@@ -1,24 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'openweathermap'
3
+ require "openweathermap"
4
4
 
5
5
  module CoffeeOutside
6
6
  class OWM
7
- def initialize(config)
8
- @city_id = config['city_id']
9
- @api_key = config['api_key']
7
+ attr_accessor :result
10
8
 
11
- get_forecast
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, 'en', 'metric')
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 get_forecast
20
- # TODO: this looks wrong, check @time!
21
- fc = api_call.forecast[2]
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,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
- require 'date'
5
- require_relative 'coffeeoutside/version'
6
- require_relative 'coffeeoutside/locations'
7
- require_relative 'coffeeoutside/weather'
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
- require_relative 'coffeeoutside/dispatchers/json'
11
- require_relative 'coffeeoutside/dispatchers/ical'
12
- require_relative 'coffeeoutside/dispatchers/twitter'
11
+ %w[stdout json ical rss].each do |d|
12
+ require_relative "coffeeoutside/dispatchers/#{d}"
13
+ end
13
14
 
14
15
  module CoffeeOutside
15
16
  class Error < StandardError; end
@@ -17,11 +18,11 @@ module CoffeeOutside
17
18
  class Config
18
19
  attr_reader :dispatchers, :openweathermap
19
20
 
20
- def initialize(config_file = 'config.yaml')
21
+ def initialize(config_file = "config.yaml")
21
22
  config = YAML.load_file(config_file)
22
- @production = config['production']
23
- @dispatchers = config['dispatchers']
24
- @openweathermap = config['openweathermap']
23
+ @production = config["production"]
24
+ @dispatchers = config["dispatchers"]
25
+ @openweathermap = config["openweathermap"]
25
26
  end
26
27
 
27
28
  def production?
@@ -29,48 +30,28 @@ 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
- puts config.openweathermap
55
- owm = OWM.new config.openweathermap
56
- forecast = owm.get_forecast
36
+ owm = OWM.new config.openweathermap, EventTime.start_time
37
+ forecast = owm.forecast
57
38
  else
39
+ # stub a forecast in since OWM is rate-limited
58
40
  forecast = Forecast.new(humidity: 0, temperature: 10)
59
41
  end
60
42
 
61
- destructive = config.production?
62
- location = LocationChooser.new(destructive, forecast).location
63
- puts "Chosen location is #{location}"
43
+ location = LocationChooser.new(forecast, destructive: config.production?).location
64
44
 
65
45
  dispatch = {
66
- start_time: get_start_time,
67
- end_time: get_end_time,
46
+ start_time: EventTime.start_time,
47
+ end_time: EventTime.end_time,
68
48
  forecast: forecast,
69
49
  location: location,
70
50
  production: config.production?
71
51
  }
52
+ StdoutDispatcher.new(dispatch).notify
72
53
  JsonDispatcher.new(dispatch).notify
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
@@ -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: "@LaBoulangerie4"
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: "@SidewalkSimmons"
9
- url: http://sidewalkcitizenbakery.com/
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: "@AlfornoYYC"
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: "@iloveyoucoffee_"
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: "@BayaRicaCafe"
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: "@monogramco"
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: "@cafegravity"
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: 5
59
- url: TODO
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: Containr Park
65
+ - name: Gladstone Pocket Park
68
66
  address: TODO
69
- low_limit: -15
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 (Weir)
99
+ - name: Harvie Passage Lookout
100
+ location_hint: by the weir entrance
93
101
  low_limit: 0
94
- url: https://www.google.ca/maps/place/Harvie+Passage,+Calgary,+AB/@51.0439438,-114.0137959,15z/data=!4m2!3m1!1s0x53717ac80a6f6a1b:0xe7a331ec29495195
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 (walk your bike up)
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: 10
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: James Short Park
121
+ - name: Harmony Park
113
122
  low_limit: -5
114
- url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/James-Short-Park.aspx
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: -5
126
+ low_limit: -2
117
127
  url: http://www.calgary.ca/CSPS/Parks/Pages/Locations/Downtown-parks/Beaulieu-Gardens.aspx
118
- - name: McHugh Bluff
119
- low_limit: 0
120
- url: TODO
121
- paused: true
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 (Stage)
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
- paused: true
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.1.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: 2022-05-06 00:00:00.000000000 Z
11
+ date: 2023-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: icalendar
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.2.3
41
41
  - !ruby/object:Gem::Dependency
42
- name: twitter
42
+ name: rss
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 7.0.0
47
+ version: 0.2.7
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 7.0.0
54
+ version: 0.2.7
55
55
  description: The CoffeeOutside bot helps choose a coffee location based on weather
56
56
  and other inputs
57
57
  email:
@@ -59,7 +59,9 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
+ - ".github/workflows/ruby.yaml"
62
63
  - ".gitignore"
64
+ - ".rubocop.yml"
63
65
  - CODE_OF_CONDUCT.md
64
66
  - Gemfile
65
67
  - Guardfile
@@ -75,7 +77,9 @@ files:
75
77
  - lib/coffeeoutside/dispatchers/dispatcher.rb
76
78
  - lib/coffeeoutside/dispatchers/ical.rb
77
79
  - lib/coffeeoutside/dispatchers/json.rb
78
- - lib/coffeeoutside/dispatchers/twitter.rb
80
+ - lib/coffeeoutside/dispatchers/rss.rb
81
+ - lib/coffeeoutside/dispatchers/stdout.rb
82
+ - lib/coffeeoutside/event_time.rb
79
83
  - lib/coffeeoutside/locations.rb
80
84
  - lib/coffeeoutside/version.rb
81
85
  - lib/coffeeoutside/weather.rb
@@ -90,6 +94,7 @@ metadata:
90
94
  allowed_push_host: https://rubygems.org
91
95
  homepage_uri: https://coffeeoutside.bike
92
96
  source_code_uri: https://github.com/yycbike/coffeeoutside
97
+ rubygems_mfa_required: 'true'
93
98
  post_install_message:
94
99
  rdoc_options: []
95
100
  require_paths:
@@ -105,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
110
  - !ruby/object:Gem::Version
106
111
  version: '0'
107
112
  requirements: []
108
- rubygems_version: 3.2.22
113
+ rubygems_version: 3.3.26
109
114
  signing_key:
110
115
  specification_version: 4
111
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