pxmyportal 0.0.3 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cbf8a59677b55d931c451253e344c95bd6982772b435aee1ff36d61758d58e0
4
- data.tar.gz: d898d019ea5c9cf1a4322d010fa6b9a340124eaf51f486c8ce4598c8f840cba2
3
+ metadata.gz: 2c9e7264c0b9326ead4b20b960416715c49a7e0a6c780fdb1ad8c938a4ba215d
4
+ data.tar.gz: 328ee111614bafbe6d0a44888d5d9256ce1126ccd97c3a99ca5d953d91957a0c
5
5
  SHA512:
6
- metadata.gz: 3a29af76c458801437e49d666ff0811511a2cd0eebb115f712237aca8434b973b254bef3178e4f27cd7ed4af10cf02374993f902d0f9ef9c7a5a0242bc5f8b01
7
- data.tar.gz: a76893da0c041570c1895ed29a3db7c2e6757f25b31846a90f13db5a84cd49a6320503a8e2bb56c367ebf723d57ed609d169de685f9b1069b909ca8959c87f71
6
+ metadata.gz: 231587763356fc6ebb4b7c58b49ab9dbfa786714c9f78a22d1bac6134c74a6ef5ad937734fa4c9dfe0d3767cbb8edffc2a980a0a5ed25691ab02fea2ba70ae36
7
+ data.tar.gz: f5c7045478a23f82b1e24738565f79ec345f520a301b185ed62d7c5894b7d770ab1d376c12f6f81c11ca1aab759b6e3618974b85a80fcb4b7430139ac81e8348
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log of PXMyPortal gem
2
2
 
3
+ ## 0.1.0 - 2025-08-09
4
+
5
+ Bug fixes.
6
+
7
+ ## 0.0.4 - 2025-07-19
8
+
9
+ Bug fixes.
10
+
3
11
  ## 0.0.3 - 2025-07-19
4
12
 
5
13
  Enable to save bonus payslips.
@@ -1,71 +1,72 @@
1
1
  # Copyright (C) 2025 gemmaro
2
-
2
+ #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
5
5
  # the Free Software Foundation, either version 3 of the License, or
6
6
  # (at your option) any later version.
7
-
7
+ #
8
8
  # This program is distributed in the hope that it will be useful,
9
9
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
  # GNU General Public License for more details.
12
-
12
+ #
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
16
  require "yaml"
17
- require "net/http"
18
- require "nokogiri"
19
17
  require "logger"
20
18
  require "set"
21
19
  require_relative "payslip"
22
20
  require_relative "error"
23
- require_relative "cookie"
24
21
  require_relative "page"
25
22
 
26
23
  class PXMyPortal::Agent
27
- def let_redirect
28
- token = request_verification_token
29
- request = Net::HTTP::Post.new(PXMyPortal::Page::BASEPATH)
30
- @cookie.provide(request, url: build_url(PXMyPortal::Page::BASEPATH))
31
-
32
- data = { LoginId: @user,
33
- Password: @password,
34
- "__RequestVerificationToken" => token }
35
- request.form_data = data
36
- response = http.request(request)
37
- begin
38
- response => Net::HTTPFound
39
- rescue => e
40
- File.write(File.join(PXMyPortal::XDG::CACHE_DIR, "debug", "let_redirect.html"),
41
- response.body)
42
- raise e
43
- end
44
-
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))
48
- self
24
+ def let_redirect(path: PXMyPortal::Page::BASEPATH)
25
+ @request_verification_token ||= @token_issuer.get
26
+
27
+ @page = PXMyPortal::Authentication.new(
28
+ path:,
29
+ user: @user,
30
+ password: @password,
31
+ token: @request_verification_token,
32
+ http: @http,
33
+ logger: @logger,
34
+ ).post
49
35
  end
50
36
 
51
37
  def save_payslips
38
+ let_redirect
39
+
52
40
  existing_payslips = (YAML.load_file(@payslips_path) rescue []) || []
