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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1225bb380c46c4d93e001e758959b0e2575fed37ea9b9197ad1dc378ec21ccf
4
- data.tar.gz: 7f5d218eb64715592228e72653bd97eea380399679ec2eb0fe682d59718bb448
3
+ metadata.gz: 4cbf8a59677b55d931c451253e344c95bd6982772b435aee1ff36d61758d58e0
4
+ data.tar.gz: d898d019ea5c9cf1a4322d010fa6b9a340124eaf51f486c8ce4598c8f840cba2
5
5
  SHA512:
6
- metadata.gz: fd3bbe882a8658587a95c124a91cf3f69dd408c81faa090728b1ae04eb2b48540ee11c9630d9c101a7fc255b0900ffc66b687a557a1a1449fc4ca0f485a23e8c
7
- data.tar.gz: e055de254378ab03a63493d0f6e2cb8d842558c5778042e7dd20fdea0394665f25cb204ce9b4989caf28e859ea10da1ab0ade3f319cae08c390f10ac7b622a5e
6
+ metadata.gz: 3a29af76c458801437e49d666ff0811511a2cd0eebb115f712237aca8434b973b254bef3178e4f27cd7ed4af10cf02374993f902d0f9ef9c7a5a0242bc5f8b01
7
+ data.tar.gz: a76893da0c041570c1895ed29a3db7c2e6757f25b31846a90f13db5a84cd49a6320503a8e2bb56c367ebf723d57ed609d169de685f9b1069b909ca8959c87f71
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Change Log of PXMyPortal gem
2
2
 
3
+ ## 0.0.3 - 2025-07-19
4
+
5
+ Enable to save bonus payslips.
6
+
3
7
  ## 0.0.2 - 2025-05-26
4
8
 
5
9
  No significant changes.
@@ -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
- path = PXMyPortal::BASEPATH
29
- request = Net::HTTP::Post.new(path)
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", response.body)
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
- case (location = response["location"])
45
- when PAYSLIP_PAGE_PATH_SAMPLE
46
- @phase = :sample
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
- $stderr.puts "skip #{payslip}"
55
+ @logger.info("skipping") { payslip }
61
56
  next
62
57
  end
63
- path = confirm_pdf_frame_path
58
+ path = @page.confirm_path
64
59
  request = Net::HTTP::Post.new(path)
65
- provide_cookie(request, url: build_url(path))
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
- Dir.mkdir(payslip.directory) unless File.directory?(payslip.directory)
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(@payslips_path, "w") { |file| YAML.dump(existing_payslips, file) } \
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 payslip_page_path
84
- @payslip_page_path ||= { sample: PAYSLIP_PAGE_PATH_SAMPLE,
85
- normal: PAYSLIP_PAGE_PATH_NORMAL }[@phase]
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
- request = Net::HTTP::Get.new(payslip_page_path)
92
- provide_cookie(request, url: build_url(payslip_page_path))
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
- @payslips = Nokogiri::HTML(response.body)
97
- .xpath("//*[@id='ContentPlaceHolder1_PayslipGridView']//tr")
98
- .map { |row| PXMyPortal::Payslip.from_row(row, directory: @payslip_dir) }
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
- @debug and http.set_debug_output($stderr)
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
- @jar.load(@cookie_jar_path) if File.exist?(@cookie_jar_path)
112
- accept_cookie(response, url: build_url(path, query:))
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: "pxmyportal.cookie-jar",
140
- payslips_path: "payslips.yaml",
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
@@ -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") { |path| options[:payslips_path] = 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
@@ -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:, directory:)
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
- directory:)
75
+ **options)
69
76
  end
70
77
  end
@@ -16,5 +16,5 @@
16
16
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  module PXMyPortal
19
- VERSION = "0.0.2"
19
+ VERSION = "0.0.3"
20
20
  end
@@ -0,0 +1,5 @@
1
+ class PXMyPortal::XDG
2
+ CACHE_DIR = File.join(ENV["XDG_CACHE_HOME"], "pxmyportal")
3
+ DOC_DIR = File.join(ENV["XDG_DOCUMENTS_DIR"] || File.join(Dir.home, "Documents"),
4
+ "pxmyportal")
5
+ end
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.2
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-05-26 00:00:00.000000000 Z
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.3.26
83
+ rubygems_version: 3.5.22
80
84
  signing_key:
81
85
  specification_version: 4
82
86
  summary: PXまいポータルのコマンドラインツール