Smile Bank to Ledger

Check-in [5454406454]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Build it into a proper script

I'm not entirely sure whether a class structure is appropriate for this. At the
very least because I'm only ever creating one of them, but also because
although it's nice having the separate methods for each stage of the logon
process, etc, they don't really make sense, and cna't be used, as standalone
methods. I'm also not sure about the use of instance variables. Splitting
things out into methods almost requires them as passing arguments would be a
bit clumsy, but then it is almost like using global variables. So, all in all,
I've attempted to make it more Ruby-ish, but I think I've failed.

- I'd got credits and debits the wrong way round
- Adds in dealing with memorable date logon details

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 54544064545452a6bcd792561c7a633106c1b9ed
User & Date: atomicules 2015-07-04 11:21:27
Context
2015-07-04
13:36
Fix sorting/ordering of transactions

Need to get things in the right order for checking whether we've gone far
enough back and also for appending to the main transactions array and then
again for writing out.

Also add appropriate Expenses/Income placeholder check-in: 5bdd7b804f user: atomicules tags: trunk

11:21
Build it into a proper script

I'm not entirely sure whether a class structure is appropriate for this. At the
very least because I'm only ever creating one of them, but also because
although it's nice having the separate methods for each stage of the logon
process, etc, they don't really make sense, and cna't be used, as standalone
methods. I'm also not sure about the use of instance variables. Splitting
things out into methods almost requires them as passing arguments would be a
bit clumsy, but then it is almost like using global variables. So, all in all,
I've attempted to make it more Ruby-ish, but I think I've failed.

- I'd got credits and debits the wrong way round
- Adds in dealing with memorable date logon details check-in: 5454406454 user: atomicules tags: trunk

2015-07-02
20:54
Add basics of getting previous statements check-in: b65738a784 user: atomicules tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to smile.rb.


1
2
3











4


5



6







7
8







9
10
11
12
13
14
15
16
17
18

19
20
21

22
23
24
25
26
27
28

29


30
31
32


33


34

35


36
37
38

39
40




41
42


43









44
45

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
















96

require 'mechanize'
require 'nokogiri'












a = Mechanize.new






#Fetch logon page







page = a.get("https://banking.smile.co.uk/SmileWeb/start.do")








#Since dealing with sensitive logon data don't want to get it from anywhere apart from keyboard input

#Sortcode and Account number login
puts "Enter sortcode"
sortCode = gets.strip
puts "Enter Account number"
accountNumber = gets.strip
page.form.sortCode = sortCode
page.form.accountNumber = accountNumber
page = page.form.submit


#TODO: Need to check for timeouts


#Passcode login
doc = Nokogiri::HTML(page.body)
puts doc.xpath("//label")[0].text
page.form.firstPassCodeDigit = gets.strip
puts doc.xpath("//label")[1].text
page.form.secondPassCodeDigit = gets.strip
page = page.form.submit




#Other logon details
#TODO: Have yet to figure out date of birth
puts page.form.fields[1].name


page.form.fields[1].value = gets.strip


page = page.form.submit




if page.form.action.include?("bulletinBoard")
	page = page.form.submit
end


#Logged on!





#Current account


page = a.click(page.link_with(:text => "current account"))










#recent items

doc = Nokogiri::HTML(page.body)
#Look for summaryTable
rows = doc.xpath("//table[@class='summaryTable']//tr")
#Do something with that!
#array of hashes?
transactions = []
#Skip first row (ths)
rows = rows[1..-1]
rows.each do |row|
	#Skip last row, maybe
	unless row.elements.length == 1
		#TODO: Need to skip BROUGHT FORWARD as well for previous item pages
		date = row.elements[0].text
		name = row.elements[1].text
		credit = row.elements[2].text
		debit = row.elements[3].text

		transactions << { :date => date, :name => name, :credit => credit, :debit => debit}
	end
end	

#TODO: Need to figure out a way to get input for number of previous pages required. Well, a good way, perhaps limit by a date?
puts "Enter date back to fetch transactions to"
#Assume ISO thingy format?
date_limit = gets.strip #.convert_to_date

#Previous statements
page = a.click(page.link_with(:text => "previous statements"))
#Get first page of previous statements
page = a.click(page.links_with(:href => %r{getDomestic} )[0])
#TODO: get transactions in the same way

#then click back
#TODO: This needs to be in a loop
page = a.click(page.link_with(:text => "previous statement page"))
#Need someway of stopping if I call the above in a loop. So probably check dates.

#Write transactions out 
#TODO: Might need to sort by date

File.open("smile.dat", "w") do |file|
	transactions.each do |transaction|
		file << Date.parse(transaction[:date]).strftime("%Y/%m/%d")+"\t"+transaction[:name]+"\n"
		if transaction[:credit].include?("£")
			amount = transaction[:credit].gsub(/£/, "£-")
		else
			amount = transaction[:debit]
		end
		file << "\t"+"Assets:Smile:Current"+"\t"+amount+"\n"
		file << "\n"
	end
