53
- payslips.each do |payslip|
54
- if existing_payslips&.find { |candidate| payslip == candidate }
55
- @logger.info("skipping") { payslip }
56
- next
41
+
42
+ pages = Set[PXMyPortal::Page::BONUS]
43
+ unless @bonus_only
44
+ pages << @page
45
+ end
46
+
47
+ pages.each do |page|
48
+ let_redirect(path: page.path)
49
+ payslips = payslips_for_page(page)
50
+
51
+ payslips.each do |payslip|
52
+ if !@force && existing_payslips&.find { |candidate| payslip == candidate }
53
+ @logger.info("skipping") { payslip }
54
+ next
55
+ end
56
+ path = @page.confirm_path
57
+ data = PXMyPortal::DocumentDownloader.new(
58
+ path:,
59
+ form_data: payslip.form_data,
60
+ http: @http,
61
+ logger: @logger,
62
+ ).post
63
+
64
+ path = File.join(@payslip_dir, payslip.filename)
65
+ FileUtils.mkdir_p(@payslip_dir)
66
+ @logger.info("saving payslip...") { path }
67
+ File.write(path, data) unless @test
68
+ existing_payslips << payslip.metadata
57
69
  end
58
- path = @page.confirm_path
59
- request = Net::HTTP::Post.new(path)
60
- @cookie.provide(request, url: build_url(path))
61
- request.form_data = payslip.form_data
62
- response = http.request(request)
63
- response => Net::HTTPOK
64
-
65
- FileUtils.mkdir_p(payslip.directory) unless File.directory?(payslip.directory)
66
- @logger.info("saving payslip...") { payslip.filename }
67
- File.write(payslip.filename, response.body) unless @test
68
- existing_payslips << payslip.metadata
69
70
  end
70
71
 
71
72
  File.open(payslips_path, "w") { |file| YAML.dump(existing_payslips, file) } \
@@ -86,97 +87,54 @@ class PXMyPortal::Agent
86
87
  @created_payslips_path = @payslips_path
87
88
  end
88
89
 
89
- def payslips
90
- return @payslips if @payslips
91
-
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
90
  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))
112
- response = http.request(request)
113
- @debug and @logger.debug("response") { response }
114
- response => Net::HTTPOK
115
-
116
- File.write(page.cache_path, response.body)
117
- page.rows(response.body)
118
- .map { |row| PXMyPortal::Payslip.from_row(row, directory: @payslip_dir) }
119
- end
120
-
121
- def request_verification_token
122
- return @request_verification_token if @request_verification_token
123
-
124
- @debug_http and http.set_debug_output($stderr)
125
- http.start
126
- path = File.join(PXMyPortal::Page::BASEPATH, "Auth/Login")
127
- query = @company
128
- response = http.get("#{path}?#{query}")
129
- response => Net::HTTPOK
130
-
131
- @cookie.load
132
- @cookie.accept(response, url: build_url(path, query:))
133
-
134
- document = Nokogiri::HTML(response.body)
135
- token = <<~XPATH
136
- //form//input[ @type='hidden'
137
- and @name='__RequestVerificationToken' ]
138
- /@value
139
- XPATH
140
- document.xpath(token) => [token]
141
- token or raise Error, token
142
- @request_verification_token = token
143
- end
144
-
145
- def build_url(path, query: nil)
146
- URI::HTTPS.build(host: PXMyPortal::HOST, path:, query:)
91
+ data = PXMyPortal::PayslipList.new(
92
+ path: page.path,
93
+ logger: @logger,
94
+ http: @http,
95
+ ).get
96
+ File.write(page.cache_path, data)
97
+ page.rows(data)
98
+ .map { |row| PXMyPortal::Payslip.from_row(row) }
147
99
  end
148
100
 
149
101
  def initialize(debug: false,
150
- cookie_jar_path: nil,
151
- payslips_path: File.join(ENV["XDG_DATA_HOME"],
152
- "pxmyportal", "payslips.yaml"),
102
+ payslips_path: File.join(PXMyPortal::XDG::DATA_DIR, "payslips.yaml"),
153
103
  company:,
154
104
  user:,
155
105
  password:,
156
106
  test: false,
157
- payslip_dir: nil,
107
+ payslip_dir: PXMyPortal::XDG::DOC_DIR,
158
108
  bonus_only: false,
159
- debug_http: false)
109
+ debug_http: false,
110
+ force: false)
160
111
 
161
112
  @company = company
162
113
  @user = user
163
114
  @password = password
164
- @debug = debug
165
115
  @payslips_path = payslips_path
166
116
  @test = test
167
117
  @payslip_dir = payslip_dir
168
118
  @bonus_only = bonus_only
169
119
  @debug_http = debug_http
