extracare2of 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d174fbafa72c46930ecc9a9b8bb6de7d1f31a935
4
+ data.tar.gz: 07364580e6775bc782038da7cdcbbaacbd184354
5
+ SHA512:
6
+ metadata.gz: 1f4dfd3c0818b0178869f2a6bb2d434d0750b2a5dff458817882d8092903e115b41c69f5793bc353c82c1645c3605e5a44e401c014b39523a894b1c3ec302665
7
+ data.tar.gz: eb25c94b7957907b398d4ed0550b88eace0b4fffd4575940c2ccfeca0170362d67d04d187f29639405e369df887264a9fe5be06dc9931a4c99b3adc6cec13187
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+ gem "mechanize"
3
+ gem "amatch"
4
+ gem "rb-appscript"
5
+ gem "highline"
6
+ gem "chronic"
7
+ gem "sqlite3"
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+
2
+ # ExtraCare Reminders
3
+
4
+ ## Synopsis
5
+
6
+ This script will send CVS ExtraCare coupon reminders to [OmniFocus][6764-001], [iCloud Reminders][6764-002], [Things for Mac][6764-003], or [DueApp][6764-004]. It includes SQLite database support for tracking previously imported coupons and will set task name, due date, start date, and a note
7
+
8
+ This script performs the following actions:
9
+
10
+ + Logs into [CVS mobile][6764-005] web
11
+ + Scans the ExtraCare page for active coupons on your card/account
12
+ + Checks coupons against the database for previously imported coupons
13
+ + Sends every new coupon to the task manager of your choice
14
+
15
+ ## Code Example
16
+
17
+ $ ruby bin/extracare2of username password
18
+ $ Looking for coupons...
19
+ $ ----
20
+ $ Title: 10% off skincare products
21
+ $ - Due Date: 8/10/2013
22
+ $ - Start Date: 8/1/2013
23
+ $ - Note: Reedemable in-store only
24
+
25
+
26
+
27
+ ## Installation
28
+
29
+ Bundle install
30
+
31
+ Edit `config/config.yml` to choose which reminder app you'd like to use.
32
+
33
+ ## Todo
34
+
35
+ + Parse ExtraBucks... I need to wait for CVS to send me some before I can figure out the right regular expression
36
+ + Handle deals that require action, i.e., activate coupon via web
37
+
38
+ _Note: This is for paper coupons only. The actual coupons are on your CVS receipts. This script merely sets reminders for you to use them._
39
+
40
+ ## Credits
41
+
42
+ This ruby script borrows some code from [ttscoff's][6764-006] [otask](http://brettterpstra.com/2011/07/02/otask-cli-for-omnifocus/) CLI OmniFocus gem.
43
+
44
+ ## License
45
+
46
+ Licensed under the [MIT License][6764-007]
47
+ [6764-001]: http://www.omnigroup.com/products/omnifocus/ "OmniFocus for Mac - The Omni Group"
48
+ [6764-002]: http://support.apple.com/kb/ht4861 "iCloud: Calendar Events, Reminders, To Dos, and Tasks behavior ..."
49
+ [6764-003]: http://culturedcode.com/things/ "Things - task management for Mac & iOS | Cultured Code"
50
+ [6764-004]: http://www.dueapp.com/ "Due: The Superfast Reminder App for iPhone & iPad"
51
+ [6764-005]: http://www.cvs.com/promo/promoLandingTemplate.jsp?promoLandingId=mobile-apps "CVS Mobile Apps - CVS pharmacy"
52
+ [6764-006]: https://github.com/ttscoff "ttscoff (Brett Terpstra) · GitHub"
53
+ [6764-007]: http://opensource.org/licenses/MIT "The MIT License (MIT) | Open Source Initiative"
data/bin/extracare2of ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require_relative '../lib/extracare2of.rb'
4
+ username = ARGV[0]
5
+ password = ARGV[1]
6
+ runner = ExtraCare2OF::Runner.new(username: username, password: password)
7
+ runner.run
data/config/config.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ :services:
3
+ :use_omnifocus: true
4
+ :use_reminders: false
5
+ :use_things: false
6
+ :use_dueapp: false
data/db/coupons.db ADDED
File without changes
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'extracare2of/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "extracare2of"
8
+ spec.version = ExtraCare2OF::VERSION
9
+ spec.authors = ["Nick Prokesch"]
10
+ spec.email = ["nick@prokes.ch"]
11
+ spec.summary = %q{send CVS ExtraCare coupon reminders to OmniFocus}
12
+ spec.description = %q{Logs into CVS account, adds coupons with due dates to OmniFocus, remembers imported coupons}
13
+ spec.homepage = "https://github.com/prokizzle/extracare2of"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.require_paths = ["lib"]
18
+ spec.bindir = 'bin'
19
+ spec.executables << 'extracare2of'
20
+
21
+ spec.add_dependency "mechanize", "~> 2.7"
22
+ spec.add_dependency "amatch", "~> 0.2"
23
+ spec.add_dependency "rb-appscript", "~> 0.6"
24
+ spec.add_dependency "highline", "~> 1.6"
25
+ spec.add_dependency "chronic", "~> 0.10"
26
+ spec.add_dependency "sqlite3", "~> 1.3"
27
+ end
@@ -0,0 +1,39 @@
1
+ module ExtraCare2OF
2
+ class Authentication
3
+ attr_reader :hash
4
+
5
+ def initialize(args)
6
+ @username = args[:username]
7
+ @password = args[:password]
8
+ @agent = Mechanize.new
9
+ @hash = Hash.new
10
+ end
11
+
12
+ def login
13
+ page = @agent.get("https://m.cvs.com/mt/www.cvs.com/account/login.jsp")
14
+ form = page.forms.first
15
+ form['/atg/userprofiling/ProfileFormHandler.value.login'] = @username
16
+ form['/atg/userprofiling/ProfileFormHandler.value.password'] = @password
17
+ page = form.submit
18
+ @page = page
19
+ sleep 3
20
+ # puts page.parser.xpath("//body").to_html
21
+ end
22
+
23
+ def request(link, request_id)
24
+ @hash[request_id] = {ready: false}
25
+ url = URI.escape(link)
26
+ @agent.read_timeout=30
27
+ temp = @agent.get(url)
28
+ # @log.debug "#{@url}"
29
+ returned_body = temp.parser.xpath("//body").to_html.to_s
30
+ @hash[request_id] = {url: url.to_s, body: returned_body, html: temp, ready: true}
31
+ {url: url.to_s, body: returned_body, html: temp, hash: request_id.to_i}
32
+ end
33
+
34
+ def logged_in?
35
+ /Welcome/.match(@page.parser.xpath("//body").to_html) == true
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,65 @@
1
+ require 'sqlite3'
2
+
3
+ module ExtraCare2OF
4
+ class Database
5
+ attr_accessor :add_user
6
+ attr_reader :add_user
7
+
8
+ def initialize(args)
9
+ @handle = args[ :username]
10
+ open
11
+ create_tables
12
+ tasks
13
+ end
14
+
15
+ def tasks
16
+ begin
17
+ # @db.execute("alter table matches add column emailed integer")
18
+ rescue
19
+ end
20
+ end
21
+
22
+ def create_tables
23
+ begin
24
+ @db.execute("CREATE TABLE coupons(
25
+ id integer,
26
+ name text,
27
+ due_date integer,
28
+ start_date text,
29
+ handle text,
30
+ PRIMARY KEY(id)
31
+ )
32
+ ")
33
+ rescue
34
+ end
35
+ end
36
+
37
+
38
+ def open
39
+ @db = SQLite3::Database.new( "./db/coupons.db" )
40
+ end
41
+
42
+ def close
43
+ @db.close
44
+ end
45
+
46
+ def add_coupon(args)
47
+
48
+ name = args[:name].to_s
49
+ due_date = args[:due_date].to_s
50
+ start_date = args[:start_date].to_s
51
+ unless coupon_exists?(name)
52
+ # begin
53
+ @db.execute("insert into coupons(name, due_date, start_date, handle) values (?,?,?,?)", name, due_date, start_date, @handle)
54
+ end
55
+ end
56
+
57
+ def coupon_exists?(name)
58
+ temp = @db.execute( "select 1 where exists(
59
+ select 1
60
+ from coupons
61
+ where name = ?
62
+ ) ", [name] ).any?
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,107 @@
1
+
2
+ module ExtraCare2OF
3
+ class Runner
4
+
5
+ def initialize(args)
6
+ @username = args[:username]
7
+ @password = args[:password]
8
+ @db = ExtraCare2OF::Database.new(username: @username)
9
+ @browser = ExtraCare2OF::Authentication.new(username: @username, password: @password)
10
+ @settings = Settings.new
11
+ @browser.login
12
+ @count = 0
13
+ # p @rewards_source
14
+ end
15
+
16
+ def async_response(url)
17
+ request_id = Time.now.to_i
18
+ @browser.request(url, request_id)
19
+ until @browser.hash[request_id][:ready]
20
+ sleep 0.1
21
+ end
22
+ @browser.hash[request_id]
23
+ end
24
+
25
+ # [fix] - rewards scanner not picking up all deals
26
+ # [todo] - add extrabucks support
27
+ def get_coupons
28
+ @rewards_source = async_response("https://m.cvs.com/mt/www.cvs.com/extracare/landing.jsp")[:body]
29
+ if $debug
30
+ puts @rewards_source
31
+ exit
32
+ end
33
+ @deals_array = Array.new
34
+ rewards = @rewards_source.scan(/<div class="un_exEntry">\n.+\>(.+)\<.div><.div.+$\n.+\>(.+)\<.div.+$\n.+\>(.+)\<.div.+$\n.+$/)
35
+ rewards.each do |deal|
36
+ name = deal[0]
37
+ due_date = deal[1]
38
+ note = deal[2]
39
+ # defer_date = deal[3]
40
+ @deals_array.push({:name => name,:due_date => parse_date(due_date), :defer_date => Time.now, :note => note})
41
+ end
42
+ @deals_array
43
+ end
44
+
45
+ def send_bucks_to_card
46
+ bucks = @rewards_source.scan(regex)
47
+ bucks.each do |buck|
48
+ link = buck.match(url)
49
+ link_page = @browser.request(link)
50
+ confirmation = @browser.request(button)
51
+ if confirmation == regex
52
+ puts "Extra Bucks sent to card"
53
+ else
54
+ puts "Error: Unable to send to card"
55
+ end
56
+ end
57
+ end
58
+
59
+ #borrowed from ttscoff's otask
60
+ def parse_date(datestring)
61
+ days = 0
62
+ if datestring =~ /^\+(\d+)$/
63
+ days = (60 * 60 * 24 * $1.to_i)
64
+ newdate = Time.now + days
65
+ else
66
+ newdate = Chronic.parse(datestring, {:context => :future, :ambiguous_time_range => 8})
67
+ end
68
+ # parsed = newdate.strftime('%D %l:%M%p').gsub(/\s+/,' ');
69
+ # return parsed =~ /1969/ ? false : parsed
70
+ return newdate
71
+ end
72
+
73
+ def process_coupon(coupon)
74
+ # puts " - Sending #{get_coupons.size} tasks to OF"
75
+ unless @db.coupon_exists?(coupon[:name])
76
+ @db.add_coupon(name: coupon[:name], due_date: coupon[:due_date], defer_date: coupon[:defer_date])
77
+ puts "----"
78
+ puts " Title: #{coupon[:name]}"
79
+ puts " - Due Date: #{coupon[:due_date]}"
80
+ puts " - Start Date: #{coupon[:defer_date]}"
81
+ puts " - Note: #{coupon[:note]}"
82
+ CreateTask::OmniFocus.new(coupon.to_hash) if @settings.use_omnifocus
83
+ CreateTask::Reminders.new(coupon.to_hash) if @settings.use_reminders
84
+ CreateTask::Things.new(coupon.to_hash) if @settings.use_things
85
+ CreateTask::DueApp.new(coupon.to_hash) if @settings.use_dueapp
86
+ # Services::Reminders.new(coupon.to_hash)
87
+ end
88
+ end
89
+
90
+
91
+ def run
92
+ puts "Looking for coupons..."
93
+ @result = get_coupons
94
+ @result.each {|coupon| process_coupon(coupon)}
95
+ if @count > 0
96
+ puts "Sent #{@count} coupons to OmniFocus"
97
+ else
98
+ puts "No new coupons found."
99
+ end
100
+ # puts "Sending extra bucks to card"
101
+ # send_bucks_to_card
102
+ # puts "Done"
103
+ # exit
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,113 @@
1
+ module CreateTask
2
+ require 'chronic'
3
+
4
+ class OmniFocus
5
+ require 'appscript'#;include Appscript
6
+
7
+
8
+ def initialize(args)
9
+ args[:due_date] = parse_date(args[:due_date]) if args[:due_date]
10
+ args[:defer_date] = parse_date(args[:defer_date]) if args[:defer_date]
11
+
12
+ of = app("OmniFocus")
13
+ @dd= of.default_document
14
+ @dd.make(:new => :inbox_task, :with_properties => args.to_hash)
15
+ end
16
+
17
+ def parse_date(datestring)
18
+ days = 0
19
+ if datestring =~ /^\+(\d+)$/
20
+ days = (60 * 60 * 24 * $1.to_i)
21
+ newdate = Time.now + days
22
+ else
23
+ newdate = Chronic.parse(datestring.to_s, {:context => :future, :ambiguous_time_range => 8})
24
+ end
25
+ # parsed = newdate.strftime('%D %l:%M%p').gsub(/\s+/,' ');
26
+ # return parsed =~ /1969/ ? false : parsed
27
+ return newdate
28
+ end
29
+
30
+
31
+
32
+ end
33
+
34
+ class Reminders
35
+ # include 'helper'
36
+ def initialize(args)
37
+ name = args[:name]
38
+ # due_date = parse_date(args[:due_date])
39
+ due_date = args[:due_date]
40
+ remind = %x{osascript <<'APPLESCRIPT'
41
+ set d_date to date "#{due_date}"
42
+ tell application "Reminders" to show (make new reminder with properties {name:"#{name}", due date:(d_date)})
43
+ APPLESCRIPT}
44
+ end
45
+
46
+ def parse_date(datestring)
47
+ days = 0
48
+ if datestring =~ /^\+(\d+)$/
49
+ days = (60 * 60 * 24 * $1.to_i)
50
+ newdate = Time.now + days
51
+ else
52
+ newdate = Chronic.parse(datestring, {:context => :future, :ambiguous_time_range => 8})
53
+ end
54
+ # parsed = newdate.strftime('%D %l:%M%p').gsub(/\s+/,' ');
55
+ # return parsed =~ /1969/ ? false : parsed
56
+ return newdate
57
+ end
58
+
59
+ end
60
+
61
+ class DueApp
62
+ # include 'helper'
63
+ def initialize(args)
64
+ name = args[:name]
65
+ # due_date = parse_date(args[:due_date])
66
+ due_date = args[:due_date]
67
+ remind =%x{osascript << 'APPLESCRIPT'
68
+ set d_date to date "#{due_date}"
69
+ set theURl to "due:///add?title=#{name}&due_date=" & d_date
70
+ tell application "Finder" to open location theURl
71
+ tell application "Due" to activate
72
+ delay 0.5 -- This delay can be tweaked for your system
73
+ tell application "Due" to activate
74
+ tell application "System Events" to key code 36
75
+ delay 1 -- This delay can be tweaked for your system
76
+ APPLESCRIPT
77
+ }
78
+
79
+ end
80
+
81
+ def parse_date(datestring)
82
+ days = 0
83
+ if datestring =~ /^\+(\d+)$/
84
+ days = (60 * 60 * 24 * $1.to_i)
85
+ newdate = Time.now + days
86
+ else
87
+ newdate = Chronic.parse(datestring, {:context => :future, :ambiguous_time_range => 8})
88
+ end
89
+ # parsed = newdate.strftime('%D %l:%M%p').gsub(/\s+/,' ');
90
+ # return parsed =~ /1969/ ? false : parsed
91
+ return newdate
92
+ end
93
+ end
94
+
95
+ class Things
96
+ def initialize(args)
97
+ name = args[:name]
98
+ due_date = args[:due_date]
99
+ notes = args[:notes]
100
+ tags = args[:tag]
101
+ remind = %x{osascript << 'APPLESCRIPT'
102
+ set d_date to date "#{due_date}"
103
+ tell application "Things"
104
+ set newToDo to make new to do with properties {name: "#{name}"}
105
+ set newToDo's notes to "#{notes}"
106
+ set newToDo's due date to d_date
107
+ set tag names of newToDo to "#{tags}"
108
+ end tell
109
+ APPLESCRIPT}
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,27 @@
1
+ module ExtraCare2OF
2
+ class Settings
3
+ attr_reader :debug, :use_omnifocus, :use_reminders, :use_things, :use_dueapp
4
+
5
+ def initialize
6
+ @filename = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "config", "config.yml"))
7
+ unless File.exists?(@filename)
8
+ config = {services: {
9
+ :use_omnifocus => true,
10
+ :use_reminders => false,
11
+ :use_things => false,
12
+ :use_dueapp => false
13
+ }
14
+ }
15
+ File.open(@filename, "w") do |f|
16
+ f.write(config.to_yaml)
17
+ end
18
+ end
19
+ @settings = YAML.load_file(@filename)
20
+ @use_omnifocus = @settings[:services][:use_omnifocus]
21
+ @use_reminders = @settings[:services][:use_reminders]
22
+ @use_things = @settings[:services][:use_things]
23
+ @use_dueapp = @settings[:services][:use_dueapp]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module ExtraCare2OF
2
+ ExtraCare2OF::VERSION = 0.2
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+ require 'highline/import'
3
+ require 'appscript';include Appscript
4
+ require 'amatch';include Amatch
5
+ require 'chronic'
6
+ require 'mechanize'
7
+ require 'open-uri'
8
+
9
+ module ExtraCare2OF
10
+ Dir[File.dirname(__FILE__) + '/extracare2of/*.rb'].each do |file|
11
+ require file
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: extracare2of
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Nick Prokesch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: amatch
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rb-appscript
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: highline
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: chronic
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ description: Logs into CVS account, adds coupons with due dates to OmniFocus, remembers
98
+ imported coupons
99
+ email:
100
+ - nick@prokes.ch
101
+ executables:
102
+ - extracare2of
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".ruby-version"
108
+ - Gemfile
109
+ - README.md
110
+ - bin/extracare2of
111
+ - config/config.yml
112
+ - db/coupons.db
113
+ - extracare2of.gemspec
114
+ - lib/ExtraCare2OF/authentication.rb
115
+ - lib/ExtraCare2OF/database.rb
116
+ - lib/ExtraCare2OF/runner.rb
117
+ - lib/ExtraCare2OF/services.rb
118
+ - lib/ExtraCare2OF/settings.rb
119
+ - lib/ExtraCare2OF/version.rb
120
+ - lib/extracare2of.rb
121
+ homepage: https://github.com/prokizzle/extracare2of
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.2.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: send CVS ExtraCare coupon reminders to OmniFocus
145
+ test_files: []