end
>



>
>
>
>
>
>
>
>
>
>
>
|
>
>

>
>
>
|
>
>
>
>
>
>
>
|

>
>
>
>
>
>
>
|
<
|
|
|
|
|
|
|
|
>

<

>
|
|
|
|
|
|
|
>

>
>
|
<
|
>
>
|
>
>
|
>

>
>
|
|
|
>

|
>
>
>
>

|
>
>
|
>
>
>
>
>
>
>
>
>

|
>
|
|
|
<
<
<
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
<
<
<
<
|
<
<
<
<
<

<
<
<
<

<
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

41
42
43
44
45
46
47
48
49
50

51
52
53
54
55
56
57
58
59
60
61
62
63
64

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120




121





122




123

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
require 'optparse'
require 'mechanize'
require 'nokogiri'

optparse = OptionParser.new do |opts|
	opts.on('-d', '--date DATE', "Date") { |d| Date_back = d }
end
optparse.parse!

class Smile
	attr_writer :date_back 
	attr_reader :page
	attr_reader :transactions

	def initialize
		@a = Mechanize.new
		@transactions = []
	end


	def transactions(date_back)
		@date_back = Date.parse(date_back)
		logon_start
		logon_passcode
		logon_other
		logon_bulletin
		recent_transactions
		previous_statements
		write_ledger
	end


	#All these private
	#Note sure how valid it is breaking these out into separate methods
	#Might make more sense in a rakefile as separate tasks with dependencies,
	#but these methods can't be used alone
	#Also, really not sure about using instance variables instead of passing args
	def logon_start
		@page = @a.get("https://banking.smile.co.uk/SmileWeb/start.do")
		#Since dealing with sensitive logon data don't want to get it from anywhere apart from keyboard input

		#Sortcode and Account number login
		puts "Enter sortcode"
		sortCode = gets.strip
		puts "Enter Account number"
		accountNumber = gets.strip
		@page.form.sortCode = sortCode
		@page.form.accountNumber = accountNumber
		@page = @page.form.submit
	end



	def logon_passcode
		#Passcode login
		doc = Nokogiri::HTML(@page.body)
		puts doc.xpath("//label")[0].text
		@page.form.firstPassCodeDigit = gets.strip
		puts doc.xpath("//label")[1].text
		@page.form.secondPassCodeDigit = gets.strip
		@page = @page.form.submit
	end


	def logon_other
		#Other logon details

		@page.form.fields.each do |field|
			unless field.type == "hidden"
				puts field.name
				field.value = gets.strip
			end
		end
		@page = @page.form.submit
	end


	def logon_bulletin
		if @page.form.action.include?("bulletinBoard")
			@page = @page.form.submit
		end
	end


	def recent_transactions
		@page = @a.click(@page.link_with(:text => "current account"))
		get_transactions
	end


	def previous_statements
		#Previous statements
		@page = @a.click(@page.link_with(:text => "previous statements"))
		#Get first page of previous statements
		@page = @a.click(@page.links_with(:href => %r{getDomestic} )[0])
		date = Date.today
		until date < @date_back
			get_transactions
			date = Date.parse(@transactions[-1][:date])
			@page = @a.click(@page.link_with(:text => "previous statement page"))
		end
	end


	def get_transactions
		doc = Nokogiri::HTML(@page.body)
		#Look for summaryTable
		rows = doc.xpath("//table[@class='summaryTable']//tr")



		#Skip first row (table headers, ths)
		rows = rows[1..-1]
		rows.each do |row|
			#Skip last row, maybe
			unless row.elements.length == 1
				#TODO: Need to skip BROUGHT FORWARD as well for previous item pages
				date = row.elements[0].text
				name = row.elements[1].text
				credit = row.elements[2].text
				debit = row.elements[3].text
				unless name.include?("BROUGHT FORWARD")
					@transactions << { :date => date, :name => name, :credit => credit, :debit => debit}
				end
			end
		end	




	end












	#TODO: Might need to sort by date
	def write_ledger
		File.open("smile.dat", "w") do |file|
			@transactions.each do |transaction|
				file << Date.parse(transaction[:date]).strftime("%Y/%m/%d")+"\t"+transaction[:name]+"\n"
				if transaction[:debit].include?("£")
					amount = transaction[:debit].gsub(/£/, "£-")
				else
					amount = transaction[:credit]
				end
				file << "\t"+"Assets:Smile:Current"+"\t"+amount+"\n"
				file << "\n"
			end
		end
	end

	private :logon_start
	private :logon_passcode
	private :logon_other
	private :logon_bulletin
	private :recent_transactions
	private :previous_statements
	private :write_ledger
end


if defined?(Date_back)
	smile = Smile.new
	smile.transactions(Date_back)
end