170
-
171
- @logger = Logger.new($stderr)
172
- @cookie = PXMyPortal::Cookie.new(jar_path: cookie_jar_path, logger: @logger)
173
- end
174
-
175
- def http
176
- return @http if @http
177
-
178
- http = Net::HTTP.new(PXMyPortal::HOST, Net::HTTP.https_default_port)
179
- http.use_ssl = true
180
- @http = http
120
+ @force = force
121
+
122
+ @logger = Logger.new($stderr, level: :info)
123
+ @logger.level = :debug if @debug
124
+
125
+ @http = PXMyPortal::HTTPClient.new(
126
+ debug: @debug_http,
127
+ logger: @logger,
128
+ )
129
+ @token_issuer = PXMyPortal::TokenIssuer.new(
130
+ http: @http,
131
+ company: @company,
132
+ )
181
133
  end
182
134
  end
135
+
136
+ require_relative "http_client"
137
+ require_relative "token_issuer"
138
+ require_relative "document_downloader"
139
+ require_relative "authentication"
140
+ require_relative "payslip_list"
@@ -0,0 +1,58 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "net/http"
17
+ require_relative "xdg"
18
+ require_relative "page"
19
+
20
+ class PXMyPortal::Authentication
21
+ def initialize(path:, user:, password:, token:, http:, logger:)
22
+ @path = path
23
+ @user = user
24
+ @password = password
25
+ @token = token
26
+ @http = http
27
+ @logger = logger
28
+
29
+ @request = Net::HTTP::Post.new(@path)
30
+ end
31
+
32
+ def post
33
+ @http.provide_cookie(@request)
34
+
35
+ @request.form_data = {
36
+ LoginId: @user,
37
+ Password: @password,
38
+ "__RequestVerificationToken" => @token,
39
+ }
40
+ response = @http.request(@request)
41
+ begin
42
+ response => Net::HTTPFound | Net::HTTPOK
43
+ rescue => e
44
+ File.write(File.join(PXMyPortal::XDG::CACHE_DIR, "authentication.html"),
45
+ response.body)
46
+ raise e
47
+ end
48
+
49
+ @logger.debug("response") { response.to_hash }
50
+ page = PXMyPortal::Page.from_path(response["location"] || @path)
51
+ unless page
52
+ @logger.error("location") { response["location"] }
53
+ raise PXMyPortal::Error, "unexpected location"
54
+ end
55
+ @http.accept_cookie(response)
56
+ page
57
+ end
58
+ end
@@ -1,15 +1,15 @@
1
1
  # Copyright (C) 2025 gemmaro
2
-
2
+ #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
5
5
  # the Free Software Foundation, either version 3 of the License, or
6
6
  # (at your option) any later version.
7
-
7
+ #
8
8
  # This program is distributed in the hope that it will be useful,
9
9
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
  # GNU General Public License for more details.
12
-
12
+ #
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
 
@@ -25,17 +25,17 @@ class PXMyPortal::Command
25
25
 
26
26
  parser = OptionParser.new
27
27
  parser.on("--debug") { options[:debug] = true }
28
- parser.on("--cookie-jar=PATH") { |path| options[:cookie_jar_path] = path }
28
+ parser.on("--cookie-jar=PATH") { warn "deprecated option" }
29
29
  parser.on("--payslips=PATH",
30
30
  "database file for previously stored payslips") { |path|
31
31
  options[:payslips_path] = path }
32
32
  parser.on("--payslip-dir=PATH") { |path| options[:payslip_dir] = path }
33
33
  parser.on("--bonus-only") { options[:bonus_only] = true }
34
34
  parser.on("--debug-http") { options[:debug_http] = true }
35
+ parser.on("--force") { options[:force] = true }
35
36
  parser.parse!
36
37
 
37
38
  agent = PXMyPortal::Agent.new(**options)
38
- agent.let_redirect
39
39
  agent.save_payslips
40
40
  end
41
41
  end
@@ -1,37 +1,56 @@
1
- require "http-cookie" # Use CGI::Cookie?
2
- require_relative "xdg"
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "set"
3
17
 
4
18
  class PXMyPortal::Cookie
5
- def initialize(jar_path: nil, logger:)
6
- @jar_path = jar_path || File.join(PXMyPortal::XDG::CACHE_DIR, "cookie-jar")
19
+ def initialize(logger:)
20
+ @set = Set.new
7
21
  @logger = logger
8
-
9
- @jar = HTTP::CookieJar.new
10
22
  end
