@mash43/relnext 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mash43
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # @mash43/relnext
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@mash43/relnext.svg)](https://www.npmjs.com/package/@mash43/relnext) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
4
+
5
+ `@mash43/relnext` is a TypeScript library designed to detect pagination links such as "next" and "previous" from web page HTML content or URLs.
6
+
7
+ ## Features
8
+
9
+ - **Diverse Detection Methods**: Combines multiple strategies to detect links, including `rel="next"` attribute, text content (e.g., "Next"), CSS class names, and `aria-label` attributes.
10
+ - **URL Pattern Inference**: Infers the URL of the next or previous page from query parameters like `page=2` or path segments like `/page/2`.
11
+ - **HTML Fetching Capability**: Includes built-in helper functions to directly fetch HTML content from a specified URL.
12
+ - **Flexible Configuration**: Allows customization of behavior, such as changing the order of search methods and setting timeouts.
13
+
14
+ ## Installation
15
+
16
+ You can install it using npm.
17
+
18
+ ```bash
19
+ npm install @mash43/relnext
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Finding the next page from HTML content
25
+
26
+ ```typescript
27
+ import { findNext } from "@mash43/relnext";
28
+
29
+ const baseUrl = "https://example.com";
30
+ const html = `
31
+ <html>
32
+ <body>
33
+ <div class="pagination">
34
+ <span>Page 1</span>
35
+ <a href="/page/2">Next</a>
36
+ </div>
37
+ </body>
38
+ </html>
39
+ `;
40
+
41
+ const nextLink = findNext(html, baseUrl);
42
+
43
+ if (nextLink) {
44
+ console.log(`URL of the next page: ${nextLink}`);
45
+ // Example output: URL of the next page: https://example.com/page/2
46
+ } else {
47
+ console.log("Next page not found.");
48
+ }
49
+ ```
50
+
51
+ ### Inferring the next page from URL patterns
52
+
53
+ ```typescript
54
+ import { findNextByUrl } from "@mash43/relnext";
55
+
56
+ const currentUrl = "https://example.com/articles?page=3";
57
+ const nextUrl = await findNextByUrl(currentUrl);
58
+
59
+ if (nextUrl) {
60
+ console.log(`URL of the next page: ${nextUrl}`);
61
+ // Example output: URL of the next page: https://example.com/articles?page=4
62
+ } else {
63
+ console.log("Next page not found.");
64
+ }
65
+ ```
66
+
67
+ ## API
68
+
69
+ #### `findNext(html, baseUrl, options?)`
70
+
71
+ Finds the URL of the "next" page from an HTML string.
72
+
73
+ - `html`: (string) The HTML content to parse.
74
+ - `baseUrl`: (string) The base URL to resolve relative URLs.
75
+ - `options`: (FindNextOptions) Search options.
76
+
77
+ #### `findPrev(html, baseUrl, options?)`
78
+
79
+ Finds the URL of the "previous" page from an HTML string.
80
+
81
+ - `html`: (string) The HTML content to parse.
82
+ - `baseUrl`: (string) The base URL to resolve relative URLs.
83
+ - `options`: (FindNextOptions) Search options.
84
+
85
+ #### `findNextByUrl(url, options?)`
86
+
87
+ Analyzes URL query parameters (e.g., `?page=2`) and path segments (e.g., `/page/2`) to infer the URL of the "next" page.
88
+
89
+ This function is **asynchronous** because it performs a network request (`HEAD`) to verify that the inferred URL actually exists.
90
+
91
+ - `url`: (string) The URL of the current page.
92
+ - `options`: (BaseOptions) Options. The `timeout` option can be used to set the timeout for the URL existence check.
93
+
94
+ #### `findPrevByUrl(url, options?)`
95
+
96
+ Analyzes URL query parameters and paths to infer the URL of the "previous" page.
97
+
98
+ This function is **asynchronous**. See `findNextByUrl` for details.
99
+
100
+ - `url`: (string) The URL of the current page.
101
+ - `options`: (BaseOptions) Options. The `timeout` option can be used to set the timeout for the URL existence check.
102
+
103
+ #### `fetchHtml(url, options?)`
104
+
105
+ Asynchronously fetches HTML content from the specified URL.
106
+
107
+ - `url`: (string) The URL to fetch from.
108
+ - `options`: (BaseOptions) Options.
109
+
110
+ ---
111
+
112
+ ### Options
113
+
114
+ Options that can be passed to functions like `findNext` and `findPrev`.
115
+
116
+ ##### `BaseOptions`
117
+
118
+ The base interface for all option objects.
119
+
120
+ | Property | Type | Description |
121
+ | ---------- | -------------------------- | ------------------------------------------------------------------------------ |
122
+ | `logger` | `(level, message) => void` | A logger function for recording internal warnings and errors. |
123
+ | `timeout` | `number` | Timeout in milliseconds for `fetchHtml` and URL existence checks. Default is `8000`. |
124
+ | `verifyExists` | `boolean` | Controls whether `findNextByUrl` and `findPrevByUrl` perform a HEAD request to verify the existence of inferred URLs. Setting to `false` skips verification, potentially improving performance, but may return non-existent URLs. Defaults to `true`. |
125
+
126
+ ##### `FindNextOptions`
127
+
128
+ Inherits from `BaseOptions`.
129
+
130
+ | Property | Type | Description |
131
+ | ---------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
132
+ | `methods` | `Method[]` | An array and order of strategies to use for searching. Default is `["rel", "pagination", "text", "className", "aria-label", "alt"]`. |
133
+ | `classNameRegex` | `RegExp` | Custom regular expression to use when searching for links with the `className` method. |
134
+
135
+ ##### `Method` Type
136
+
137
+ `"rel" | "pagination" | "text" | "className" | "aria-label" | "alt"`
138
+
139
+ ## Search Strategies
140
+
141
+ `findNext` and `findPrev` search for links in the following order by default. This order can be customized with `options.methods`.
142
+
143
+ 1. **`rel`**: Searches for `<link rel="next" href="...">` or `<a rel="next" href="...">`.
144
+ 2. **`pagination`**: Searches for links adjacent to pagination components (`<li>` or `<span>`) with `.current` or `.active` classes.
145
+ 3. **`text`**: Searches for anchor tags with text content such as "Next", or ">".
146
+ 4. **`className`**: Searches for anchor tags with class names or IDs like `next`.
147
+ 5. **`aria-label`**: Searches for anchor tags with `aria-label` attributes like "Next".
148
+ 6. **`alt`**: Searches for anchor tags containing images with `alt` attributes including "Next".
149
+
150
+ ## License
151
+
152
+ [MIT](./LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const e={REL:{next:/rel\s*=\s*(['"])[^'"]*?\bnext\b[^'"]*?\1/i,prev:/rel\s*=\s*(['"])[^'"]*?\b(prev|previous)\b[^'"]*?\1/i},TEXT:{next:/^\s*(((Next)\s*(page)?|older|forward)|((次|つぎ)(のページ)?(へ)?)|((下|后)\s*(一)?(页|頁))|(다음)|»|>|→)\s*>*$/i,prev:/^\s*<*(((Prev|Previous)\s*(page)?|older|back)|((前)(のページ)?(へ)?)|((上|前)\s*(一)?(页|頁))|(이전)|«|<|←)\s*$/i},CLASS_NAME:{next:/next/i,prev:/prev|previous/i},PAGINATION_LI:{next:/<li[^>]+class\s*=\s*['"][^'"]*(?:current|active)[^'"]*['"][^>]*>.*?<\/li>\s*<li[^>]*>\s*<a\s+(?<attributes>[^>]+)>/is,prev:/<li[^>]*>\s*<a\s+(?<attributes>[^>]+)>.*?<\/a>\s*<\/li>\s*<li[^>]+class\s*=\s*['"][^'"]*(?:current|active)[^'"]*['"][^>]*>/is},PAGINATION_FALLBACK:{next:/(?:<(?:span|a)[^>]+(?:class\s*=\s*['"](?:current|active)['"]|aria-current\s*=\s*['"]page['"])|strong)\s*[^<]*\s*<\/(?:span|a|strong)>\s*<a\s+(?<attributes>[^>]+)>/i,prev:/<a\s+(?<attributes>[^>]+)>.*?<\/a>\s*(?:<(?:span|a)[^>]+(?:class\s*=\s*['"](?:current|active)['"]|aria-current\s*=\s*['"]page['"])|strong)\s*[^<]*/i},POTENTIAL_LINK_TAGS:/<(?:a|link)\s+(?<attributes>[^>]*?)>/gi,ANCHOR_TAG:/<a\s+(?<attributes>[^>]+)>(?<innerText>.*?)<\/a>/gis,ANCHOR_TAG_START:/<a\s+(?<attributes>[^>]+)>/gi,PAGINATION_CONTAINER:/<(?:div|nav|ul)[^>]+(?:class|id)\s*=\s*['"][^'"]*(?:pagination|pager|page-nav)[^'"]*['"][^>]*>(?<containerHtml>[\s\S]*?)<\/(?:div|nav|ul)>/gi,IMG_TAG:/<img[^>]+>/gi,PATH_PAGE_NUMBER:/^(.*[/\-_])(\d+)$/,HTML_TAGS:/<[^>]+>/g,HTML_ENTITIES:/&[a-z]+;|&#[0-9]+;|&#x[0-9a-f]+;/gi},t=8e3,n=new Map;function r(e,t){let r=n.get(t);return r||(r=RegExp(`${t}\\s*=\\s*(['"])(?<value>[^"']*)\\1`,`i`),n.set(t,r)),e.match(r)?.groups?.value??null}function i(e,t,n){let i=r(e,`href`);if(i)try{return new URL(i,t).href}catch(e){let r=e instanceof Error?e.message:String(e);return n?.logger?.(`warn`,`Invalid URL '${i}' for base '${t}': ${r}`),null}return null}async function a(e,n){let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??t);try{let t=await fetch(e,{signal:r.signal});if(!t.ok)return n?.logger?.(`warn`,`Failed to fetch ${e}: ${t.status} ${t.statusText}`),null;let i=t.headers.get(`content-type`);return!i||!i.includes(`text/html`)?(n?.logger?.(`warn`,`URL ${e} did not return HTML content.`),null):await t.text()}catch(t){let r=t instanceof Error?t.message:String(t);return n?.logger?.(`error`,`Error fetching or parsing ${e}: ${r}`),null}finally{clearTimeout(i)}}function o(t,n,r,a){let o=t.matchAll(e.POTENTIAL_LINK_TAGS);for(let e of o){let t=e.groups?.attributes;if(t&&r.test(t)){let e=i(t,n,a);if(e)return e}}return null}function s(t,n,r,a,o){let s=t.matchAll(e.PAGINATION_CONTAINER);for(let e of s){let t=e.groups?.containerHtml;if(!t)continue;let s=t.match(r);if(s?.groups?.attributes){let e=i(s.groups.attributes,n,o);if(e)return e}let c=t.match(a);if(c?.groups?.attributes){let e=i(c.groups.attributes,n,o);if(e)return e}}return null}function c(t,n,r,a){let o=t.matchAll(e.ANCHOR_TAG);for(let t of o){let o=t.groups?.attributes,s=t.groups?.innerText;if(!o||!s)continue;let c=s.replace(e.HTML_TAGS,``).replace(e.HTML_ENTITIES,``).trim();if(c&&r.test(c)){let e=i(o,n,a);if(e)return e}}return null}function l(t,n,a,o,s){let c=t.matchAll(e.ANCHOR_TAG_START);for(let e of c){let t=e.groups?.attributes;if(!t)continue;let c=r(t,`class`),l=r(t,`id`);if(c&&(o??a).test(c)||l&&a.test(l)){let e=i(t,n,s);if(e)return e}}return null}function u(t,n,a,o){let s=t.matchAll(e.ANCHOR_TAG_START);for(let e of s){let t=e.groups?.attributes;if(!t)continue;let s=r(t,`aria-label`);if(s&&a.test(s)){let e=i(t,n,o);if(e)return e}}return null}function d(t,n,a,o){let s=t.matchAll(e.ANCHOR_TAG);for(let t of s){let s=t.groups?.attributes,c=t.groups?.innerText;if(!s||!c)continue;let l=c.matchAll(e.IMG_TAG);for(let[e]of l){let t=r(e,`alt`);if(t&&a.test(t.trim())){let e=i(s,n,o);if(e)return e}}}return null}function f(t,n,r,i){let a=i?.methods??[`rel`,`pagination`,`text`,`className`,`aria-label`,`alt`],f={rel:()=>o(t,n,e.REL[r],i),pagination:()=>s(t,n,e.PAGINATION_LI[r],e.PAGINATION_FALLBACK[r],i),text:()=>c(t,n,e.TEXT[r],i),className:()=>l(t,n,e.CLASS_NAME[r],i?.classNameRegex,i),"aria-label":()=>u(t,n,e.TEXT[r],i),alt:()=>d(t,n,e.TEXT[r],i)};for(let e of a){let t=f[e]();if(t)return t}return null}function p(e,t,n){return f(e,t,`next`,n)}function m(e,t,n){return f(e,t,`prev`,n)}async function h(e,n){let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??t);try{return(await fetch(e,{method:`HEAD`,signal:r.signal})).ok}catch{return!1}finally{clearTimeout(i)}}async function g(e,t,n){try{let r=new URL(e),i=r.searchParams,a=null,o=null;for(let e of[`page`,`p`,`index`]){let t=i.get(e);if(t){let n=parseInt(t,10);if(!Number.isNaN(n)){a=e,o=n;break}}}if(a&&o!==null){let e=t===`next`?o+1:o-1;if(e>0){i.set(a,String(e));let t=r.toString();if(n?.verifyExists===!1||await h(t,n))return t}}}catch(e){let t=e instanceof Error?e.message:String(e);n?.logger?.(`warn`,`Invalid URL provided to findUrlByQueryParam: ${t}`)}return null}async function _(t,n,r){try{let i=new URL(t),a=i.pathname.replace(/\/$/,``).match(e.PATH_PAGE_NUMBER);if(a){let[e,t,o]=a;if(t&&o){let e=parseInt(o,10),a=n===`next`?e+1:e-1;if(a>0){let e=`${t}${a}`,n=`${i.origin}${e}${i.search}${i.hash}`;if(r?.verifyExists===!1||await h(n,r))return n}}}}catch(e){let t=e instanceof Error?e.message:String(e);r?.logger?.(`warn`,`Invalid URL provided to findUrlByPathSegment: ${t}`)}return null}async function v(e,t,n){return await g(e,t,n)||await _(e,t,n)||null}function y(e,t){return v(e,`next`,t)}function b(e,t){return v(e,`prev`,t)}exports.fetchHtml=a,exports.findNext=p,exports.findNextByUrl=y,exports.findPrev=m,exports.findPrevByUrl=b;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["// src/index.ts\n\nexport interface BaseOptions {\n\tlogger?: (level: \"warn\" | \"error\", message: string) => void;\n\ttimeout?: number;\n\tverifyExists?: boolean;\n}\n\nexport type Method =\n\t| \"rel\"\n\t| \"pagination\"\n\t| \"text\"\n\t| \"className\"\n\t| \"aria-label\"\n\t| \"alt\";\n\nexport interface FindNextOptions extends BaseOptions {\n\tmethods?: Method[];\n\tclassNameRegex?: RegExp;\n}\n\ntype Direction = \"next\" | \"prev\";\n\nconst REGEX = {\n\tREL: {\n\t\tnext: /rel\\s*=\\s*(['\"])[^'\"]*?\\bnext\\b[^'\"]*?\\1/i,\n\t\tprev: /rel\\s*=\\s*(['\"])[^'\"]*?\\b(prev|previous)\\b[^'\"]*?\\1/i,\n\t},\n\tTEXT: {\n\t\tnext: /^\\s*(((Next)\\s*(page)?|older|forward)|((次|つぎ)(のページ)?(へ)?)|((下|后)\\s*(一)?(页|頁))|(다음)|»|>|→)\\s*>*$/i,\n\t\tprev: /^\\s*<*(((Prev|Previous)\\s*(page)?|older|back)|((前)(のページ)?(へ)?)|((上|前)\\s*(一)?(页|頁))|(이전)|«|<|←)\\s*$/i,\n\t},\n\tCLASS_NAME: {\n\t\tnext: /next/i,\n\t\tprev: /prev|previous/i,\n\t},\n\tPAGINATION_LI: {\n\t\t// Extracts attributes of an <a> tag within an <li> element that follows a current/active <li> element (e.g., <li class=\"current\">1</li> <li class=\"\"><a href=\"/page/2\">2</a></li>)\n\t\tnext: /<li[^>]+class\\s*=\\s*['\"][^'\"]*(?:current|active)[^'\"]*['\"][^>]*>.*?<\\/li>\\s*<li[^>]*>\\s*<a\\s+(?<attributes>[^>]+)>/is,\n\t\t// Extracts attributes of an <a> tag within an <li> element that precedes a current/active <li> element (e.g., <li class=\"\"><a href=\"/page/1\">1</a></li> <li class=\"current\">2</li>)\n\t\tprev: /<li[^>]*>\\s*<a\\s+(?<attributes>[^>]+)>.*?<\\/a>\\s*<\\/li>\\s*<li[^>]+class\\s*=\\s*['\"][^'\"]*(?:current|active)[^'\"]*['\"][^>]*>/is,\n\t},\n\tPAGINATION_FALLBACK: {\n\t\t// Extracts attributes of an <a> tag that follows a current/active element (span, a, or strong tag with current/active class or aria-current=\"page\").\n\t\t// This handles cases where there's no <li> element structure or for more general pagination.\n\t\t// Example: <span class=\"current\">1</span> <a href=\"/page/2\">2</a>\n\t\tnext: /(?:<(?:span|a)[^>]+(?:class\\s*=\\s*['\"](?:current|active)['\"]|aria-current\\s*=\\s*['\"]page['\"])|strong)\\s*[^<]*\\s*<\\/(?:span|a|strong)>\\s*<a\\s+(?<attributes>[^>]+)>/i,\n\t\t// Extracts attributes of an <a> tag that precedes a current/active element.\n\t\t// Example: <a href=\"/page/1\">1</a> <span class=\"current\">2</span>\n\t\tprev: /<a\\s+(?<attributes>[^>]+)>.*?<\\/a>\\s*(?:<(?:span|a)[^>]+(?:class\\s*=\\s*['\"](?:current|active)['\"]|aria-current\\s*=\\s*['\"]page['\"])|strong)\\s*[^<]*/i,\n\t},\n\t// Common Regex\n\tPOTENTIAL_LINK_TAGS: /<(?:a|link)\\s+(?<attributes>[^>]*?)>/gi,\n\tANCHOR_TAG: /<a\\s+(?<attributes>[^>]+)>(?<innerText>.*?)<\\/a>/gis,\n\tANCHOR_TAG_START: /<a\\s+(?<attributes>[^>]+)>/gi,\n\tPAGINATION_CONTAINER:\n\t\t/<(?:div|nav|ul)[^>]+(?:class|id)\\s*=\\s*['\"][^'\"]*(?:pagination|pager|page-nav)[^'\"]*['\"][^>]*>(?<containerHtml>[\\s\\S]*?)<\\/(?:div|nav|ul)>/gi,\n\tIMG_TAG: /<img[^>]+>/gi,\n\tPATH_PAGE_NUMBER: /^(.*[/\\-_])(\\d+)$/,\n\tHTML_TAGS: /<[^>]+>/g,\n\tHTML_ENTITIES: /&[a-z]+;|&#[0-9]+;|&#x[0-9a-f]+;/gi,\n};\n\nconst DEFAULT_TIMEOUT_MS = 8000;\n\nconst attributeRegexCache = new Map<string, RegExp>();\n\n/**\n * Extracts the value of a specified attribute from an attribute string.\n * @param attributes The string containing attributes.\n * @param attributeName The name of the attribute to extract (e.g., \"href\", \"class\", \"id\").\n * @returns The value of the attribute, or null if not found.\n */\nfunction extractAttribute(\n\tattributes: string,\n\tattributeName: string,\n): string | null {\n\tlet regex = attributeRegexCache.get(attributeName);\n\tif (!regex) {\n\t\tregex = new RegExp(\n\t\t\t`${attributeName}\\\\s*=\\\\s*(['\"])(?<value>[^\"']*)\\\\1`,\n\t\t\t\"i\",\n\t\t);\n\t\tattributeRegexCache.set(attributeName, regex);\n\t}\n\tconst match = attributes.match(regex);\n\treturn match?.groups?.value ?? null;\n}\n\n/**\n * Extracts the href attribute from an attribute string and converts it to an absolute URL.\n * @param attributes The string containing attributes.\n * @param baseUrl The base URL for resolving relative paths.\n * @returns The absolute URL, or null if not found/invalid.\n */\nfunction extractAbsoluteHref(\n\tattributes: string,\n\tbaseUrl: string,\n\toptions?: BaseOptions,\n): string | null {\n\tconst href = extractAttribute(attributes, \"href\");\n\tif (href) {\n\t\ttry {\n\t\t\treturn new URL(href, baseUrl).href;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\toptions?.logger?.(\n\t\t\t\t\"warn\",\n\t\t\t\t`Invalid URL '${href}' for base '${baseUrl}': ${message}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Asynchronously fetches HTML content from a given URL.\n * @param url The URL to fetch.\n * @param options Options.\n * @returns The HTML string, or null if fetching failed.\n */\nexport async function fetchHtml(\n\turl: string,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\tconst controller = new AbortController();\n\tconst timeoutId = setTimeout(\n\t\t() => controller.abort(),\n\t\toptions?.timeout ?? DEFAULT_TIMEOUT_MS,\n\t);\n\n\ttry {\n\t\tconst response = await fetch(url, { signal: controller.signal });\n\n\t\tif (!response.ok) {\n\t\t\toptions?.logger?.(\n\t\t\t\t\"warn\",\n\t\t\t\t`Failed to fetch ${url}: ${response.status} ${response.statusText}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst contentType = response.headers.get(\"content-type\");\n\t\tif (!contentType || !contentType.includes(\"text/html\")) {\n\t\t\toptions?.logger?.(\"warn\", `URL ${url} did not return HTML content.`);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn await response.text();\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toptions?.logger?.(\"error\", `Error fetching or parsing ${url}: ${message}`);\n\t\treturn null;\n\t} finally {\n\t\tclearTimeout(timeoutId);\n\t}\n}\n\nfunction findLinkByRel(\n\thtml: string,\n\tbaseUrl: string,\n\trelRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst allPotentialLinks = html.matchAll(REGEX.POTENTIAL_LINK_TAGS);\n\n\tfor (const match of allPotentialLinks) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tif (!attributes) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (relRegex.test(attributes)) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByPaginationStructure(\n\thtml: string,\n\tbaseUrl: string,\n\tliRegex: RegExp,\n\tfallbackRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst paginationContainers = html.matchAll(REGEX.PAGINATION_CONTAINER);\n\n\tfor (const match of paginationContainers) {\n\t\tconst containerHtml = match.groups?.containerHtml;\n\n\t\tif (!containerHtml) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst liMatch = containerHtml.match(liRegex);\n\t\tif (liMatch?.groups?.attributes) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(\n\t\t\t\tliMatch.groups.attributes,\n\t\t\t\tbaseUrl,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\n\t\tconst fallbackMatch = containerHtml.match(fallbackRegex);\n\t\tif (fallbackMatch?.groups?.attributes) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(\n\t\t\t\tfallbackMatch.groups.attributes,\n\t\t\t\tbaseUrl,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByText(\n\thtml: string,\n\tbaseUrl: string,\n\ttextRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tconst innerText = match.groups?.innerText;\n\n\t\tif (!attributes || !innerText) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst cleanText = innerText\n\t\t\t.replace(REGEX.HTML_TAGS, \"\")\n\t\t\t.replace(REGEX.HTML_ENTITIES, \"\")\n\t\t\t.trim();\n\t\tif (!cleanText) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (textRegex.test(cleanText)) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByClassName(\n\thtml: string,\n\tbaseUrl: string,\n\tdefaultRegex: RegExp,\n\tclassNameRegex?: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG_START);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tif (!attributes) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst classAttr = extractAttribute(attributes, \"class\");\n\t\tconst idAttr = extractAttribute(attributes, \"id\");\n\n\t\tif (\n\t\t\t(classAttr && (classNameRegex ?? defaultRegex).test(classAttr)) ||\n\t\t\t(idAttr && defaultRegex.test(idAttr))\n\t\t) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByAriaLabel(\n\thtml: string,\n\tbaseUrl: string,\n\ttextRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG_START);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tif (!attributes) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst ariaLabelAttr = extractAttribute(attributes, \"aria-label\");\n\t\tif (ariaLabelAttr && textRegex.test(ariaLabelAttr)) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByAltText(\n\thtml: string,\n\tbaseUrl: string,\n\ttextRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tconst innerHtml = match.groups?.innerText;\n\n\t\tif (!attributes || !innerHtml) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst imgTags = innerHtml.matchAll(REGEX.IMG_TAG);\n\t\tfor (const [imgTag] of imgTags) {\n\t\t\tconst altText = extractAttribute(imgTag, \"alt\");\n\t\t\tif (altText && textRegex.test(altText.trim())) {\n\t\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\t\tif (absoluteUrl) {\n\t\t\t\t\treturn absoluteUrl;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLink(\n\thtml: string,\n\tbaseUrl: string,\n\tdirection: Direction,\n\toptions?: FindNextOptions,\n): string | null {\n\tconst methods: Method[] = options?.methods ?? [\n\t\t\"rel\",\n\t\t\"pagination\",\n\t\t\"text\",\n\t\t\"className\",\n\t\t\"aria-label\",\n\t\t\"alt\",\n\t];\n\n\t// Dispatch table (map of strategies)\n\tconst strategies: { [key in Method]: () => string | null } = {\n\t\trel: () => findLinkByRel(html, baseUrl, REGEX.REL[direction], options),\n\t\tpagination: () =>\n\t\t\tfindLinkByPaginationStructure(\n\t\t\t\thtml,\n\t\t\t\tbaseUrl,\n\t\t\t\tREGEX.PAGINATION_LI[direction],\n\t\t\t\tREGEX.PAGINATION_FALLBACK[direction],\n\t\t\t\toptions,\n\t\t\t),\n\t\ttext: () => findLinkByText(html, baseUrl, REGEX.TEXT[direction], options),\n\t\tclassName: () =>\n\t\t\tfindLinkByClassName(\n\t\t\t\thtml,\n\t\t\t\tbaseUrl,\n\t\t\t\tREGEX.CLASS_NAME[direction],\n\t\t\t\toptions?.classNameRegex,\n\t\t\t\toptions,\n\t\t\t),\n\t\t\"aria-label\": () =>\n\t\t\tfindLinkByAriaLabel(html, baseUrl, REGEX.TEXT[direction], options),\n\t\talt: () => findLinkByAltText(html, baseUrl, REGEX.TEXT[direction], options),\n\t};\n\n\tfor (const method of methods) {\n\t\tconst url = strategies[method]();\n\t\tif (url) {\n\t\t\treturn url;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Attempts multiple strategies in order to find the next page link URL from an HTML string.\n * @param html The HTML string to parse.\n * @param baseUrl The base URL from which the HTML was fetched (for resolving relative paths).\n * @param options Options for specifying search strategies.\n * @returns The URL of the next page, or null if not found.\n */\nexport function findNext(\n\thtml: string,\n\tbaseUrl: string,\n\toptions?: FindNextOptions,\n): string | null {\n\treturn findLink(html, baseUrl, \"next\", options);\n}\n\n/**\n * Attempts multiple strategies in order to find the previous page link URL from an HTML string.\n * @param html The HTML string to parse.\n * @param baseUrl The base URL from which the HTML was fetched.\n * @param options Options for specifying search strategies.\n * @returns The URL of the previous page, or null if not found.\n */\nexport function findPrev(\n\thtml: string,\n\tbaseUrl: string,\n\toptions?: FindNextOptions,\n): string | null {\n\treturn findLink(html, baseUrl, \"prev\", options);\n}\n\n/**\n * Checks if a URL actually exists using a HEAD request.\n * @param url The URL to check.\n * @returns True if the URL exists, false otherwise.\n */\nasync function urlExists(url: string, options?: BaseOptions): Promise<boolean> {\n\tconst controller = new AbortController();\n\tconst timeoutId = setTimeout(\n\t\t() => controller.abort(),\n\t\toptions?.timeout ?? DEFAULT_TIMEOUT_MS,\n\t);\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tsignal: controller.signal,\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t} finally {\n\t\tclearTimeout(timeoutId);\n\t}\n}\n\nasync function findUrlByQueryParam(\n\turl: string,\n\tdirection: Direction,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\ttry {\n\t\tconst urlObject = new URL(url);\n\t\tconst params = urlObject.searchParams;\n\t\tlet targetKey: string | null = null;\n\t\tlet currentValue: number | null = null;\n\n\t\tfor (const key of [\"page\", \"p\", \"index\"]) {\n\t\t\tconst value = params.get(key);\n\t\t\tif (value) {\n\t\t\t\tconst num = parseInt(value, 10);\n\t\t\t\tif (!Number.isNaN(num)) {\n\t\t\t\t\ttargetKey = key;\n\t\t\t\t\tcurrentValue = num;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (targetKey && currentValue !== null) {\n\t\t\tconst newNumber =\n\t\t\t\tdirection === \"next\" ? currentValue + 1 : currentValue - 1;\n\n\t\t\tif (newNumber > 0) {\n\t\t\t\tparams.set(targetKey, String(newNumber));\n\t\t\t\tconst newUrl = urlObject.toString();\n\t\t\t\tif (\n\t\t\t\t\toptions?.verifyExists === false ||\n\t\t\t\t\t(await urlExists(newUrl, options))\n\t\t\t\t) {\n\t\t\t\t\treturn newUrl;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toptions?.logger?.(\n\t\t\t\"warn\",\n\t\t\t`Invalid URL provided to findUrlByQueryParam: ${message}`,\n\t\t);\n\t}\n\n\treturn null;\n}\n\nasync function findUrlByPathSegment(\n\turl: string,\n\tdirection: Direction,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\ttry {\n\t\tconst urlObject = new URL(url);\n\t\tconst pathname = urlObject.pathname.replace(/\\/$/, \"\");\n\t\tconst pathMatch = pathname.match(REGEX.PATH_PAGE_NUMBER);\n\n\t\tif (pathMatch) {\n\t\t\tconst [_, prefix, currentNumberStr] = pathMatch;\n\t\t\tif (prefix && currentNumberStr) {\n\t\t\t\tconst currentNumber = parseInt(currentNumberStr, 10);\n\t\t\t\tconst newNumber =\n\t\t\t\t\tdirection === \"next\" ? currentNumber + 1 : currentNumber - 1;\n\n\t\t\t\tif (newNumber > 0) {\n\t\t\t\t\tconst newPath = `${prefix}${newNumber}`;\n\t\t\t\t\tconst newUrl = `${urlObject.origin}${newPath}${urlObject.search}${urlObject.hash}`;\n\t\t\t\t\tif (\n\t\t\t\t\t\toptions?.verifyExists === false ||\n\t\t\t\t\t\t(await urlExists(newUrl, options))\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn newUrl;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toptions?.logger?.(\n\t\t\t\"warn\",\n\t\t\t`Invalid URL provided to findUrlByPathSegment: ${message}`,\n\t\t);\n\t}\n\n\treturn null;\n}\n\n/**\n * Asynchronously infers and finds the next/previous page URL based on URL patterns.\n * @param url The URL of the current page.\n * @param direction \"next\" or \"prev\".\n * @param options Options for URL pattern inference.\n * @returns The next/previous page URL, or null if not found.\n */\nasync function findUrlByPattern(\n\turl: string,\n\tdirection: Direction,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\tconst urlByQuery = await findUrlByQueryParam(url, direction, options);\n\tif (urlByQuery) {\n\t\treturn urlByQuery;\n\t}\n\n\tconst urlByPath = await findUrlByPathSegment(url, direction, options);\n\tif (urlByPath) {\n\t\treturn urlByPath;\n\t}\n\n\treturn null;\n}\n\n/**\n * Asynchronously infers and finds the next page URL based on URL patterns.\n * Increments the page number in the URL and checks if the URL exists.\n * @param url The URL of the current page.\n * @param options Options for URL pattern inference.\n * @returns The next page URL, or null if not found.\n */\nexport function findNextByUrl(\n\turl: string,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\treturn findUrlByPattern(url, \"next\", options);\n}\n\n/**\n * Asynchronously infers and finds the previous page URL based on URL patterns.\n * @param url The URL of the current page.\n * @param options Options for URL pattern inference.\n * @returns The previous page URL, or null if not found.\n */\nexport function findPrevByUrl(\n\turl: string,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\treturn findUrlByPattern(url, \"prev\", options);\n}\n"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ //#region src/index.d.ts
2
+ interface BaseOptions {
3
+ logger?: (level: "warn" | "error", message: string) => void;
4
+ timeout?: number;
5
+ verifyExists?: boolean;
6
+ }
7
+ type Method = "rel" | "pagination" | "text" | "className" | "aria-label" | "alt";
8
+ interface FindNextOptions extends BaseOptions {
9
+ methods?: Method[];
10
+ classNameRegex?: RegExp;
11
+ }
12
+ /**
13
+ * Asynchronously fetches HTML content from a given URL.
14
+ * @param url The URL to fetch.
15
+ * @param options Options.
16
+ * @returns The HTML string, or null if fetching failed.
17
+ */
18
+ declare function fetchHtml(url: string, options?: BaseOptions): Promise<string | null>;
19
+ /**
20
+ * Attempts multiple strategies in order to find the next page link URL from an HTML string.
21
+ * @param html The HTML string to parse.
22
+ * @param baseUrl The base URL from which the HTML was fetched (for resolving relative paths).
23
+ * @param options Options for specifying search strategies.
24
+ * @returns The URL of the next page, or null if not found.
25
+ */
26
+ declare function findNext(html: string, baseUrl: string, options?: FindNextOptions): string | null;
27
+ /**
28
+ * Attempts multiple strategies in order to find the previous page link URL from an HTML string.
29
+ * @param html The HTML string to parse.
30
+ * @param baseUrl The base URL from which the HTML was fetched.
31
+ * @param options Options for specifying search strategies.
32
+ * @returns The URL of the previous page, or null if not found.
33
+ */
34
+ declare function findPrev(html: string, baseUrl: string, options?: FindNextOptions): string | null;
35
+ /**
36
+ * Asynchronously infers and finds the next page URL based on URL patterns.
37
+ * Increments the page number in the URL and checks if the URL exists.
38
+ * @param url The URL of the current page.
39
+ * @param options Options for URL pattern inference.
40
+ * @returns The next page URL, or null if not found.
41
+ */
42
+ declare function findNextByUrl(url: string, options?: BaseOptions): Promise<string | null>;
43
+ /**
44
+ * Asynchronously infers and finds the previous page URL based on URL patterns.
45
+ * @param url The URL of the current page.
46
+ * @param options Options for URL pattern inference.
47
+ * @returns The previous page URL, or null if not found.
48
+ */
49
+ declare function findPrevByUrl(url: string, options?: BaseOptions): Promise<string | null>;
50
+ //#endregion
51
+ export { BaseOptions, FindNextOptions, Method, fetchHtml, findNext, findNextByUrl, findPrev, findPrevByUrl };
52
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,52 @@
1
+ //#region src/index.d.ts
2
+ interface BaseOptions {
3
+ logger?: (level: "warn" | "error", message: string) => void;
4
+ timeout?: number;
5
+ verifyExists?: boolean;
6
+ }
7
+ type Method = "rel" | "pagination" | "text" | "className" | "aria-label" | "alt";
8
+ interface FindNextOptions extends BaseOptions {
9
+ methods?: Method[];
10
+ classNameRegex?: RegExp;
11
+ }
12
+ /**
13
+ * Asynchronously fetches HTML content from a given URL.
14
+ * @param url The URL to fetch.
15
+ * @param options Options.
16
+ * @returns The HTML string, or null if fetching failed.
17
+ */
18
+ declare function fetchHtml(url: string, options?: BaseOptions): Promise<string | null>;
19
+ /**
20
+ * Attempts multiple strategies in order to find the next page link URL from an HTML string.
21
+ * @param html The HTML string to parse.
22
+ * @param baseUrl The base URL from which the HTML was fetched (for resolving relative paths).
23
+ * @param options Options for specifying search strategies.
24
+ * @returns The URL of the next page, or null if not found.
25
+ */
26
+ declare function findNext(html: string, baseUrl: string, options?: FindNextOptions): string | null;
27
+ /**
28
+ * Attempts multiple strategies in order to find the previous page link URL from an HTML string.
29
+ * @param html The HTML string to parse.
30
+ * @param baseUrl The base URL from which the HTML was fetched.
31
+ * @param options Options for specifying search strategies.
32
+ * @returns The URL of the previous page, or null if not found.
33
+ */
34
+ declare function findPrev(html: string, baseUrl: string, options?: FindNextOptions): string | null;
35
+ /**
36
+ * Asynchronously infers and finds the next page URL based on URL patterns.
37
+ * Increments the page number in the URL and checks if the URL exists.
38
+ * @param url The URL of the current page.
39
+ * @param options Options for URL pattern inference.
40
+ * @returns The next page URL, or null if not found.
41
+ */
42
+ declare function findNextByUrl(url: string, options?: BaseOptions): Promise<string | null>;
43
+ /**
44
+ * Asynchronously infers and finds the previous page URL based on URL patterns.
45
+ * @param url The URL of the current page.
46
+ * @param options Options for URL pattern inference.
47
+ * @returns The previous page URL, or null if not found.
48
+ */
49
+ declare function findPrevByUrl(url: string, options?: BaseOptions): Promise<string | null>;
50
+ //#endregion
51
+ export { BaseOptions, FindNextOptions, Method, fetchHtml, findNext, findNextByUrl, findPrev, findPrevByUrl };
52
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ const e={REL:{next:/rel\s*=\s*(['"])[^'"]*?\bnext\b[^'"]*?\1/i,prev:/rel\s*=\s*(['"])[^'"]*?\b(prev|previous)\b[^'"]*?\1/i},TEXT:{next:/^\s*(((Next)\s*(page)?|older|forward)|((次|つぎ)(のページ)?(へ)?)|((下|后)\s*(一)?(页|頁))|(다음)|»|>|→)\s*>*$/i,prev:/^\s*<*(((Prev|Previous)\s*(page)?|older|back)|((前)(のページ)?(へ)?)|((上|前)\s*(一)?(页|頁))|(이전)|«|<|←)\s*$/i},CLASS_NAME:{next:/next/i,prev:/prev|previous/i},PAGINATION_LI:{next:/<li[^>]+class\s*=\s*['"][^'"]*(?:current|active)[^'"]*['"][^>]*>.*?<\/li>\s*<li[^>]*>\s*<a\s+(?<attributes>[^>]+)>/is,prev:/<li[^>]*>\s*<a\s+(?<attributes>[^>]+)>.*?<\/a>\s*<\/li>\s*<li[^>]+class\s*=\s*['"][^'"]*(?:current|active)[^'"]*['"][^>]*>/is},PAGINATION_FALLBACK:{next:/(?:<(?:span|a)[^>]+(?:class\s*=\s*['"](?:current|active)['"]|aria-current\s*=\s*['"]page['"])|strong)\s*[^<]*\s*<\/(?:span|a|strong)>\s*<a\s+(?<attributes>[^>]+)>/i,prev:/<a\s+(?<attributes>[^>]+)>.*?<\/a>\s*(?:<(?:span|a)[^>]+(?:class\s*=\s*['"](?:current|active)['"]|aria-current\s*=\s*['"]page['"])|strong)\s*[^<]*/i},POTENTIAL_LINK_TAGS:/<(?:a|link)\s+(?<attributes>[^>]*?)>/gi,ANCHOR_TAG:/<a\s+(?<attributes>[^>]+)>(?<innerText>.*?)<\/a>/gis,ANCHOR_TAG_START:/<a\s+(?<attributes>[^>]+)>/gi,PAGINATION_CONTAINER:/<(?:div|nav|ul)[^>]+(?:class|id)\s*=\s*['"][^'"]*(?:pagination|pager|page-nav)[^'"]*['"][^>]*>(?<containerHtml>[\s\S]*?)<\/(?:div|nav|ul)>/gi,IMG_TAG:/<img[^>]+>/gi,PATH_PAGE_NUMBER:/^(.*[/\-_])(\d+)$/,HTML_TAGS:/<[^>]+>/g,HTML_ENTITIES:/&[a-z]+;|&#[0-9]+;|&#x[0-9a-f]+;/gi},t=8e3,n=new Map;function r(e,t){let r=n.get(t);return r||(r=RegExp(`${t}\\s*=\\s*(['"])(?<value>[^"']*)\\1`,`i`),n.set(t,r)),e.match(r)?.groups?.value??null}function i(e,t,n){let i=r(e,`href`);if(i)try{return new URL(i,t).href}catch(e){let r=e instanceof Error?e.message:String(e);return n?.logger?.(`warn`,`Invalid URL '${i}' for base '${t}': ${r}`),null}return null}async function a(e,n){let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??t);try{let t=await fetch(e,{signal:r.signal});if(!t.ok)return n?.logger?.(`warn`,`Failed to fetch ${e}: ${t.status} ${t.statusText}`),null;let i=t.headers.get(`content-type`);return!i||!i.includes(`text/html`)?(n?.logger?.(`warn`,`URL ${e} did not return HTML content.`),null):await t.text()}catch(t){let r=t instanceof Error?t.message:String(t);return n?.logger?.(`error`,`Error fetching or parsing ${e}: ${r}`),null}finally{clearTimeout(i)}}function o(t,n,r,a){let o=t.matchAll(e.POTENTIAL_LINK_TAGS);for(let e of o){let t=e.groups?.attributes;if(t&&r.test(t)){let e=i(t,n,a);if(e)return e}}return null}function s(t,n,r,a,o){let s=t.matchAll(e.PAGINATION_CONTAINER);for(let e of s){let t=e.groups?.containerHtml;if(!t)continue;let s=t.match(r);if(s?.groups?.attributes){let e=i(s.groups.attributes,n,o);if(e)return e}let c=t.match(a);if(c?.groups?.attributes){let e=i(c.groups.attributes,n,o);if(e)return e}}return null}function c(t,n,r,a){let o=t.matchAll(e.ANCHOR_TAG);for(let t of o){let o=t.groups?.attributes,s=t.groups?.innerText;if(!o||!s)continue;let c=s.replace(e.HTML_TAGS,``).replace(e.HTML_ENTITIES,``).trim();if(c&&r.test(c)){let e=i(o,n,a);if(e)return e}}return null}function l(t,n,a,o,s){let c=t.matchAll(e.ANCHOR_TAG_START);for(let e of c){let t=e.groups?.attributes;if(!t)continue;let c=r(t,`class`),l=r(t,`id`);if(c&&(o??a).test(c)||l&&a.test(l)){let e=i(t,n,s);if(e)return e}}return null}function u(t,n,a,o){let s=t.matchAll(e.ANCHOR_TAG_START);for(let e of s){let t=e.groups?.attributes;if(!t)continue;let s=r(t,`aria-label`);if(s&&a.test(s)){let e=i(t,n,o);if(e)return e}}return null}function d(t,n,a,o){let s=t.matchAll(e.ANCHOR_TAG);for(let t of s){let s=t.groups?.attributes,c=t.groups?.innerText;if(!s||!c)continue;let l=c.matchAll(e.IMG_TAG);for(let[e]of l){let t=r(e,`alt`);if(t&&a.test(t.trim())){let e=i(s,n,o);if(e)return e}}}return null}function f(t,n,r,i){let a=i?.methods??[`rel`,`pagination`,`text`,`className`,`aria-label`,`alt`],f={rel:()=>o(t,n,e.REL[r],i),pagination:()=>s(t,n,e.PAGINATION_LI[r],e.PAGINATION_FALLBACK[r],i),text:()=>c(t,n,e.TEXT[r],i),className:()=>l(t,n,e.CLASS_NAME[r],i?.classNameRegex,i),"aria-label":()=>u(t,n,e.TEXT[r],i),alt:()=>d(t,n,e.TEXT[r],i)};for(let e of a){let t=f[e]();if(t)return t}return null}function p(e,t,n){return f(e,t,`next`,n)}function m(e,t,n){return f(e,t,`prev`,n)}async function h(e,n){let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??t);try{return(await fetch(e,{method:`HEAD`,signal:r.signal})).ok}catch{return!1}finally{clearTimeout(i)}}async function g(e,t,n){try{let r=new URL(e),i=r.searchParams,a=null,o=null;for(let e of[`page`,`p`,`index`]){let t=i.get(e);if(t){let n=parseInt(t,10);if(!Number.isNaN(n)){a=e,o=n;break}}}if(a&&o!==null){let e=t===`next`?o+1:o-1;if(e>0){i.set(a,String(e));let t=r.toString();if(n?.verifyExists===!1||await h(t,n))return t}}}catch(e){let t=e instanceof Error?e.message:String(e);n?.logger?.(`warn`,`Invalid URL provided to findUrlByQueryParam: ${t}`)}return null}async function _(t,n,r){try{let i=new URL(t),a=i.pathname.replace(/\/$/,``).match(e.PATH_PAGE_NUMBER);if(a){let[e,t,o]=a;if(t&&o){let e=parseInt(o,10),a=n===`next`?e+1:e-1;if(a>0){let e=`${t}${a}`,n=`${i.origin}${e}${i.search}${i.hash}`;if(r?.verifyExists===!1||await h(n,r))return n}}}}catch(e){let t=e instanceof Error?e.message:String(e);r?.logger?.(`warn`,`Invalid URL provided to findUrlByPathSegment: ${t}`)}return null}async function v(e,t,n){return await g(e,t,n)||await _(e,t,n)||null}function y(e,t){return v(e,`next`,t)}function b(e,t){return v(e,`prev`,t)}export{a as fetchHtml,p as findNext,y as findNextByUrl,m as findPrev,b as findPrevByUrl};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["// src/index.ts\n\nexport interface BaseOptions {\n\tlogger?: (level: \"warn\" | \"error\", message: string) => void;\n\ttimeout?: number;\n\tverifyExists?: boolean;\n}\n\nexport type Method =\n\t| \"rel\"\n\t| \"pagination\"\n\t| \"text\"\n\t| \"className\"\n\t| \"aria-label\"\n\t| \"alt\";\n\nexport interface FindNextOptions extends BaseOptions {\n\tmethods?: Method[];\n\tclassNameRegex?: RegExp;\n}\n\ntype Direction = \"next\" | \"prev\";\n\nconst REGEX = {\n\tREL: {\n\t\tnext: /rel\\s*=\\s*(['\"])[^'\"]*?\\bnext\\b[^'\"]*?\\1/i,\n\t\tprev: /rel\\s*=\\s*(['\"])[^'\"]*?\\b(prev|previous)\\b[^'\"]*?\\1/i,\n\t},\n\tTEXT: {\n\t\tnext: /^\\s*(((Next)\\s*(page)?|older|forward)|((次|つぎ)(のページ)?(へ)?)|((下|后)\\s*(一)?(页|頁))|(다음)|»|>|→)\\s*>*$/i,\n\t\tprev: /^\\s*<*(((Prev|Previous)\\s*(page)?|older|back)|((前)(のページ)?(へ)?)|((上|前)\\s*(一)?(页|頁))|(이전)|«|<|←)\\s*$/i,\n\t},\n\tCLASS_NAME: {\n\t\tnext: /next/i,\n\t\tprev: /prev|previous/i,\n\t},\n\tPAGINATION_LI: {\n\t\t// Extracts attributes of an <a> tag within an <li> element that follows a current/active <li> element (e.g., <li class=\"current\">1</li> <li class=\"\"><a href=\"/page/2\">2</a></li>)\n\t\tnext: /<li[^>]+class\\s*=\\s*['\"][^'\"]*(?:current|active)[^'\"]*['\"][^>]*>.*?<\\/li>\\s*<li[^>]*>\\s*<a\\s+(?<attributes>[^>]+)>/is,\n\t\t// Extracts attributes of an <a> tag within an <li> element that precedes a current/active <li> element (e.g., <li class=\"\"><a href=\"/page/1\">1</a></li> <li class=\"current\">2</li>)\n\t\tprev: /<li[^>]*>\\s*<a\\s+(?<attributes>[^>]+)>.*?<\\/a>\\s*<\\/li>\\s*<li[^>]+class\\s*=\\s*['\"][^'\"]*(?:current|active)[^'\"]*['\"][^>]*>/is,\n\t},\n\tPAGINATION_FALLBACK: {\n\t\t// Extracts attributes of an <a> tag that follows a current/active element (span, a, or strong tag with current/active class or aria-current=\"page\").\n\t\t// This handles cases where there's no <li> element structure or for more general pagination.\n\t\t// Example: <span class=\"current\">1</span> <a href=\"/page/2\">2</a>\n\t\tnext: /(?:<(?:span|a)[^>]+(?:class\\s*=\\s*['\"](?:current|active)['\"]|aria-current\\s*=\\s*['\"]page['\"])|strong)\\s*[^<]*\\s*<\\/(?:span|a|strong)>\\s*<a\\s+(?<attributes>[^>]+)>/i,\n\t\t// Extracts attributes of an <a> tag that precedes a current/active element.\n\t\t// Example: <a href=\"/page/1\">1</a> <span class=\"current\">2</span>\n\t\tprev: /<a\\s+(?<attributes>[^>]+)>.*?<\\/a>\\s*(?:<(?:span|a)[^>]+(?:class\\s*=\\s*['\"](?:current|active)['\"]|aria-current\\s*=\\s*['\"]page['\"])|strong)\\s*[^<]*/i,\n\t},\n\t// Common Regex\n\tPOTENTIAL_LINK_TAGS: /<(?:a|link)\\s+(?<attributes>[^>]*?)>/gi,\n\tANCHOR_TAG: /<a\\s+(?<attributes>[^>]+)>(?<innerText>.*?)<\\/a>/gis,\n\tANCHOR_TAG_START: /<a\\s+(?<attributes>[^>]+)>/gi,\n\tPAGINATION_CONTAINER:\n\t\t/<(?:div|nav|ul)[^>]+(?:class|id)\\s*=\\s*['\"][^'\"]*(?:pagination|pager|page-nav)[^'\"]*['\"][^>]*>(?<containerHtml>[\\s\\S]*?)<\\/(?:div|nav|ul)>/gi,\n\tIMG_TAG: /<img[^>]+>/gi,\n\tPATH_PAGE_NUMBER: /^(.*[/\\-_])(\\d+)$/,\n\tHTML_TAGS: /<[^>]+>/g,\n\tHTML_ENTITIES: /&[a-z]+;|&#[0-9]+;|&#x[0-9a-f]+;/gi,\n};\n\nconst DEFAULT_TIMEOUT_MS = 8000;\n\nconst attributeRegexCache = new Map<string, RegExp>();\n\n/**\n * Extracts the value of a specified attribute from an attribute string.\n * @param attributes The string containing attributes.\n * @param attributeName The name of the attribute to extract (e.g., \"href\", \"class\", \"id\").\n * @returns The value of the attribute, or null if not found.\n */\nfunction extractAttribute(\n\tattributes: string,\n\tattributeName: string,\n): string | null {\n\tlet regex = attributeRegexCache.get(attributeName);\n\tif (!regex) {\n\t\tregex = new RegExp(\n\t\t\t`${attributeName}\\\\s*=\\\\s*(['\"])(?<value>[^\"']*)\\\\1`,\n\t\t\t\"i\",\n\t\t);\n\t\tattributeRegexCache.set(attributeName, regex);\n\t}\n\tconst match = attributes.match(regex);\n\treturn match?.groups?.value ?? null;\n}\n\n/**\n * Extracts the href attribute from an attribute string and converts it to an absolute URL.\n * @param attributes The string containing attributes.\n * @param baseUrl The base URL for resolving relative paths.\n * @returns The absolute URL, or null if not found/invalid.\n */\nfunction extractAbsoluteHref(\n\tattributes: string,\n\tbaseUrl: string,\n\toptions?: BaseOptions,\n): string | null {\n\tconst href = extractAttribute(attributes, \"href\");\n\tif (href) {\n\t\ttry {\n\t\t\treturn new URL(href, baseUrl).href;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\toptions?.logger?.(\n\t\t\t\t\"warn\",\n\t\t\t\t`Invalid URL '${href}' for base '${baseUrl}': ${message}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Asynchronously fetches HTML content from a given URL.\n * @param url The URL to fetch.\n * @param options Options.\n * @returns The HTML string, or null if fetching failed.\n */\nexport async function fetchHtml(\n\turl: string,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\tconst controller = new AbortController();\n\tconst timeoutId = setTimeout(\n\t\t() => controller.abort(),\n\t\toptions?.timeout ?? DEFAULT_TIMEOUT_MS,\n\t);\n\n\ttry {\n\t\tconst response = await fetch(url, { signal: controller.signal });\n\n\t\tif (!response.ok) {\n\t\t\toptions?.logger?.(\n\t\t\t\t\"warn\",\n\t\t\t\t`Failed to fetch ${url}: ${response.status} ${response.statusText}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst contentType = response.headers.get(\"content-type\");\n\t\tif (!contentType || !contentType.includes(\"text/html\")) {\n\t\t\toptions?.logger?.(\"warn\", `URL ${url} did not return HTML content.`);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn await response.text();\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toptions?.logger?.(\"error\", `Error fetching or parsing ${url}: ${message}`);\n\t\treturn null;\n\t} finally {\n\t\tclearTimeout(timeoutId);\n\t}\n}\n\nfunction findLinkByRel(\n\thtml: string,\n\tbaseUrl: string,\n\trelRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst allPotentialLinks = html.matchAll(REGEX.POTENTIAL_LINK_TAGS);\n\n\tfor (const match of allPotentialLinks) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tif (!attributes) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (relRegex.test(attributes)) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByPaginationStructure(\n\thtml: string,\n\tbaseUrl: string,\n\tliRegex: RegExp,\n\tfallbackRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst paginationContainers = html.matchAll(REGEX.PAGINATION_CONTAINER);\n\n\tfor (const match of paginationContainers) {\n\t\tconst containerHtml = match.groups?.containerHtml;\n\n\t\tif (!containerHtml) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst liMatch = containerHtml.match(liRegex);\n\t\tif (liMatch?.groups?.attributes) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(\n\t\t\t\tliMatch.groups.attributes,\n\t\t\t\tbaseUrl,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\n\t\tconst fallbackMatch = containerHtml.match(fallbackRegex);\n\t\tif (fallbackMatch?.groups?.attributes) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(\n\t\t\t\tfallbackMatch.groups.attributes,\n\t\t\t\tbaseUrl,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByText(\n\thtml: string,\n\tbaseUrl: string,\n\ttextRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tconst innerText = match.groups?.innerText;\n\n\t\tif (!attributes || !innerText) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst cleanText = innerText\n\t\t\t.replace(REGEX.HTML_TAGS, \"\")\n\t\t\t.replace(REGEX.HTML_ENTITIES, \"\")\n\t\t\t.trim();\n\t\tif (!cleanText) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (textRegex.test(cleanText)) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByClassName(\n\thtml: string,\n\tbaseUrl: string,\n\tdefaultRegex: RegExp,\n\tclassNameRegex?: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG_START);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tif (!attributes) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst classAttr = extractAttribute(attributes, \"class\");\n\t\tconst idAttr = extractAttribute(attributes, \"id\");\n\n\t\tif (\n\t\t\t(classAttr && (classNameRegex ?? defaultRegex).test(classAttr)) ||\n\t\t\t(idAttr && defaultRegex.test(idAttr))\n\t\t) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByAriaLabel(\n\thtml: string,\n\tbaseUrl: string,\n\ttextRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG_START);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tif (!attributes) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst ariaLabelAttr = extractAttribute(attributes, \"aria-label\");\n\t\tif (ariaLabelAttr && textRegex.test(ariaLabelAttr)) {\n\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\tif (absoluteUrl) {\n\t\t\t\treturn absoluteUrl;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLinkByAltText(\n\thtml: string,\n\tbaseUrl: string,\n\ttextRegex: RegExp,\n\toptions?: BaseOptions,\n): string | null {\n\tconst anchorTags = html.matchAll(REGEX.ANCHOR_TAG);\n\n\tfor (const match of anchorTags) {\n\t\tconst attributes = match.groups?.attributes;\n\t\tconst innerHtml = match.groups?.innerText;\n\n\t\tif (!attributes || !innerHtml) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst imgTags = innerHtml.matchAll(REGEX.IMG_TAG);\n\t\tfor (const [imgTag] of imgTags) {\n\t\t\tconst altText = extractAttribute(imgTag, \"alt\");\n\t\t\tif (altText && textRegex.test(altText.trim())) {\n\t\t\t\tconst absoluteUrl = extractAbsoluteHref(attributes, baseUrl, options);\n\t\t\t\tif (absoluteUrl) {\n\t\t\t\t\treturn absoluteUrl;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction findLink(\n\thtml: string,\n\tbaseUrl: string,\n\tdirection: Direction,\n\toptions?: FindNextOptions,\n): string | null {\n\tconst methods: Method[] = options?.methods ?? [\n\t\t\"rel\",\n\t\t\"pagination\",\n\t\t\"text\",\n\t\t\"className\",\n\t\t\"aria-label\",\n\t\t\"alt\",\n\t];\n\n\t// Dispatch table (map of strategies)\n\tconst strategies: { [key in Method]: () => string | null } = {\n\t\trel: () => findLinkByRel(html, baseUrl, REGEX.REL[direction], options),\n\t\tpagination: () =>\n\t\t\tfindLinkByPaginationStructure(\n\t\t\t\thtml,\n\t\t\t\tbaseUrl,\n\t\t\t\tREGEX.PAGINATION_LI[direction],\n\t\t\t\tREGEX.PAGINATION_FALLBACK[direction],\n\t\t\t\toptions,\n\t\t\t),\n\t\ttext: () => findLinkByText(html, baseUrl, REGEX.TEXT[direction], options),\n\t\tclassName: () =>\n\t\t\tfindLinkByClassName(\n\t\t\t\thtml,\n\t\t\t\tbaseUrl,\n\t\t\t\tREGEX.CLASS_NAME[direction],\n\t\t\t\toptions?.classNameRegex,\n\t\t\t\toptions,\n\t\t\t),\n\t\t\"aria-label\": () =>\n\t\t\tfindLinkByAriaLabel(html, baseUrl, REGEX.TEXT[direction], options),\n\t\talt: () => findLinkByAltText(html, baseUrl, REGEX.TEXT[direction], options),\n\t};\n\n\tfor (const method of methods) {\n\t\tconst url = strategies[method]();\n\t\tif (url) {\n\t\t\treturn url;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Attempts multiple strategies in order to find the next page link URL from an HTML string.\n * @param html The HTML string to parse.\n * @param baseUrl The base URL from which the HTML was fetched (for resolving relative paths).\n * @param options Options for specifying search strategies.\n * @returns The URL of the next page, or null if not found.\n */\nexport function findNext(\n\thtml: string,\n\tbaseUrl: string,\n\toptions?: FindNextOptions,\n): string | null {\n\treturn findLink(html, baseUrl, \"next\", options);\n}\n\n/**\n * Attempts multiple strategies in order to find the previous page link URL from an HTML string.\n * @param html The HTML string to parse.\n * @param baseUrl The base URL from which the HTML was fetched.\n * @param options Options for specifying search strategies.\n * @returns The URL of the previous page, or null if not found.\n */\nexport function findPrev(\n\thtml: string,\n\tbaseUrl: string,\n\toptions?: FindNextOptions,\n): string | null {\n\treturn findLink(html, baseUrl, \"prev\", options);\n}\n\n/**\n * Checks if a URL actually exists using a HEAD request.\n * @param url The URL to check.\n * @returns True if the URL exists, false otherwise.\n */\nasync function urlExists(url: string, options?: BaseOptions): Promise<boolean> {\n\tconst controller = new AbortController();\n\tconst timeoutId = setTimeout(\n\t\t() => controller.abort(),\n\t\toptions?.timeout ?? DEFAULT_TIMEOUT_MS,\n\t);\n\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tsignal: controller.signal,\n\t\t});\n\t\treturn response.ok;\n\t} catch {\n\t\treturn false;\n\t} finally {\n\t\tclearTimeout(timeoutId);\n\t}\n}\n\nasync function findUrlByQueryParam(\n\turl: string,\n\tdirection: Direction,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\ttry {\n\t\tconst urlObject = new URL(url);\n\t\tconst params = urlObject.searchParams;\n\t\tlet targetKey: string | null = null;\n\t\tlet currentValue: number | null = null;\n\n\t\tfor (const key of [\"page\", \"p\", \"index\"]) {\n\t\t\tconst value = params.get(key);\n\t\t\tif (value) {\n\t\t\t\tconst num = parseInt(value, 10);\n\t\t\t\tif (!Number.isNaN(num)) {\n\t\t\t\t\ttargetKey = key;\n\t\t\t\t\tcurrentValue = num;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (targetKey && currentValue !== null) {\n\t\t\tconst newNumber =\n\t\t\t\tdirection === \"next\" ? currentValue + 1 : currentValue - 1;\n\n\t\t\tif (newNumber > 0) {\n\t\t\t\tparams.set(targetKey, String(newNumber));\n\t\t\t\tconst newUrl = urlObject.toString();\n\t\t\t\tif (\n\t\t\t\t\toptions?.verifyExists === false ||\n\t\t\t\t\t(await urlExists(newUrl, options))\n\t\t\t\t) {\n\t\t\t\t\treturn newUrl;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toptions?.logger?.(\n\t\t\t\"warn\",\n\t\t\t`Invalid URL provided to findUrlByQueryParam: ${message}`,\n\t\t);\n\t}\n\n\treturn null;\n}\n\nasync function findUrlByPathSegment(\n\turl: string,\n\tdirection: Direction,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\ttry {\n\t\tconst urlObject = new URL(url);\n\t\tconst pathname = urlObject.pathname.replace(/\\/$/, \"\");\n\t\tconst pathMatch = pathname.match(REGEX.PATH_PAGE_NUMBER);\n\n\t\tif (pathMatch) {\n\t\t\tconst [_, prefix, currentNumberStr] = pathMatch;\n\t\t\tif (prefix && currentNumberStr) {\n\t\t\t\tconst currentNumber = parseInt(currentNumberStr, 10);\n\t\t\t\tconst newNumber =\n\t\t\t\t\tdirection === \"next\" ? currentNumber + 1 : currentNumber - 1;\n\n\t\t\t\tif (newNumber > 0) {\n\t\t\t\t\tconst newPath = `${prefix}${newNumber}`;\n\t\t\t\t\tconst newUrl = `${urlObject.origin}${newPath}${urlObject.search}${urlObject.hash}`;\n\t\t\t\t\tif (\n\t\t\t\t\t\toptions?.verifyExists === false ||\n\t\t\t\t\t\t(await urlExists(newUrl, options))\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn newUrl;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toptions?.logger?.(\n\t\t\t\"warn\",\n\t\t\t`Invalid URL provided to findUrlByPathSegment: ${message}`,\n\t\t);\n\t}\n\n\treturn null;\n}\n\n/**\n * Asynchronously infers and finds the next/previous page URL based on URL patterns.\n * @param url The URL of the current page.\n * @param direction \"next\" or \"prev\".\n * @param options Options for URL pattern inference.\n * @returns The next/previous page URL, or null if not found.\n */\nasync function findUrlByPattern(\n\turl: string,\n\tdirection: Direction,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\tconst urlByQuery = await findUrlByQueryParam(url, direction, options);\n\tif (urlByQuery) {\n\t\treturn urlByQuery;\n\t}\n\n\tconst urlByPath = await findUrlByPathSegment(url, direction, options);\n\tif (urlByPath) {\n\t\treturn urlByPath;\n\t}\n\n\treturn null;\n}\n\n/**\n * Asynchronously infers and finds the next page URL based on URL patterns.\n * Increments the page number in the URL and checks if the URL exists.\n * @param url The URL of the current page.\n * @param options Options for URL pattern inference.\n * @returns The next page URL, or null if not found.\n */\nexport function findNextByUrl(\n\turl: string,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\treturn findUrlByPattern(url, \"next\", options);\n}\n\n/**\n * Asynchronously infers and finds the previous page URL based on URL patterns.\n * @param url The URL of the current page.\n * @param options Options for URL pattern inference.\n * @returns The previous page URL, or null if not found.\n */\nexport function findPrevByUrl(\n\turl: string,\n\toptions?: BaseOptions,\n): Promise<string | null> {\n\treturn findUrlByPattern(url, \"prev\", options);\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@mash43/relnext",
3
+ "version": "0.1.0",
4
+ "description": "A TypeScript library designed to detect pagination links such as \"next\" and \"previous\" from web page HTML content or URLs.",
5
+ "author": "mash43",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/mashx43/relnext#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mashx43/relnext.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/mashx43/relnext/issues"
14
+ },
15
+ "keywords": [
16
+ "pagination",
17
+ "next",
18
+ "prev",
19
+ "rel-next"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "type": "module",
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.mjs",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "import": "./dist/index.mjs",
31
+ "require": "./dist/index.cjs",
32
+ "types": "./dist/index.d.ts"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsdown",
40
+ "test": "bun test",
41
+ "typecheck": "tsc --noEmit --skipLibCheck"
42
+ },
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^2.3.11",
45
+ "@types/bun": "^1.3.6",
46
+ "tsdown": "^0.20.1",
47
+ "typescript": "^5"
48
+ }
49
+ }