miser 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba6d3fe5d8a6c40c9bc2305b990e551c77d0e798
4
+ data.tar.gz: f5b20d416913fa3858557acf8736e524cf174fee
5
+ SHA512:
6
+ metadata.gz: 2efc8d444d850a622d164ab88ac64c04fef5d9b5ac895eaa6c534aba16fc6d7e644e1661b25678fe9dd56511cd8d44058e7f9d8e54dbff8c5855410f1fdb1240
7
+ data.tar.gz: e86ec67a877fc113b037aaff6c7fa44384af91a8effb16528ef9cba95e1fb5b77ea2f572ab287d350f03acd59ce3f6621df203df856594a84288ec7fbaf9adaa
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in miser.gemspec
4
+ gemspec
5
+
6
+
7
+ group :development do
8
+ gem 'pry-byebug'
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Michal Cichra
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Miser
2
+
3
+ Your personal assistant. Will fetch your bank records every day and send you a summary.
4
+ That way you can know what happened last night even without checking online banking.
5
+
6
+ ## Installation
7
+
8
+
9
+ Install the gem like:
10
+
11
+ $ gem install miser
12
+
13
+ And then use it like:
14
+
15
+ ```bash
16
+ miser check banc_sabadell NIF PIN
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ You need a Driver that will fetch information from your bank.
22
+ So far there are drivers for Spanish Banc Sabadell and EVO Banco.
23
+
24
+ ## Contributing
25
+
26
+ 1. Fork it ( https://github.com/mikz/miser/fork )
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/miser ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'miser/cli'
4
+
5
+ Miser::CLI.start
data/lib/miser/cli.rb ADDED
@@ -0,0 +1,76 @@
1
+ require 'thor'
2
+ require 'miser'
3
+ require 'miser/driver/banc_sabadell'
4
+ require 'miser/driver/evo_banco'
5
+ require 'rufus-scheduler'
6
+
7
+ begin
8
+ require 'pry'
9
+ rescue LoadError
10
+ end
11
+
12
+ module Miser
13
+ class CLI < Thor
14
+ DATE_FORMAT = '%Y-%m-%d'.freeze
15
+
16
+ desc 'check DRIVER *LOGIN', 'check account with DRIVER'
17
+ option :days, desc: 'how many days back check', required: true, type: :numeric, default: 1
18
+ def check(driver, *login)
19
+ console = Miser::Report::Console.new
20
+ movements = movements(driver, *login)
21
+ report = Miser::Report.new(movements, date)
22
+ console.deliver(report)
23
+ end
24
+
25
+ option :api_key, desc: 'Mailgun API key', required: true
26
+ option :to, desc: 'email address', required: true
27
+ option :from, desc: 'email address', required: true
28
+ option :days, desc: 'how many days back check', required: true, type: :numeric, default: 1
29
+ desc 'report DRIVER *LOGIN', 'check your account and deliver the report to email'
30
+ def report(driver, *login)
31
+ mail = Miser::Report::Mailgun.new(options[:api_key], options[:from], options[:to])
32
+ movements = movements(driver, *login)
33
+ report = Miser::Report.new(movements, date)
34
+ mail.deliver(report)
35
+ end
36
+
37
+ desc 'schedule TIME *ARGS', 'schedule check every TIME'
38
+ def schedule(time, *args)
39
+ hour, minute = time.split(':')
40
+ scheduler = Rufus::Scheduler.new
41
+
42
+ line = "#{minute || '*'} #{hour || '*'} * * *"
43
+ scheduler.cron line do
44
+ system(Process.argv0, *args)
45
+ end
46
+
47
+ Process.setproctitle "miser schedule #{line}"
48
+ puts "Scheduled to be executed every #{line}"
49
+
50
+ scheduler.join
51
+ end
52
+
53
+ private
54
+
55
+ def movements(driver, *login)
56
+ driver = Miser::Driver[driver].new
57
+ driver.login(*login)
58
+
59
+ movements = driver.movements(date)
60
+ movements.select{ |m| m.days_from_now < days && m.debit? }
61
+ end
62
+
63
+
64
+ def days
65
+ options[:days].to_f
66
+ end
67
+
68
+ def date
69
+ DateTime.now - options[:days]
70
+ end
71
+
72
+ def now
73
+ DateTime.now
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ require 'miser/driver'
2
+
3
+ module Miser
4
+ module Driver
5
+ class BancSabadell < Miser::Driver::Base
6
+ def login(nif, pin)
7
+ @session.visit('https://www.bancsabadell.com/txbs/ChangeLocale.init.bs?locale=CAS')
8
+
9
+ @session.fill_in("NIF", with: nif)
10
+ @session.fill_in("pin", with: pin)
11
+
12
+ @session.click_on('Entrar')
13
+ end
14
+
15
+ def parse_movements
16
+ @session.all('table.sorted > tbody > tr').map do |row|
17
+ fragment = Nokogiri::HTML::DocumentFragment.parse(row['innerHTML'].strip)
18
+
19
+ amount = fragment.css('td[headers=amount]').attribute('title')
20
+ date = fragment.css('td[headers=date]').attribute('abbr')
21
+ purpose = fragment.css('td[headers=purpose]')
22
+
23
+ Movement.new(
24
+ date: DateTime.strptime(date.value, '%y%m%d%H%M%S'),
25
+ amount: amount.value.to_f,
26
+ purpose: purpose.text
27
+ )
28
+ end
29
+ end
30
+
31
+ def movements(date)
32
+ @session.click_on('Saldo y extracto cuentas')
33
+
34
+ @session.fill_in('dateMovFrom.day', with: date.day)
35
+ @session.fill_in('dateMovFrom.month', with: date.month)
36
+ @session.fill_in('dateMovFrom.year', with: date.year)
37
+ @session.fill_in('CUExtractOperationsQuery.paginationRows', with: 100)
38
+
39
+ # select the date period choice
40
+ @session.find('input[type="radio"][name="r1"][value="2"]').click
41
+
42
+ @session.click_on('Aceptar')
43
+
44
+ parse_movements
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,52 @@
1
+ require 'miser/driver'
2
+
3
+ module Miser
4
+ module Driver
5
+ class EvoBanco < Miser::Driver::Base
6
+
7
+ def login(card_number, pin)
8
+ @session.visit('https://bancaelectronica.evobanco.com/')
9
+
10
+ @session.fill_in('card01', with: card_number)
11
+ @session.find('#pin_number').click
12
+ keyboard = @session.find('.virtual_keyboard')
13
+ pin.split('').each do |number|
14
+ keyboard.find('a', text: number).click
15
+ end
16
+ @session.click_on('Accept')
17
+ end
18
+
19
+
20
+ def movements(date)
21
+ @session.click_on('Accounts')
22
+ @session.click_on('Movements')
23
+ @session.click_on('Search for more movements')
24
+
25
+ @session.select date.day.to_s.rjust(2,'0'), from: 'ctl00$ctl00$ctl00$CuerpoPlaceHolder$CuerpoOperacionPlaceHolder$ContenidoOperacionPlaceHolder$fechas$fromdays'
26
+ @session.select date.month.to_s.rjust(2,'0'), from: 'ctl00$ctl00$ctl00$CuerpoPlaceHolder$CuerpoOperacionPlaceHolder$ContenidoOperacionPlaceHolder$fechas$frommonth'
27
+ @session.select date.year, from: 'ctl00$ctl00$ctl00$CuerpoPlaceHolder$CuerpoOperacionPlaceHolder$ContenidoOperacionPlaceHolder$fechas$fromyears'
28
+ @session.click_on('Search')
29
+
30
+ parse_movements
31
+ end
32
+
33
+
34
+ def parse_movements
35
+ @session.all('table.movements > tbody > tr').map do |row|
36
+ fragment = Nokogiri::HTML::DocumentFragment.parse(row['innerHTML'].strip)
37
+ div = fragment.css('td div')
38
+
39
+ date = div.text.match(/Transaction date: (.{19})/)[1]
40
+ purpose = div.css('a')
41
+ amount, _balance = fragment.css('td > span[id] > span[id]')
42
+
43
+ Movement.new(
44
+ date: DateTime.strptime(date, '%d/%m/%Y %H:%M:%S'),
45
+ amount: amount.text.to_f,
46
+ purpose: purpose.text
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ require 'capybara'
2
+ require 'miser'
3
+ require 'miser/movement'
4
+ require 'capybara/selenium/driver'
5
+
6
+ module Miser
7
+ module Driver
8
+
9
+ Capybara.default_wait_time = 30
10
+
11
+ Capybara.register_driver(:firefox) do |app|
12
+ Capybara::Selenium::Driver.new(app, browser: :firefox)
13
+ end
14
+
15
+ Capybara.register_driver(:chrome) do |app|
16
+ Capybara::Selenium::Driver.new(app, browser: :chrome, switches: %w[--test-type])
17
+ end
18
+
19
+ def self.[](name)
20
+ case name
21
+ when 'banc_sabadell', 'sabadell', 'bs'
22
+ Miser::Driver::BancSabadell
23
+ when 'evo_banco', 'evo'
24
+ Miser::Driver::EvoBanco
25
+ else
26
+ raise "unknown driver '#{name}'"
27
+ end
28
+ end
29
+
30
+ class Base
31
+ DEFAULT_BROWSER = :chrome
32
+
33
+ def initialize
34
+ @session = Capybara::Session.new(browser)
35
+ end
36
+
37
+ def browser
38
+ DEFAULT_BROWSER
39
+ end
40
+
41
+ def login(*args)
42
+ end
43
+
44
+ def movements
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ module Miser
2
+ class Movement
3
+ attr_reader :date, :amount, :purpose
4
+
5
+ ZERO = 0.0
6
+
7
+ def initialize(date:, amount:, purpose:)
8
+ @date = date
9
+ @amount = amount
10
+ @purpose = purpose
11
+ end
12
+
13
+ def +(amount)
14
+ @amount + amount
15
+ end
16
+
17
+ def debit?
18
+ @amount < ZERO
19
+ end
20
+
21
+ def credit?
22
+ @amount > ZERO
23
+ end
24
+
25
+ def coerce(numeric)
26
+ [@amount, numeric]
27
+ end
28
+
29
+ def to_s
30
+ "#{@date.to_time.utc}: #{@amount} (#{@purpose})"
31
+ end
32
+
33
+ def days_from_now
34
+ (DateTime.now - @date).to_f
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ module Miser
2
+ class Report
3
+ class Console
4
+ def deliver(report)
5
+ puts report.subject
6
+ puts
7
+ puts report.text
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ require 'mailgun'
2
+
3
+ module Miser
4
+ class Report
5
+ class Mailgun
6
+
7
+ def initialize(api_key, from, to)
8
+ @client = ::Mailgun::Client.new(api_key)
9
+ @to = to
10
+ @from = from
11
+ @domain = from.split('@').last
12
+ end
13
+
14
+ def deliver(report)
15
+ params = {
16
+ from: "Miser <postmaster@#{@domain}>",
17
+ to: @to,
18
+ subject: report.subject,
19
+ text: report.text
20
+ }
21
+
22
+ @client.send_message(@domain, params)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ module Miser
2
+ class Report
3
+ autoload :Mailgun, 'miser/report/mailgun'
4
+ autoload :Console, 'miser/report/console'
5
+
6
+ def initialize(movements, date)
7
+ @movements = movements
8
+ @date = date
9
+ end
10
+
11
+ def total_spent
12
+ total.abs
13
+ end
14
+
15
+ def subject
16
+ "Miser report for #{@date.to_date}"
17
+ end
18
+
19
+ def text
20
+ <<-TEXT
21
+ Total Spent: #{total_spent}
22
+
23
+ #{@movements.join("\n")}
24
+ TEXT
25
+ end
26
+
27
+ private
28
+ def total
29
+ @movements.reduce(:+) || 0
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,3 @@
1
+ module Miser
2
+ VERSION = "0.0.2"
3
+ end
data/lib/miser.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'miser/version'
2
+
3
+ module Miser
4
+ autoload :Report, 'miser/report'
5
+ end
data/miser.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'miser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "miser"
8
+ spec.version = Miser::VERSION
9
+ spec.authors = ["Michal Cichra"]
10
+ spec.email = ["michal@o2h.cz"]
11
+ spec.summary = %q{Your personal finance assistant.}
12
+ spec.description = %q{Will annoy you every day with how much you spent last day.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency 'capybara'
25
+ spec.add_dependency 'selenium-webdriver'
26
+ spec.add_dependency 'thor'
27
+ spec.add_dependency 'rufus-scheduler', '~> 3.0.8'
28
+ spec.add_dependency 'mailgun-ruby', '~> 1.0.2'
29
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Michal Cichra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: capybara
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: selenium-webdriver
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rufus-scheduler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.8
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.8
97
+ - !ruby/object:Gem::Dependency
98
+ name: mailgun-ruby
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.0.2
111
+ description: Will annoy you every day with how much you spent last day.
112
+ email:
113
+ - michal@o2h.cz
114
+ executables:
115
+ - miser
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - bin/miser
125
+ - lib/miser.rb
126
+ - lib/miser/cli.rb
127
+ - lib/miser/driver.rb
128
+ - lib/miser/driver/banc_sabadell.rb
129
+ - lib/miser/driver/evo_banco.rb
130
+ - lib/miser/movement.rb
131
+ - lib/miser/report.rb
132
+ - lib/miser/report/console.rb
133
+ - lib/miser/report/mailgun.rb
134
+ - lib/miser/version.rb
135
+ - miser.gemspec
136
+ homepage: ''
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.2.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Your personal finance assistant.
160
+ test_files: []