easycard 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: de65d5a1fa9046e9b85a468ce23da1c259b968cf
4
+ data.tar.gz: c0c60ef9ea374f6957fa657dd4092dc7ee70565b
5
+ SHA512:
6
+ metadata.gz: 602fdb29058772249ab6eb56b19bf43a21025f8b1ee6d8608ddb72f8ab89e0d1ec168fb6e0cbd742e8d9fec4e544f9089b45f393e71a2b079060bbc258cc7e54
7
+ data.tar.gz: 5257b248199a25b517710f2421c5fd86f2e0bf1352457aa2c26556da2529fd3f218097d8ab5a6095b5d40cda859c10a75ea296642b1039a1ef80a6566f8f25fb
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'optparse/time'
4
+ require 'easycard'
5
+
6
+ options = {format: :table, from: Date.today << 1, to: Date.today}
7
+ parser = OptionParser.new do |opts|
8
+ opts.banner = 'Usage: easycard CARD_ID [options]'
9
+
10
+ opts.on('-mNUMBER', '--months=NUMBER', Float, '最近 n 月') do |n|
11
+ today = Date.today
12
+ options[:from] = today << n
13
+ options[:to] = today
14
+ end
15
+
16
+ opts.on('-dNUMBER', '--days=NUMBER', Float, '最近 n 天') do |n|
17
+ today = Date.today
18
+ options[:from] = today - n
19
+ options[:to] = today
20
+ end
21
+
22
+ opts.on('-j', '--json', '以 JSON 格式顯示') do
23
+ options[:format] = :json
24
+ end
25
+
26
+ opts.on('-y', '--yaml', '以 YAML 格式顯示') do
27
+ options[:format] = :yaml
28
+ end
29
+
30
+ opts.on('-f', '--from=YYYY/MM/DD', Time, '起始時間') do |time|
31
+ options[:from] = time
32
+ end
33
+
34
+ opts.on('-t', '--to=YYYY/MM/DD', Time, '結束時間') do |time|
35
+ options[:to] = time
36
+ end
37
+
38
+ opts.on('-h', '--help', '顯示此訊息') do |v|
39
+ puts opts
40
+ exit 0
41
+ end
42
+ end
43
+
44
+ parser.parse!
45
+
46
+ if ARGV.empty?
47
+ puts parser.help
48
+ exit 0
49
+ end
50
+
51
+
52
+ begin
53
+ response = EasyCard.query(ARGV.first, from: options[:from], to: options[:to])
54
+ puts response.as(options[:format])
55
+ rescue EasyCard::Error => e
56
+ $stderr.puts e.message
57
+ end
@@ -0,0 +1,45 @@
1
+ require 'easycard/response'
2
+ require 'openssl'
3
+ require 'cgi'
4
+ require 'base64'
5
+ require 'net/http'
6
+
7
+ module EasyCard
8
+ KEY = 'EasyCardToKingay23456789'
9
+ IV = '01234567'
10
+ SALT = 'L0CalKing'
11
+ CONST = 8544
12
+
13
+ class Error < RuntimeError; end
14
+ class NotFound < Error; end
15
+
16
+ module_function
17
+
18
+ def query cart_number, from: Date.today-30, to: Date.today, format: nil
19
+ query_hash = {
20
+ cardID: card_id(cart_number),
21
+ begin: from.strftime('%Y-%m-%d'),
22
+ end: to.strftime('%Y-%m-%d'),
23
+ verify: verify,
24
+ ev: 1
25
+ }
26
+ uri = URI('https://wallet.easycard.com.tw/EasyWallet/QueryManager/V3/GetTXNThinDataInfo')
27
+ uri.query = URI.encode_www_form(query_hash)
28
+ Response.new(Net::HTTP.get(uri))
29
+ end
30
+
31
+ def card_id card_number
32
+ remainder = card_number.size % 16
33
+ data = card_number + "\x06"*(16 - remainder) if remainder != 0
34
+ cipher = OpenSSL::Cipher.new('DES3')
35
+ cipher.encrypt
36
+ cipher.key = KEY
37
+ cipher.iv = IV
38
+ Base64.encode64(cipher.update(data)).chop!
39
+ end
40
+
41
+ def verify time = Time.now
42
+ seed = time.month + time.day + time.hour
43
+ Digest::MD5.hexdigest("#{seed * CONST}#{SALT}").upcase!
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ module EasyCard
2
+ module ColorString
3
+ refine String do
4
+ def bold; "\033[1m#{self}\033[22m" end
5
+ def red; "\033[31m#{self}\033[0m" end
6
+ def green; "\033[32m#{self}\033[0m" end
7
+ def yellow; "\033[33m#{self}\033[0m" end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,66 @@
1
+ require 'easycard/color_string'
2
+ require 'json'
3
+ require 'yaml'
4
+ require 'forwardable'
5
+
6
+ module EasyCard
7
+ class Response
8
+ extend Forwardable
9
+
10
+ attr_reader :data, :raw_data, :parsed_data, :balance
11
+ def_delegators :@data, :to_json, :to_yaml, :to_a
12
+
13
+ using ColorString
14
+
15
+ def self.normalize_record record
16
+ type = case record[?T]
17
+ when ?D then :withdrawal
18
+ when ?U then :deposit
19
+ when '罰款' then :fine
20
+ when '查無交易資料' then raise EasyCard::NotFound, record[?T]
21
+ else raise EasyCard::Error, "未知類型: #{record[?T]}"
22
+ end
23
+ record[?L].gsub!('<BR>', ?-)
24
+ {type: type, datetime: record[?D], location: record[?L], balance: record[?A], amount: record[?Q]}
25
+ end
26
+
27
+ def self.type_text type
28
+ case type
29
+ when :withdrawal then '扣款'.red.bold
30
+ when :deposit then '儲值'.green.bold
31
+ when :fine then '罰款'.yellow.bold
32
+ end
33
+ end
34
+
35
+ def initialize raw_data
36
+ @raw_data = raw_data
37
+ @parsed_data = JSON.parse(@raw_data)
38
+ @balance = @parsed_data.pop[?B].to_i if @parsed_data.last[?B]
39
+ @data = @parsed_data.map do |record|
40
+ self.class.normalize_record(record)
41
+ end
42
+ end
43
+
44
+ def as format = nil
45
+ case format
46
+ when :json then to_json
47
+ when :yaml then to_yaml
48
+ when :table then to_s
49
+ else data
50
+ end
51
+ end
52
+
53
+ def to_s
54
+ ret = "%3s | %-17s | %s | %-3s | %-3s | %s\n" % %w[# 時間 種類 金額 餘額 地點]
55
+ ret << "#{?-*3} | #{?-*19} | #{?-*4} | #{?-*5} | #{?-*5} | #{?-*20}\n"
56
+ @data.each_with_index.map{|record, i| ret << line(i+1, record) << $/}
57
+ ret
58
+ end
59
+
60
+ def line id, record
61
+ type = self.class.type_text(record[:type])
62
+ '%3d | %19s | %s | %5s | %5s | %s' % [id, record[:datetime], type, record[:amount], record[:balance], record[:location]]
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module EasyCard
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easycard
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jian Weihang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: |
28
+ 台灣悠遊卡交易紀錄查詢工具,支援指令模式。
29
+ A search tool for Taiwan EasyCard, supporting CLI mode.
30
+ email: tonytonyjan@gmail.com
31
+ executables:
32
+ - easycard
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - bin/easycard
37
+ - lib/easycard.rb
38
+ - lib/easycard/color_string.rb
39
+ - lib/easycard/response.rb
40
+ - lib/easycard/version.rb
41
+ homepage: https://github.com/tonytonyjan/easycard
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.4.6
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: "台灣悠遊卡交易紀錄查詢工具/A search tool for Taiwan EasyCard"
65
+ test_files: []