11
23
 
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
24
+ def accept(response)
25
+ fields = Set[*response.get_fields("Set-Cookie"),
26
+ *response.get_fields("set-cookie")]
21
27
 
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)
28
+ fields.each do |field|
29
+ field.split(/; +/).each do |pair|
30
+ case pair
31
+ when "path=/",
32
+ "secure",
33
+ "HttpOnly",
34
+ "SameSite=Lax",
35
+ "selectedPage=pc",
36
+ /\Aexpires=/
37
+ next
38
+ end
39
+ key, = pair.split('=')
40
+ case key
41
+ when "ASP.NET_SessionId",
42
+ ".AspNet.ApplicationCookie",
43
+ "qs",
44
+ /\A__RequestVerificationToken_([A-Za-z0-9]+)/
45
+ else
46
+ raise PXMyPortal::Error, "unknown cookie entry #{pair.inspect}"
47
+ end
48
+ @set << pair
49
+ end
29
50
  end
30
- @created_jar_path = @jar_path
31
51
  end
32
52
 
33
- # Previously provide_cookie.
34
- def provide(request, url:)
35
- request["Cookie"] = HTTP::Cookie.cookie_value(@jar.cookies(url))
53
+ def provide(request)
54
+ request["Cookie"] = @set.join(';')
36
55
  end
37
56
  end
@@ -0,0 +1,39 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "net/http"
17
+
18
+ class PXMyPortal::DocumentDownloader
19
+ def initialize(path:, form_data:, logger:, http:)
20
+ @path = path
21
+ @form_data = form_data
22
+ @logger = logger
23
+ @http = http
24
+
25
+ @request = Net::HTTP::Post.new(@path)
26
+ end
27
+
28
+ def post
29
+ @http.provide_cookie(@request)
30
+ @request.form_data = @form_data
31
+ @logger.debug("request") { @request }
32
+
33
+ response = @http.request(@request)
34
+ response => Net::HTTPOK
35
+ @logger.debug("response") { response.to_hash }
36
+ response.to_hash["content-type"] => ["application/pdf"]
37
+ response.body
38
+ end
39
+ end
@@ -1 +1,16 @@
1
+ # Copyright (C) 2025 gemmaro
2
+
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
1
16
  PXMyPortal::Error = Class.new(StandardError)
@@ -0,0 +1,37 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "net/http"
17
+ require "forwardable"
18
+ require_relative "cookie"
19
+
20
+ class PXMyPortal::HTTPClient
21
+ def initialize(debug: false, logger:)
22
+ @http = Net::HTTP.new(PXMyPortal::HOST, Net::HTTP.https_default_port)
23
+ @http.use_ssl = true
24
+ @http.set_debug_output($stderr) if debug
25
+
26
+ @cookie = PXMyPortal::Cookie.new(logger:)
27
+ end
28
+
29
+ def start
30
+ @http.started? or @http.start
31
+ end
32
+
33
+ extend Forwardable
34
+ def_delegators :@http, :get, :request, :set_debug_output, :started?
35
+ def_delegator :@cookie, :provide, :provide_cookie
36
+ def_delegator :@cookie, :accept, :accept_cookie
37
+ end
@@ -1,3 +1,21 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "fileutils"
17
+ require "nokogiri"
18
+
1
19
  # Previously @phase.
2
20
  class PXMyPortal::Page
3
21
  BASEPATH = "/pmpwps/"
@@ -21,7 +39,7 @@ class PXMyPortal::Page
21
39
 
22
40
  def cache_path
23
41
  @cache_path and return @cache_path
24
- @cache_path = File.join(PXMyPortal::XDG::CACHE_DIR, "debug", "page", "#{@cache_filename}.html")
42
+ @cache_path = File.join(PXMyPortal::XDG::CACHE_DIR, "page", "#{@cache_filename}.html")
25
43
 
26
44
  dir = File.dirname(@cache_path)
27
45
  unless Dir.exist?(dir)
@@ -34,6 +52,10 @@ class PXMyPortal::Page
34
52
  Nokogiri::HTML(source).xpath(@row_xpath)
35
53
  end
36
54
 
55
+ def inspect
56
+ "#<Page #{@cache_filename}>"
57
+ end
58
+
37
59
  normal_row_xpath = "//*[@id='ContentPlaceHolder1_PayslipGridView']//tr"
38
60
 
