pxmyportal 0.0.2 → 0.0.3
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/pxmyportal/agent.rb +65 -57
- data/lib/pxmyportal/command.rb +6 -3
- data/lib/pxmyportal/cookie.rb +37 -0
- data/lib/pxmyportal/error.rb +1 -0
- data/lib/pxmyportal/page.rb +58 -0
- data/lib/pxmyportal/payslip.rb +10 -3
- data/lib/pxmyportal/version.rb +1 -1
- data/lib/pxmyportal/xdg.rb +5 -0
- data/lib/pxmyportal.rb +0 -3
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cbf8a59677b55d931c451253e344c95bd6982772b435aee1ff36d61758d58e0
|
4
|
+
data.tar.gz: d898d019ea5c9cf1a4322d010fa6b9a340124eaf51f486c8ce4598c8f840cba2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a29af76c458801437e49d666ff0811511a2cd0eebb115f712237aca8434b973b254bef3178e4f27cd7ed4af10cf02374993f902d0f9ef9c7a5a0242bc5f8b01
|
7
|
+
data.tar.gz: a76893da0c041570c1895ed29a3db7c2e6757f25b31846a90f13db5a84cd49a6320503a8e2bb56c367ebf723d57ed609d169de685f9b1069b909ca8959c87f71
|
data/CHANGELOG.md
CHANGED
data/lib/pxmyportal/agent.rb
CHANGED
@@ -15,19 +15,19 @@
|
|
15
15
|
|
16
16
|
require "yaml"
|
17
17
|
require "net/http"
|
18
|
-
require "http-cookie" # Use CGI::Cookie?
|
19
18
|
require "nokogiri"
|
19
|
+
require "logger"
|
20
|
+
require "set"
|
20
21
|
require_relative "payslip"
|
22
|
+
require_relative "error"
|
23
|
+
require_relative "cookie"
|
24
|
+
require_relative "page"
|
21
25
|
|
22
26
|
class PXMyPortal::Agent
|
23
|
-
PAYSLIP_PAGE_PATH_SAMPLE = File.join(PXMyPortal::CLIENT_BASEPATH, "SalaryPayslipSample")
|
24
|
-
PAYSLIP_PAGE_PATH_NORMAL = File.join(PXMyPortal::CLIENT_BASEPATH, "SalaryPayslip")
|
25
|
-
|
26
27
|
def let_redirect
|
27
28
|
token = request_verification_token
|
28
|
-
|
29
|
-
request
|
30
|
-
provide_cookie(request, url: build_url(path))
|
29
|
+
request = Net::HTTP::Post.new(PXMyPortal::Page::BASEPATH)
|
30
|
+
@cookie.provide(request, url: build_url(PXMyPortal::Page::BASEPATH))
|
31
31
|
|
32
32
|
data = { LoginId: @user,
|
33
33
|
Password: @password,
|
@@ -37,19 +37,14 @@ class PXMyPortal::Agent
|
|
37
37
|
begin
|
38
38
|
response => Net::HTTPFound
|
39
39
|
rescue => e
|
40
|
-
File.write("let_redirect.html",
|
40
|
+
File.write(File.join(PXMyPortal::XDG::CACHE_DIR, "debug", "let_redirect.html"),
|
41
|
+
response.body)
|
41
42
|
raise e
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
when PAYSLIP_PAGE_PATH_NORMAL
|
48
|
-
@phase = :normal
|
49
|
-
else
|
50
|
-
raise Error, "unexpected location #{location}"
|
51
|
-
end
|
52
|
-
accept_cookie(response, url: build_url(payslip_page_path))
|
45
|
+
@page = PXMyPortal::Page.from_path(response["location"]) \
|
46
|
+
or raise PXMyPortal::Error, "unexpected location #{location}"
|
47
|
+
@cookie.accept(response, url: build_url(@page.path))
|
53
48
|
self
|
54
49
|
end
|
55
50
|
|
@@ -57,22 +52,23 @@ class PXMyPortal::Agent
|
|
57
52
|
existing_payslips = (YAML.load_file(@payslips_path) rescue []) || []
|
58
53
|
payslips.each do |payslip|
|
59
54
|
if existing_payslips&.find { |candidate| payslip == candidate }
|
60
|
-
|
55
|
+
@logger.info("skipping") { payslip }
|
61
56
|
next
|
62
57
|
end
|
63
|
-
path =
|
58
|
+
path = @page.confirm_path
|
64
59
|
request = Net::HTTP::Post.new(path)
|
65
|
-
|
60
|
+
@cookie.provide(request, url: build_url(path))
|
66
61
|
request.form_data = payslip.form_data
|
67
62
|
response = http.request(request)
|
68
63
|
response => Net::HTTPOK
|
69
64
|
|
70
|
-
|
65
|
+
FileUtils.mkdir_p(payslip.directory) unless File.directory?(payslip.directory)
|
66
|
+
@logger.info("saving payslip...") { payslip.filename }
|
71
67
|
File.write(payslip.filename, response.body) unless @test
|
72
68
|
existing_payslips << payslip.metadata
|
73
69
|
end
|
74
70
|
|
75
|
-
File.open(
|
71
|
+
File.open(payslips_path, "w") { |file| YAML.dump(existing_payslips, file) } \
|
76
72
|
unless @test
|
77
73
|
|
78
74
|
self
|
@@ -80,36 +76,60 @@ class PXMyPortal::Agent
|
|
80
76
|
|
81
77
|
private
|
82
78
|
|
83
|
-
def
|
84
|
-
@
|
85
|
-
|
79
|
+
def payslips_path
|
80
|
+
@created_payslips_path and return @created_payslips_path
|
81
|
+
dir = File.dirname(@payslips_path)
|
82
|
+
unless Dir.exist?(dir)
|
83
|
+
@logger.info("creating payslips path...")
|
84
|
+
Dir.mkdir(dir)
|
85
|
+
end
|
86
|
+
@created_payslips_path = @payslips_path
|
86
87
|
end
|
87
88
|
|
88
89
|
def payslips
|
89
90
|
return @payslips if @payslips
|
90
91
|
|
91
|
-
|
92
|
-
|
92
|
+
pages = Set[PXMyPortal::Page::BONUS]
|
93
|
+
unless @bonus_only
|
94
|
+
pages << @page
|
95
|
+
end
|
96
|
+
@logger.debug("pages") { pages }
|
97
|
+
|
98
|
+
@payslips = []
|
99
|
+
pages.each do |page|
|
100
|
+
@payslips.concat(payslips_for_page(page))
|
101
|
+
end
|
102
|
+
@logger.warn("no payslips") if @payslips.empty?
|
103
|
+
@logger.debug("payslips") { @payslips }
|
104
|
+
@payslips
|
105
|
+
end
|
106
|
+
|
107
|
+
def payslips_for_page(page)
|
108
|
+
request = Net::HTTP::Get.new(page.path)
|
109
|
+
@debug and @logger.debug("request") { request }
|
110
|
+
|
111
|
+
@cookie.provide(request, url: build_url(page.path))
|
93
112
|
response = http.request(request)
|
113
|
+
@debug and @logger.debug("response") { response }
|
94
114
|
response => Net::HTTPOK
|
95
115
|
|
96
|
-
|
97
|
-
|
98
|
-
|
116
|
+
File.write(page.cache_path, response.body)
|
117
|
+
page.rows(response.body)
|
118
|
+
.map { |row| PXMyPortal::Payslip.from_row(row, directory: @payslip_dir) }
|
99
119
|
end
|
100
120
|
|
101
121
|
def request_verification_token
|
102
122
|
return @request_verification_token if @request_verification_token
|
103
123
|
|
104
|
-
@
|
124
|
+
@debug_http and http.set_debug_output($stderr)
|
105
125
|
http.start
|
106
|
-
path = File.join(PXMyPortal::BASEPATH, "Auth/Login")
|
126
|
+
path = File.join(PXMyPortal::Page::BASEPATH, "Auth/Login")
|
107
127
|
query = @company
|
108
128
|
response = http.get("#{path}?#{query}")
|
109
129
|
response => Net::HTTPOK
|
110
130
|
|
111
|
-
@
|
112
|
-
|
131
|
+
@cookie.load
|
132
|
+
@cookie.accept(response, url: build_url(path, query:))
|
113
133
|
|
114
134
|
document = Nokogiri::HTML(response.body)
|
115
135
|
token = <<~XPATH
|
@@ -126,33 +146,30 @@ class PXMyPortal::Agent
|
|
126
146
|
URI::HTTPS.build(host: PXMyPortal::HOST, path:, query:)
|
127
147
|
end
|
128
148
|
|
129
|
-
def confirm_pdf_frame_path
|
130
|
-
case @phase
|
131
|
-
in :sample
|
132
|
-
File.join(PXMyPortal::CLIENT_BASEPATH, "ConfirmSamplePDFFrame")
|
133
|
-
in :normal
|
134
|
-
File.join(PXMyPortal::CLIENT_BASEPATH, "ConfirmPDFFrame")
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
149
|
def initialize(debug: false,
|
139
|
-
cookie_jar_path:
|
140
|
-
payslips_path:
|
150
|
+
cookie_jar_path: nil,
|
151
|
+
payslips_path: File.join(ENV["XDG_DATA_HOME"],
|
152
|
+
"pxmyportal", "payslips.yaml"),
|
141
153
|
company:,
|
142
154
|
user:,
|
143
155
|
password:,
|
144
156
|
test: false,
|
145
|
-
payslip_dir:
|
157
|
+
payslip_dir: nil,
|
158
|
+
bonus_only: false,
|
159
|
+
debug_http: false)
|
146
160
|
|
147
161
|
@company = company
|
148
162
|
@user = user
|
149
163
|
@password = password
|
150
|
-
@cookie_jar_path = cookie_jar_path
|
151
|
-
@jar = HTTP::CookieJar.new
|
152
164
|
@debug = debug
|
153
165
|
@payslips_path = payslips_path
|
154
166
|
@test = test
|
155
167
|
@payslip_dir = payslip_dir
|
168
|
+
@bonus_only = bonus_only
|
169
|
+
@debug_http = debug_http
|
170
|
+
|
171
|
+
@logger = Logger.new($stderr)
|
172
|
+
@cookie = PXMyPortal::Cookie.new(jar_path: cookie_jar_path, logger: @logger)
|
156
173
|
end
|
157
174
|
|
158
175
|
def http
|
@@ -162,13 +179,4 @@ class PXMyPortal::Agent
|
|
162
179
|
http.use_ssl = true
|
163
180
|
@http = http
|
164
181
|
end
|
165
|
-
|
166
|
-
def accept_cookie(response, url:)
|
167
|
-
response.get_fields("Set-Cookie").each { |value| @jar.parse(value, url) }
|
168
|
-
@jar.save(@cookie_jar_path)
|
169
|
-
end
|
170
|
-
|
171
|
-
def provide_cookie(request, url:)
|
172
|
-
request["Cookie"] = HTTP::Cookie.cookie_value(@jar.cookies(url))
|
173
|
-
end
|
174
182
|
end
|
data/lib/pxmyportal/command.rb
CHANGED
@@ -21,14 +21,17 @@ class PXMyPortal::Command
|
|
21
21
|
options = { company: ENV["PXMYPORTAL_COMPANY"],
|
22
22
|
user: ENV["PXMYPORTAL_USER"],
|
23
23
|
password: ENV["PXMYPORTAL_PASSWORD"],
|
24
|
-
test: ENV["PXMYPORTAL_TEST"]
|
25
|
-
payslip_dir: Dir.pwd }
|
24
|
+
test: ENV["PXMYPORTAL_TEST"] }
|
26
25
|
|
27
26
|
parser = OptionParser.new
|
28
27
|
parser.on("--debug") { options[:debug] = true }
|
29
28
|
parser.on("--cookie-jar=PATH") { |path| options[:cookie_jar_path] = path }
|
30
|
-
parser.on("--payslips=PATH"
|
29
|
+
parser.on("--payslips=PATH",
|
30
|
+
"database file for previously stored payslips") { |path|
|
31
|
+
options[:payslips_path] = path }
|
31
32
|
parser.on("--payslip-dir=PATH") { |path| options[:payslip_dir] = path }
|
33
|
+
parser.on("--bonus-only") { options[:bonus_only] = true }
|
34
|
+
parser.on("--debug-http") { options[:debug_http] = true }
|
32
35
|
parser.parse!
|
33
36
|
|
34
37
|
agent = PXMyPortal::Agent.new(**options)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "http-cookie" # Use CGI::Cookie?
|
2
|
+
require_relative "xdg"
|
3
|
+
|
4
|
+
class PXMyPortal::Cookie
|
5
|
+
def initialize(jar_path: nil, logger:)
|
6
|
+
@jar_path = jar_path || File.join(PXMyPortal::XDG::CACHE_DIR, "cookie-jar")
|
7
|
+
@logger = logger
|
8
|
+
|
9
|
+
@jar = HTTP::CookieJar.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def load
|
13
|
+
@jar.load(@jar_path) if File.exist?(@jar_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Previously accept_cookie.
|
17
|
+
def accept(response, url:)
|
18
|
+
response.get_fields("Set-Cookie").each { |value| @jar.parse(value, url) }
|
19
|
+
@jar.save(jar_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Previously cookie_jar_path.
|
23
|
+
def jar_path
|
24
|
+
@created_jar_path and return @created_jar_path
|
25
|
+
dir = File.dirname(@jar_path)
|
26
|
+
unless Dir.exist?(dir)
|
27
|
+
@logger.info("creating cache directory")
|
28
|
+
Dir.mkdir(dir)
|
29
|
+
end
|
30
|
+
@created_jar_path = @jar_path
|
31
|
+
end
|
32
|
+
|
33
|
+
# Previously provide_cookie.
|
34
|
+
def provide(request, url:)
|
35
|
+
request["Cookie"] = HTTP::Cookie.cookie_value(@jar.cookies(url))
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
PXMyPortal::Error = Class.new(StandardError)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Previously @phase.
|
2
|
+
class PXMyPortal::Page
|
3
|
+
BASEPATH = "/pmpwps/"
|
4
|
+
CLIENT_BASEPATH = File.join(BASEPATH, "pc/")
|
5
|
+
|
6
|
+
SAMPLE_PATH = File.join(CLIENT_BASEPATH, "SalaryPayslipSample")
|
7
|
+
NORMAL_PATH = File.join(CLIENT_BASEPATH, "SalaryPayslip")
|
8
|
+
BONUS_PATH = File.join(CLIENT_BASEPATH, "BonusPayslip")
|
9
|
+
|
10
|
+
attr_reader :path, :confirm_path
|
11
|
+
|
12
|
+
def initialize(path:, confirm_path:, cache_filename:, row_xpath:)
|
13
|
+
@path = path
|
14
|
+
|
15
|
+
# Previously confirm_pdf_frame_path.
|
16
|
+
@confirm_path = confirm_path
|
17
|
+
|
18
|
+
@cache_filename = cache_filename
|
19
|
+
@row_xpath = row_xpath
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache_path
|
23
|
+
@cache_path and return @cache_path
|
24
|
+
@cache_path = File.join(PXMyPortal::XDG::CACHE_DIR, "debug", "page", "#{@cache_filename}.html")
|
25
|
+
|
26
|
+
dir = File.dirname(@cache_path)
|
27
|
+
unless Dir.exist?(dir)
|
28
|
+
FileUtils.mkdir_p(dir)
|
29
|
+
end
|
30
|
+
@cache_path
|
31
|
+
end
|
32
|
+
|
33
|
+
def rows(source)
|
34
|
+
Nokogiri::HTML(source).xpath(@row_xpath)
|
35
|
+
end
|
36
|
+
|
37
|
+
normal_row_xpath = "//*[@id='ContentPlaceHolder1_PayslipGridView']//tr"
|
38
|
+
|
39
|
+
# Previously PAYSLIP_PAGE_PATH_SAMPLE.
|
40
|
+
SAMPLE = new(
|
41
|
+
path: SAMPLE_PATH,
|
42
|
+
confirm_path: File.join(CLIENT_BASEPATH, "ConfirmSamplePDFFrame"),
|
43
|
+
cache_filename: "sample", row_xpath: normal_row_xpath)
|
44
|
+
|
45
|
+
# Previously PAYSLIP_PAGE_PATH_NORMAL.
|
46
|
+
NORMAL = new(path: NORMAL_PATH,
|
47
|
+
confirm_path: File.join(CLIENT_BASEPATH, "ConfirmPDFFrame"),
|
48
|
+
cache_filename: "normal", row_xpath: normal_row_xpath)
|
49
|
+
|
50
|
+
# Previously PAYSLIP_PAGE_PATH_BONUS.
|
51
|
+
BONUS = new(path: BONUS_PATH, confirm_path: :TODO,
|
52
|
+
cache_filename: "bonus",
|
53
|
+
row_xpath: "//*[@id='ContentPlaceHolder1_BonusPayslipGridView']//tr")
|
54
|
+
|
55
|
+
def self.from_path(path)
|
56
|
+
{ SAMPLE_PATH => SAMPLE, NORMAL_PATH => NORMAL, BONUS_PATH => NORMAL }[path]
|
57
|
+
end
|
58
|
+
end
|
data/lib/pxmyportal/payslip.rb
CHANGED
@@ -13,10 +13,13 @@
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
15
|
|
16
|
+
require_relative "xdg"
|
17
|
+
|
16
18
|
class PXMyPortal::Payslip
|
17
19
|
attr_reader :year_month, :description, :directory
|
18
20
|
|
19
|
-
def initialize(year_month:, description:, key1:, key2:, key3:,
|
21
|
+
def initialize(year_month:, description:, key1:, key2:, key3:,
|
22
|
+
directory: PXMyPortal::XDG::DOC_DIR)
|
20
23
|
@year_month = year_month
|
21
24
|
@description = description
|
22
25
|
@key1 = key1
|
@@ -48,7 +51,7 @@ class PXMyPortal::Payslip
|
|
48
51
|
"#<Payslip #{year_month.inspect} #{description.inspect}>"
|
49
52
|
end
|
50
53
|
|
51
|
-
def self.from_row(row, directory:)
|
54
|
+
def self.from_row(row, directory: nil)
|
52
55
|
row.xpath("./td") => [year_month, description, button]
|
53
56
|
year_month.xpath(".//text()") => [year_month]
|
54
57
|
description.xpath(".//text()") => [description]
|
@@ -62,9 +65,13 @@ class PXMyPortal::Payslip
|
|
62
65
|
key1 = match[:key1]
|
63
66
|
key2 = match[:key2]
|
64
67
|
key3 = match[:key3]
|
68
|
+
|
69
|
+
options = {}
|
70
|
+
directory and options[:directory] = directory
|
71
|
+
|
65
72
|
new(year_month: year_month.content,
|
66
73
|
description: description.content,
|
67
74
|
key1:, key2:, key3:,
|
68
|
-
|
75
|
+
**options)
|
69
76
|
end
|
70
77
|
end
|
data/lib/pxmyportal/version.rb
CHANGED
data/lib/pxmyportal.rb
CHANGED
@@ -14,10 +14,7 @@
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
15
|
|
16
16
|
class PXMyPortal
|
17
|
-
Error = Class.new(StandardError)
|
18
17
|
HOST = "pxmyportal.tkc.jp"
|
19
|
-
BASEPATH = "/pmpwps/"
|
20
|
-
CLIENT_BASEPATH = File.join(BASEPATH, "pc/")
|
21
18
|
end
|
22
19
|
|
23
20
|
require_relative "pxmyportal/command"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pxmyportal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- gemmaro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-cookie
|
@@ -55,8 +55,12 @@ files:
|
|
55
55
|
- lib/pxmyportal.rb
|
56
56
|
- lib/pxmyportal/agent.rb
|
57
57
|
- lib/pxmyportal/command.rb
|
58
|
+
- lib/pxmyportal/cookie.rb
|
59
|
+
- lib/pxmyportal/error.rb
|
60
|
+
- lib/pxmyportal/page.rb
|
58
61
|
- lib/pxmyportal/payslip.rb
|
59
62
|
- lib/pxmyportal/version.rb
|
63
|
+
- lib/pxmyportal/xdg.rb
|
60
64
|
homepage:
|
61
65
|
licenses:
|
62
66
|
- GPL-3.0-or-later
|
@@ -76,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
80
|
- !ruby/object:Gem::Version
|
77
81
|
version: '0'
|
78
82
|
requirements: []
|
79
|
-
rubygems_version: 3.
|
83
|
+
rubygems_version: 3.5.22
|
80
84
|
signing_key:
|
81
85
|
specification_version: 4
|
82
86
|
summary: PXまいポータルのコマンドラインツール
|