coelacanth 0.5.1 → 0.7.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: f6e9c7c1d9049b351076d0238e2b5747144b73244e3f0c37d2ce69fb625b823a
4
- data.tar.gz: d5ef6a678e876b1fabfffd68426785ba6bb4174178e323adbe9593bf9c6ff57a
3
+ metadata.gz: f1bc84d97135234e0e80586a789381ea2e10785c66fac0bebe21375205489ef3
4
+ data.tar.gz: fdbea80d5c55c1059aedc568ec593121517cf0cbcf55d145fbe123c121b66c2a
5
5
  SHA512:
6
- metadata.gz: 5f9c343b0ddb3a58a78af386715c8c99d905e2161905684438972fc177c8943d2c9a51ebadeffb9393162d83e914436ea134e5c78a3affe8db379f3a65fc3a65
7
- data.tar.gz: cc6fda69757f79b5a53de36f0ead0315cf1598a4cc372f4e8d2c71cd021c1cf7370aacb583111cdaf0e5bcd711eb445c2d7c673705578217599bd94e7b1fc2ec
6
+ metadata.gz: 37165c8087b6119d7cd9ce3517bfd12b1c5f52c8c27b7c4eab12351f47034d46b2e86c7fe72820e6b425e02e7f643494e74766995805d1052469fdfaec7f47f2
7
+ data.tar.gz: d45c1d607c7e35f938ca6fc3658ec082020fcdcfb6888c2a60a7e080e508c7430eedcf9f7eddcf78dad2320f3dfae2803de975d8de9f7cbfb66282944547c5f1
data/CHANGELOG.md CHANGED
@@ -4,8 +4,8 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [v0.5.1] - 2025-11-08
8
- ### :sparkles: New Features
9
- - [`726419e`](https://github.com/slidict/coelacanth/commit/726419e6b5fcb6b887d3881532328fdab25e3f0d) - expose standalone morphological analysis *(commit by [@yubele](https://github.com/yubele))*
7
+ ## [v0.7.0] - 2026-07-08
8
+ ### :wrench: Chores
9
+ - [`ab6d634`](https://github.com/slidict/coelacanth/commit/ab6d634abbc5f954b4b9861e19e3420ee034fd77) - bump version to 0.7.0 *(commit by [@abechan1](https://github.com/abechan1))*
10
10
 
11
- [v0.5.1]: https://github.com/slidict/coelacanth/compare/v0.5.0...v0.5.1
11
+ [v0.7.0]: https://github.com/slidict/coelacanth/compare/v0.6.1...v0.7.0
data/README.md CHANGED
@@ -32,6 +32,7 @@ it into contemporary ingestion stacks without bespoke glue code.
32
32
  multi-byte sources.
33
33
  - **Screenshot capture** – Fetches full-page PNGs via a configurable browser client so you can archive visual context alongside
34
34
  the extracted text.
35
+ - **PDF generation** – Renders any URL to PDF through Ferrum so you can persist printer-friendly snapshots of source pages.
35
36
  - **Redirect resolution** – Follows HTTP redirects and long redirect chains to guarantee the extractor works on the final
36
37
  landing page.
37
38
  - **Configurable HTTP headers** – Inject custom headers (user agent, authorization, etc.) into the remote browser session for
@@ -83,6 +84,10 @@ result[:dom] # => Oga DOM representation for downstream processing
83
84
  result[:screenshot] # => PNG screenshot as a binary string
84
85
  result[:response] # => HTTP status, headers, and final URL
85
86
 
87
+ pdf = Coelacanth.pdf("https://example.com/article")
88
+ File.binwrite("article.pdf", pdf)
89
+ ```
90
+
86
91
  # Plain-text morphology
87
92
 
88
93
  You can run the morphological analyzer without fetching a page by passing plain
@@ -96,7 +101,6 @@ Coelacanth.morphological_analysis("これはテストです。 Testing morpholog
96
101
  # ...
97
102
  # ]
98
103
  ```
99
- ```
100
104
 
101
105
  The returned hash includes:
102
106
 
@@ -143,7 +147,7 @@ Runtime configuration is stored in `config/coelacanth.yml`. Environments inherit
143
147
 
144
148
  ```yaml
145
149
  development:
146
- client: "ferrum" # Options: "ferrum", "screenshot_one"
150
+ client: "ferrum" # Options: "ferrum", "screenshot_one", "gotenberg"
147
151
  remote_client:
148
152
  ws_url: "ws://chrome:3000/chrome"
149
153
  timeout: 10
@@ -155,6 +159,13 @@ development:
155
159
  User-Agent: "<%= ENV.fetch("COELACANTH_REMOTE_CLIENT_USER_AGENT", "Coelacanth Chrome Extension") %>"
156
160
  screenshot_one:
157
161
  key: "<%= ENV.fetch("COELACANTH_SCREENSHOT_ONE_API_KEY", "your_screenshot_one_api_key_here") %>"
162
+ gotenberg:
163
+ url: "<%= ENV.fetch("COELACANTH_GOTENBERG_URL", "http://gotenberg:3000") %>"
164
+ open_timeout: 5
165
+ read_timeout: 30
166
+ wait_delay: "<%= ENV.fetch("COELACANTH_GOTENBERG_WAIT_DELAY", "") %>"
167
+ user_agent: "<%= ENV.fetch("COELACANTH_GOTENBERG_USER_AGENT", "") %>"
168
+ extra_http_headers:
158
169
  youtube:
159
170
  api_key: "<%= ENV.fetch("COELACANTH_YOUTUBE_API_KEY", "") %>"
160
171
  morphology:
@@ -171,6 +182,11 @@ development:
171
182
  - **Ferrum client** – Requires a running Chrome instance that exposes the DevTools protocol via WebSocket. Configure the URL,
172
183
  timeout, the network idle timeout, and any headers to inject.
173
184
  - **ScreenshotOne client** – Supply an API key to offload screenshot capture to [ScreenshotOne](https://screenshotone.com/).
185
+ - **Gotenberg client** – Set `client: "gotenberg"` to capture screenshots through Gotenberg's Chromium URL screenshot
186
+ endpoint. Configure `gotenberg.url`, request timeouts, an optional `wait_delay`, an optional screenshot `user_agent`,
187
+ and optional `extra_http_headers`.
188
+ - **PDF generation** – `Coelacanth.pdf(url)` renders the URL with the Ferrum client and returns the generated PDF as a binary
189
+ string. Configure `remote_client` with a Chrome-compatible WebSocket endpoint before using this helper.
174
190
  - **Eyecatch image extraction** – Representative images are discovered automatically by checking Open Graph/Twitter metadata,
175
191
  Schema.org JSON-LD payloads, and high-signal `<img>` elements (hero/cover images, large dimensions, etc.). No manual XPath
176
192
  maintenance is required.
@@ -207,6 +223,9 @@ export COELACANTH_REMOTE_CLIENT_AUTHORIZATION="Bearer <token>"
207
223
 
208
224
  export COELACANTH_REMOTE_CLIENT_USER_AGENT="Coelacanth Chrome Extension"
209
225
  export COELACANTH_SCREENSHOT_ONE_API_KEY="your_screenshot_one_api_key_here"
226
+ export COELACANTH_GOTENBERG_URL="http://gotenberg:3000"
227
+ export COELACANTH_GOTENBERG_WAIT_DELAY="2s"
228
+ export COELACANTH_GOTENBERG_USER_AGENT="Coelacanth Chrome Extension"
210
229
  export COELACANTH_YOUTUBE_API_KEY="your_youtube_data_api_key"
211
230
  ```
212
231
 
@@ -230,6 +249,10 @@ YouTube, so non-video pages continue to behave as before.
230
249
  When using Docker Compose, you can create a `.env` file or export the variables in your environment so the `app` service picks
231
250
  them up automatically.
232
251
 
252
+ Docker Compose also starts a `gotenberg` service and passes `COELACANTH_GOTENBERG_URL` to the `app` service by default.
253
+ To try that path locally, set `client: "gotenberg"` in `config/coelacanth.yml` or export the equivalent environment-specific
254
+ configuration before running the app container.
255
+
233
256
  If you are working inside Docker, make sure the `UID` environment variable matches your host user by exporting it in your shell
234
257
  startup file:
235
258
 
data/compose.yml CHANGED
@@ -1,25 +1,34 @@
1
- networks:
2
- app-tier:
3
- driver: bridge
4
- services:
5
- app:
1
+ networks:
2
+ app-tier:
3
+ driver: bridge
4
+ services:
5
+ app:
6
6
  environment:
7
7
  - UID=${UID}
8
8
  - COELACANTH_REMOTE_CLIENT_AUTHORIZATION=${COELACANTH_REMOTE_CLIENT_AUTHORIZATION:-}
9
9
  - COELACANTH_REMOTE_CLIENT_USER_AGENT=${COELACANTH_REMOTE_CLIENT_USER_AGENT:-}
10
10
  - COELACANTH_SCREENSHOT_ONE_API_KEY=${COELACANTH_SCREENSHOT_ONE_API_KEY:-}
11
- tty: true
12
- stdin_open: true
13
- build:
14
- context: .
15
- dockerfile: Dockerfile
16
- volumes:
17
- - ./:/app:cached
18
- working_dir: /app
19
- command: bash
20
- networks:
21
- - app-tier
22
- chrome:
23
- image: browserless/chrome:latest
24
- networks:
25
- - app-tier
11
+ - COELACANTH_GOTENBERG_URL=${COELACANTH_GOTENBERG_URL:-http://gotenberg:3000}
12
+ - COELACANTH_GOTENBERG_WAIT_DELAY=${COELACANTH_GOTENBERG_WAIT_DELAY:-}
13
+ - COELACANTH_GOTENBERG_USER_AGENT=${COELACANTH_GOTENBERG_USER_AGENT:-}
14
+ tty: true
15
+ stdin_open: true
16
+ build:
17
+ context: .
18
+ dockerfile: Dockerfile
19
+ volumes:
20
+ - ./:/app:cached
21
+ working_dir: /app
22
+ command: bash
23
+ depends_on:
24
+ - gotenberg
25
+ networks:
26
+ - app-tier
27
+ chrome:
28
+ image: browserless/chrome:latest
29
+ networks:
30
+ - app-tier
31
+ gotenberg:
32
+ image: gotenberg/gotenberg:8
33
+ networks:
34
+ - app-tier
@@ -1,5 +1,5 @@
1
1
  development: &development
2
- client: "ferrum" # Options: "ferrum", "screenshot_one"
2
+ client: "ferrum" # Options: "ferrum", "screenshot_one", "gotenberg"
3
3
  remote_client:
4
4
  ws_url: "ws://chrome:3000/chrome"
5
5
  timeout: 10 # seconds
@@ -11,6 +11,13 @@ development: &development
11
11
  User-Agent: "<%= ENV.fetch("COELACANTH_REMOTE_CLIENT_USER_AGENT", "Coelacanth Chrome Extension") %>"
12
12
  screenshot_one:
13
13
  key: "<%= ENV.fetch("COELACANTH_SCREENSHOT_ONE_API_KEY", "your_screenshot_one_api_key_here") %>"
14
+ gotenberg:
15
+ url: "<%= ENV.fetch("COELACANTH_GOTENBERG_URL", "http://gotenberg:3000") %>"
16
+ open_timeout: 5
17
+ read_timeout: 30
18
+ wait_delay: "<%= ENV.fetch("COELACANTH_GOTENBERG_WAIT_DELAY", "") %>"
19
+ user_agent: "<%= ENV.fetch("COELACANTH_GOTENBERG_USER_AGENT", "") %>"
20
+ extra_http_headers:
14
21
  youtube:
15
22
  api_key: "<%= ENV.fetch("COELACANTH_YOUTUBE_API_KEY", "") %>"
16
23
  morphology:
@@ -23,5 +23,9 @@ module Coelacanth::Client
23
23
  def get_screenshot
24
24
  raise "Must be implemented in subclass"
25
25
  end
26
+
27
+ def get_pdf
28
+ raise "Must be implemented in subclass"
29
+ end
26
30
  end
27
31
  end
@@ -5,8 +5,8 @@ require "ferrum"
5
5
  module Coelacanth::Client
6
6
  # Coelacanth::Client
7
7
  class Ferrum < Coelacanth::Client::Base
8
- def initialize(url)
9
- super(url)
8
+ def initialize(url, config = Coelacanth.config)
9
+ super(url, config)
10
10
  remote_client.goto(url)
11
11
  end
12
12
 
@@ -29,6 +29,13 @@ module Coelacanth::Client
29
29
  raise sanitized_remote_client_error(e)
30
30
  end
31
31
 
32
+ def get_pdf
33
+ wait_for_network_idle
34
+ remote_client.pdf
35
+ rescue => e
36
+ raise sanitized_remote_client_error(e)
37
+ end
38
+
32
39
  private
33
40
 
34
41
  def sanitized_remote_client_error(error)
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ require_relative "../http"
8
+
9
+ module Coelacanth::Client
10
+ # Client for capturing screenshots through a Gotenberg Chromium service.
11
+ class Gotenberg < Coelacanth::Client::Base
12
+ SCREENSHOT_URL_PATH = "/forms/chromium/screenshot/url"
13
+
14
+ def get_response
15
+ uri = URI.parse(@url)
16
+ response = Coelacanth::HTTP.get_response(
17
+ uri,
18
+ open_timeout: Coelacanth::HTTP::DEFAULT_OPEN_TIMEOUT,
19
+ read_timeout: Coelacanth::HTTP::DEFAULT_READ_TIMEOUT
20
+ )
21
+ @origin_response = response
22
+ @status_code = response.code.to_i
23
+
24
+ return response.body if response.is_a?(Net::HTTPSuccess)
25
+
26
+ Coelacanth::HTTP.raise_http_error(uri, response)
27
+ end
28
+
29
+ def get_screenshot
30
+ response = Net::HTTP.start(
31
+ endpoint_uri.host,
32
+ endpoint_uri.port,
33
+ use_ssl: endpoint_uri.scheme == "https",
34
+ open_timeout: open_timeout,
35
+ read_timeout: read_timeout
36
+ ) do |http|
37
+ http.request(screenshot_request)
38
+ end
39
+
40
+ return response.body if response.is_a?(Net::HTTPSuccess)
41
+
42
+ raise "Failed to fetch screenshot from Gotenberg: #{response.code} #{response.message}"
43
+ rescue Net::OpenTimeout, Net::ReadTimeout, Timeout::Error => e
44
+ raise Coelacanth::TimeoutError, "Gotenberg screenshot request timed out: #{e.message}"
45
+ end
46
+
47
+ private
48
+
49
+ def screenshot_request
50
+ request = Net::HTTP::Post.new(endpoint_uri)
51
+ request.set_form(gotenberg_form_fields, "multipart/form-data")
52
+ request
53
+ end
54
+
55
+ def gotenberg_form_fields
56
+ fields = [["url", @url]]
57
+ wait_delay = @config.read("gotenberg.wait_delay")
58
+ user_agent = @config.read("gotenberg.user_agent")
59
+ extra_http_headers = @config.read("gotenberg.extra_http_headers")
60
+
61
+ fields << ["waitDelay", wait_delay] if present?(wait_delay)
62
+ fields << ["userAgent", user_agent] if present?(user_agent)
63
+ if extra_http_headers && extra_http_headers.any?
64
+ fields << ["extraHttpHeaders", extra_http_headers.to_json]
65
+ end
66
+ fields
67
+ end
68
+
69
+ def endpoint_uri
70
+ @endpoint_uri ||= URI.join(base_url, SCREENSHOT_URL_PATH)
71
+ end
72
+
73
+ def base_url
74
+ @config.read("gotenberg.url") || "http://localhost:3000"
75
+ end
76
+
77
+ def open_timeout
78
+ @config.read("gotenberg.open_timeout") || Coelacanth::HTTP::DEFAULT_OPEN_TIMEOUT
79
+ end
80
+
81
+ def read_timeout
82
+ @config.read("gotenberg.read_timeout") || 30
83
+ end
84
+
85
+ def present?(value)
86
+ !value.nil? && value.to_s.strip != ""
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coelacanth
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/coelacanth.rb CHANGED
@@ -4,6 +4,7 @@ require "net/http"
4
4
  require_relative "coelacanth/configure"
5
5
  require_relative "coelacanth/client/base"
6
6
  require_relative "coelacanth/client/ferrum"
7
+ require_relative "coelacanth/client/gotenberg"
7
8
  require_relative "coelacanth/client/screenshot_one"
8
9
  require_relative "coelacanth/dom"
9
10
  require_relative "coelacanth/extractor"
@@ -21,7 +22,7 @@ module Coelacanth
21
22
  class RobotsDisallowedError < StandardError; end
22
23
 
23
24
  def self.analyze(url)
24
- client_class = config.read("client") == "screenshot_one" ? Client::ScreenshotOne : Client::Ferrum
25
+ client_class = client_class_for(config.read("client"))
25
26
  @client = client_class.new(url)
26
27
  regular_url = Redirect.new.resolve_redirect(url)
27
28
  response = begin
@@ -47,6 +48,21 @@ module Coelacanth
47
48
  }
48
49
  end
49
50
 
51
+ def self.pdf(url)
52
+ Client::Ferrum.new(url).get_pdf
53
+ end
54
+
55
+ def self.client_class_for(client_name)
56
+ case client_name
57
+ when "screenshot_one"
58
+ Client::ScreenshotOne
59
+ when "gotenberg"
60
+ Client::Gotenberg
61
+ else
62
+ Client::Ferrum
63
+ end
64
+ end
65
+
50
66
  def self.config
51
67
  @config ||= Configure.new
52
68
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coelacanth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke
@@ -33,6 +33,7 @@ files:
33
33
  - lib/coelacanth.rb
34
34
  - lib/coelacanth/client/base.rb
35
35
  - lib/coelacanth/client/ferrum.rb
36
+ - lib/coelacanth/client/gotenberg.rb
36
37
  - lib/coelacanth/client/screenshot_one.rb
37
38
  - lib/coelacanth/configure.rb
38
39
  - lib/coelacanth/dom.rb