39
61
  # Previously PAYSLIP_PAGE_PATH_SAMPLE.
@@ -42,13 +64,15 @@ class PXMyPortal::Page
42
64
  confirm_path: File.join(CLIENT_BASEPATH, "ConfirmSamplePDFFrame"),
43
65
  cache_filename: "sample", row_xpath: normal_row_xpath)
44
66
 
67
+ production_confirm_path = "ConfirmPDFFrame"
68
+
45
69
  # Previously PAYSLIP_PAGE_PATH_NORMAL.
46
70
  NORMAL = new(path: NORMAL_PATH,
47
- confirm_path: File.join(CLIENT_BASEPATH, "ConfirmPDFFrame"),
71
+ confirm_path: File.join(CLIENT_BASEPATH, production_confirm_path),
48
72
  cache_filename: "normal", row_xpath: normal_row_xpath)
49
73
 
50
74
  # Previously PAYSLIP_PAGE_PATH_BONUS.
51
- BONUS = new(path: BONUS_PATH, confirm_path: :TODO,
75
+ BONUS = new(path: BONUS_PATH, confirm_path: production_confirm_path,
52
76
  cache_filename: "bonus",
53
77
  row_xpath: "//*[@id='ContentPlaceHolder1_BonusPayslipGridView']//tr")
54
78
 
@@ -1,35 +1,33 @@
1
1
  # Copyright (C) 2025 gemmaro
2
-
2
+ #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
5
5
  # the Free Software Foundation, either version 3 of the License, or
6
6
  # (at your option) any later version.
7
-
7
+ #
8
8
  # This program is distributed in the hope that it will be useful,
9
9
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
  # GNU General Public License for more details.
12
-
12
+ #
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
16
  require_relative "xdg"
17
17
 
18
18
  class PXMyPortal::Payslip
19
- attr_reader :year_month, :description, :directory
19
+ attr_reader :year_month, :description
20
20
 
21
- def initialize(year_month:, description:, key1:, key2:, key3:,
22
- directory: PXMyPortal::XDG::DOC_DIR)
21
+ def initialize(year_month:, description:, key1:, key2:, key3:)
23
22
  @year_month = year_month
24
23
  @description = description
25
24
  @key1 = key1
26
25
  @key2 = key2
27
26
  @key3 = key3
28
- @directory = directory
29
27
  end
30
28
 
31
29
  def filename
32
- @filename ||= File.join(@directory, "#{@key1}-#{@key2}-#{@key3}.pdf")
30
+ @filename ||= "#{@key1}-#{@key2}-#{@key3}.pdf"
33
31
  end
34
32
 
35
33
  def form_data
@@ -51,7 +49,7 @@ class PXMyPortal::Payslip
51
49
  "#<Payslip #{year_month.inspect} #{description.inspect}>"
52
50
  end
53
51
 
54
- def self.from_row(row, directory: nil)
52
+ def self.from_row(row)
55
53
  row.xpath("./td") => [year_month, description, button]
56
54
  year_month.xpath(".//text()") => [year_month]
57
55
  description.xpath(".//text()") => [description]
@@ -66,12 +64,8 @@ class PXMyPortal::Payslip
66
64
  key2 = match[:key2]
67
65
  key3 = match[:key3]
68
66
 
69
- options = {}
70
- directory and options[:directory] = directory
71
-
72
67
  new(year_month: year_month.content,
73
68
  description: description.content,
74
- key1:, key2:, key3:,
75
- **options)
69
+ key1:, key2:, key3:)
76
70
  end
77
71
  end
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "net/http"
17
+
18
+ class PXMyPortal::PayslipList
19
+ def initialize(path:, logger:, http:)
20
+ @path = path
21
+ @logger = logger
22
+ @http = http
23
+
24
+ @request = Net::HTTP::Get.new(@path)
25
+ end
26
+
27
+ def get
28
+ @logger.debug("request") { @request }
29
+
30
+ @http.provide_cookie(@request)
31
+ response = @http.request(@request)
32
+ @logger.debug("response") { response }
33
+ response => Net::HTTPOK
34
+ response.body
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ require "net/http"
17
+ require "nokogiri"
18
+ require_relative "http_client"
19
+ require_relative "page"
20
+
21
+ class PXMyPortal::TokenIssuer
22
+ def initialize(http:, company:)
23
+ @http = http
24
+ @company = company
25
+ end
26
+
27
+ def get
28
+ @http.start
29
+
30
+ path = File.join(PXMyPortal::Page::BASEPATH, "Auth/Login")
31
+ query = @company
32
+ response = @http.get("#{path}?#{query}")
33
+ response => Net::HTTPOK
34
+
35
+ @http.accept_cookie(response)
36
+
37
+ document = Nokogiri::HTML(response.body)
38
+ token = <<~XPATH
39
+ //form//input[ @type='hidden'
40
+ and @name='__RequestVerificationToken' ]
41
+ /@value
42
+ XPATH
43
+ document.xpath(token) => [token]
44
+ token or raise Error, token
45
+ end
46
+ end
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Copyright (C) 2025 gemmaro
4
-
4
+ #
5
5
  # This program is free software: you can redistribute it and/or modify
6
6
  # it under the terms of the GNU General Public License as published by
7
7
  # the Free Software Foundation, either version 3 of the License, or
8
8
  # (at your option) any later version.
9
-
9
+ #
10
10
  # This program is distributed in the hope that it will be useful,
11
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
13
  # GNU General Public License for more details.
14
-
14
+ #
15
15
  # You should have received a copy of the GNU General Public License
16
16
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  module PXMyPortal
19
- VERSION = "0.0.3"
19
+ VERSION = "0.1.0"
20
20
  end
@@ -1,5 +1,22 @@
1
+ # Copyright (C) 2025 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
1
16
  class PXMyPortal::XDG
2
- CACHE_DIR = File.join(ENV["XDG_CACHE_HOME"], "pxmyportal")
17
+ CACHE_DIR = File.join(ENV["XDG_CACHE_HOME"] || File.join(Dir.home, ".cache"),
18
+ "pxmyportal")
3
19
  DOC_DIR = File.join(ENV["XDG_DOCUMENTS_DIR"] || File.join(Dir.home, "Documents"),
4
20
  "pxmyportal")
21
+ DATA_DIR = File.join(ENV["XDG_DATA_HOME"], "pxmyportal")
5
22
  end
data/lib/pxmyportal.rb CHANGED
@@ -1,15 +1,15 @@
1
1
  # Copyright (C) 2025 gemmaro
2
-
2
+ #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
5
5
  # the Free Software Foundation, either version 3 of the License, or
6
6
  # (at your option) any later version.
7
-
7
+ #
8
8
  # This program is distributed in the hope that it will be useful,
9
9
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
  # GNU General Public License for more details.
12
-
12
+ #
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
 
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pxmyportal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gemmaro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-19 00:00:00.000000000 Z
11
+ date: 2025-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: http-cookie
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: nokogiri
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -54,17 +40,29 @@ files:
54
40
  - exe/pxmyportal
55
41
  - lib/pxmyportal.rb
56
42
  - lib/pxmyportal/agent.rb
43
+ - lib/pxmyportal/authentication.rb
57
44
  - lib/pxmyportal/command.rb
58
45
  - lib/pxmyportal/cookie.rb
46
+ - lib/pxmyportal/document_downloader.rb
59
47
  - lib/pxmyportal/error.rb
48
+ - lib/pxmyportal/http_client.rb
60
49
  - lib/pxmyportal/page.rb
61
50
  - lib/pxmyportal/payslip.rb
51
+ - lib/pxmyportal/payslip_list.rb
52
+ - lib/pxmyportal/token_issuer.rb
62
53
  - lib/pxmyportal/version.rb
63
54
  - lib/pxmyportal/xdg.rb
64
- homepage:
55
+ homepage: https://github.com/gemmaro/pxmyportal
65
56
  licenses:
66
57
  - GPL-3.0-or-later
67
- metadata: {}
58
+ metadata:
59
+ rubygems_mfa_required: 'true'
60
+ bug_tracker_uri: https://github.com/gemmaro/pxmyportal/issues
61
+ changelog_uri: https://github.com/gemmaro/pxmyportal/blob/main/CHANGELOG.md
62
+ documentation_uri: https://rubydoc.info/gems/pxmyportal
63
+ homepage_uri: https://github.com/gemmaro/pxmyportal
64
+ source_code_uri: https://github.com/gemmaro/pxmyportal
65
+ wiki_uri: https://github.com/gemmaro/pxmyportal/wiki
68
66
  post_install_message:
69
67
  rdoc_options: []
70
68
  require_paths: