microlink.io 0.0.0 → 0.0.2

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.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2026 microlink.io <hello@microlink.io> (microlink.io)
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
13
+ all 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
21
+ THE SOFTWARE.
package/bin/help.txt ADDED
@@ -0,0 +1,47 @@
1
+ Usage
2
+ $ microlink <product> <url|query> [options]
3
+
4
+ Products
5
+ metadata Unified metadata (title, description, image, ...)
6
+ logo Brand logo of the site (--square prefers the square variant)
7
+ markdown Page content as Markdown
8
+ html Page content as HTML
9
+ text Page content as plain text
10
+ video Primary video of the page (returns the asset object)
11
+ audio Primary audio of the page (returns the asset object)
12
+ emails Every email address present on the page
13
+ links Every absolute link URL on the page
14
+ images Every absolute image URL on the page
15
+ videos Every absolute video URL on the page
16
+ audios Every absolute audio URL on the page
17
+ extract Custom MQL data rules (--data '{"field":{...}}')
18
+ screenshot Take a screenshot (returns the asset object)
19
+ pdf Generate a PDF (returns the asset object)
20
+ embed oEmbed-style embeddable iframe ({ html, scripts })
21
+ technologies Detect the tech stack behind the site
22
+ lighthouse Run a Lighthouse report
23
+ search Google as structured data (query instead of url); --type
24
+ routes to news, images, videos, places, maps, shopping,
25
+ scholar, patents or autocomplete
26
+ function Run code remotely with browser access (--file ./fn.js);
27
+ extra flags are injected as variables in the function scope
28
+
29
+ Options
30
+ --api-key Microlink API key (defaults to MICROLINK_API_KEY env)
31
+ --header, -H Extra request header as 'Name: value' (repeatable)
32
+ --data JSON data rules for the extract command
33
+ --file Path to the code file for the function command
34
+ --help Show this help
35
+
36
+ Any other flag is passed as an option to the product, e.g. --fullPage,
37
+ --device 'iPhone 11', --waitUntil networkidle0, --selector article.
38
+
39
+ Examples
40
+ $ microlink markdown https://example.com
41
+ $ microlink screenshot https://example.com --fullPage
42
+ $ microlink logo https://github.com --square
43
+ $ microlink links https://example.com
44
+ $ microlink search "best coffee" --limit 10 --location es
45
+ $ microlink search "open source llm" --type news --period week
46
+ $ microlink extract https://microlink.io --data '{"image":{"selector":"meta[property=og:image]","attr":"content","type":"image"}}'
47
+ $ microlink function https://example.com --file ./fn.js
package/bin/index.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { readFileSync } = require('fs')
5
+ const path = require('path')
6
+ const jsome = require('jsome')
7
+ const mri = require('mri')
8
+
9
+ const create = require('../src')
10
+
11
+ const showHelp = () => {
12
+ console.log(readFileSync(path.join(__dirname, 'help.txt'), 'utf8'))
13
+ process.exit(0)
14
+ }
15
+
16
+ const parseHeaders = input => {
17
+ const headers = {}
18
+ for (const item of [].concat(input ?? [])) {
19
+ const index = String(item).indexOf(':')
20
+ if (index === -1) continue
21
+ headers[String(item).slice(0, index).trim().toLowerCase()] = String(item)
22
+ .slice(index + 1)
23
+ .trim()
24
+ }
25
+ return headers
26
+ }
27
+
28
+ const argv = mri(process.argv.slice(2), {
29
+ alias: { H: 'header' },
30
+ string: ['header', 'api-key', 'data', 'file']
31
+ })
32
+
33
+ const {
34
+ _: [command, target],
35
+ header,
36
+ help,
37
+ data,
38
+ file,
39
+ 'api-key': apiKeyFlag,
40
+ apiKey: apiKeyCamel,
41
+ ...flags
42
+ } = argv
43
+
44
+ if (help || !command) showHelp()
45
+
46
+ const apiKey = apiKeyFlag || apiKeyCamel || process.env.MICROLINK_API_KEY
47
+ const client = create(apiKey ? { apiKey } : {})
48
+
49
+ if (typeof client[command] !== 'function') {
50
+ console.error(
51
+ `Unknown command \`${command}\`. Run \`microlink --help\` to see the available commands.`
52
+ )
53
+ process.exit(1)
54
+ }
55
+
56
+ const options = { ...flags }
57
+ const headers = parseHeaders(header)
58
+ if (Object.keys(headers).length > 0) options.headers = headers
59
+
60
+ const invoke = () => {
61
+ if (command === 'extract') {
62
+ return client.extract(target, JSON.parse(data), options)
63
+ }
64
+ if (command === 'function' || command === 'run') {
65
+ const code = readFileSync(path.resolve(file), 'utf8')
66
+ return client.function(target, code, options)
67
+ }
68
+ return client[command](target, options)
69
+ }
70
+
71
+ Promise.resolve()
72
+ .then(invoke)
73
+ .then(result => {
74
+ if (typeof result === 'string') console.log(result)
75
+ else jsome(result)
76
+ process.exit(0)
77
+ })
78
+ .catch(error => {
79
+ console.error(error.message)
80
+ process.exit(1)
81
+ })
package/package.json CHANGED
@@ -1,13 +1,82 @@
1
1
  {
2
2
  "name": "microlink.io",
3
- "version": "0.0.0",
4
- "description": "",
5
- "main": "index.js",
6
- "keywords": [],
7
- "author": "",
8
- "license": "ISC",
9
- "type": "module",
3
+ "description": "The Microlink API organized into products, each returning a direct result",
4
+ "homepage": "https://github.com/microlinkhq/microlink",
5
+ "version": "0.0.2",
6
+ "types": "./src/index.d.ts",
7
+ "main": "./src/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.d.ts",
11
+ "require": "./src/index.js",
12
+ "import": "./src/main.mjs",
13
+ "default": "./src/main.mjs"
14
+ }
15
+ },
16
+ "bin": {
17
+ "microlink": "bin/index.js"
18
+ },
19
+ "author": {
20
+ "email": "hello@microlink.io",
21
+ "name": "microlink.io",
22
+ "url": "https://microlink.io"
23
+ },
24
+ "repository": {
25
+ "directory": "packages/core",
26
+ "type": "git",
27
+ "url": "git+https://github.com/microlinkhq/microlink.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/microlinkhq/microlink/issues"
31
+ },
32
+ "keywords": [
33
+ "api",
34
+ "extract",
35
+ "lighthouse",
36
+ "markdown",
37
+ "metadata",
38
+ "microlink",
39
+ "mql",
40
+ "pdf",
41
+ "scraper",
42
+ "screenshot",
43
+ "seo"
44
+ ],
45
+ "dependencies": {
46
+ "@microlink/function": "0.2.7",
47
+ "@microlink/google": "1.0.4",
48
+ "@microlink/mql": "0.17.0",
49
+ "jsome": "~2.5.0",
50
+ "mri": "~1.2.0"
51
+ },
52
+ "devDependencies": {
53
+ "ava": "latest",
54
+ "proxyquire": "latest",
55
+ "tinyspawn": "latest",
56
+ "tsd": "latest"
57
+ },
58
+ "engines": {
59
+ "node": ">= 24"
60
+ },
61
+ "files": [
62
+ "bin",
63
+ "src"
64
+ ],
10
65
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1"
12
- }
13
- }
66
+ "lint": "tsd",
67
+ "test": "ava"
68
+ },
69
+ "preferGlobal": true,
70
+ "license": "MIT",
71
+ "ava": {
72
+ "timeout": "2m"
73
+ },
74
+ "tsd": {
75
+ "compilerOptions": {
76
+ "module": "node16",
77
+ "moduleResolution": "node16"
78
+ },
79
+ "directory": "test"
80
+ },
81
+ "gitHead": "35ba2e58e85c61899d5abd23fca47a4b01ccbebf"
82
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,178 @@
1
+ import createGoogleClient from '@microlink/google'
2
+
3
+ type GoogleClient = ReturnType<typeof createGoogleClient>
4
+
5
+ /**
6
+ * Transport & top-level API query params. Unknown keys fall through
7
+ * to the API query string, so an index signature is provided.
8
+ */
9
+ interface Options {
10
+ apiKey?: string
11
+ endpoint?: string
12
+ headers?: Record<string, string>
13
+ adblock?: boolean
14
+ animations?: boolean
15
+ audio?: boolean
16
+ cacheKey?: string
17
+ click?: string | string[]
18
+ colorScheme?: 'light' | 'dark' | 'no-preference'
19
+ device?: string
20
+ filename?: string
21
+ force?: boolean
22
+ javascript?: boolean
23
+ meta?: boolean
24
+ modules?: string | string[]
25
+ prerender?: boolean | 'auto'
26
+ proxy?: string
27
+ retry?: number
28
+ scripts?: string | string[]
29
+ staleTtl?: string | number
30
+ styles?: string | string[]
31
+ timeout?: number
32
+ ttl?: string | number
33
+ video?: boolean
34
+ viewport?: Record<string, unknown>
35
+ waitForTimeout?: number
36
+ waitUntil?: string | string[]
37
+ [key: string]: unknown
38
+ }
39
+
40
+ interface ContentOptions extends Options {
41
+ selector?: string
42
+ selectorAll?: string | string[]
43
+ type?: string
44
+ }
45
+
46
+ interface CollectionOptions extends Options {
47
+ selector?: string
48
+ selectorAll?: string | string[]
49
+ attr?: string
50
+ type?: string
51
+ }
52
+
53
+ interface ScreenshotOptions extends Options {
54
+ fullPage?: boolean
55
+ type?: 'jpeg' | 'png'
56
+ overlay?: Record<string, unknown>
57
+ element?: string
58
+ omitBackground?: boolean
59
+ optimizeForSpeed?: boolean
60
+ codeScheme?: string
61
+ animated?: boolean
62
+ palette?: boolean
63
+ }
64
+
65
+ interface PdfOptions extends Options {
66
+ format?: string
67
+ margin?: string | Record<string, string | number>
68
+ scale?: number
69
+ landscape?: boolean
70
+ pageRanges?: string
71
+ width?: string | number
72
+ height?: string | number
73
+ printBackground?: boolean
74
+ }
75
+
76
+ interface EmbedOptions extends Options {
77
+ maxWidth?: number
78
+ maxHeight?: number
79
+ }
80
+
81
+ interface LogoOptions extends Options {
82
+ square?: boolean
83
+ }
84
+
85
+ interface LighthouseOptions extends Options {
86
+ onlyCategories?: string[]
87
+ onlyAudits?: string[]
88
+ skipAudits?: string[]
89
+ output?: string | string[]
90
+ }
91
+
92
+ /** Rich asset object (screenshot, pdf, logo, image rules, ...). */
93
+ interface Asset {
94
+ url: string
95
+ type?: string
96
+ size?: number
97
+ size_pretty?: string
98
+ width?: number
99
+ height?: number
100
+ [key: string]: unknown
101
+ }
102
+
103
+ interface Metadata {
104
+ title?: string | null
105
+ description?: string | null
106
+ url?: string | null
107
+ [key: string]: unknown
108
+ }
109
+
110
+ interface Embed {
111
+ html: string
112
+ scripts?: unknown[]
113
+ [key: string]: unknown
114
+ }
115
+
116
+ type FunctionInput = string | ((args: Record<string, unknown>) => unknown)
117
+
118
+ interface FunctionResult<T = unknown> {
119
+ isFulfilled: boolean
120
+ value: T
121
+ profiling: Record<string, unknown>
122
+ logging: Record<string, unknown>
123
+ }
124
+
125
+ export declare class MicrolinkError extends Error {
126
+ status?: string
127
+ code?: string
128
+ statusCode?: number
129
+ description?: string
130
+ url?: string
131
+ headers?: Record<string, string>
132
+ more?: string
133
+ constructor (props: Record<string, unknown>)
134
+ }
135
+
136
+ interface MicrolinkClient {
137
+ metadata (url: string, options?: Options): Promise<Metadata>
138
+ logo (url: string, options?: LogoOptions): Promise<Asset>
139
+ markdown (url: string, options?: ContentOptions): Promise<string>
140
+ html (url: string, options?: ContentOptions): Promise<string>
141
+ text (url: string, options?: ContentOptions): Promise<string>
142
+ video (url: string, options?: Options): Promise<Asset>
143
+ audio (url: string, options?: Options): Promise<Asset>
144
+ emails (url: string, options?: Options): Promise<string[]>
145
+ links (url: string, options?: CollectionOptions): Promise<string[]>
146
+ images (url: string, options?: CollectionOptions): Promise<string[]>
147
+ videos (url: string, options?: CollectionOptions): Promise<string[]>
148
+ audios (url: string, options?: CollectionOptions): Promise<string[]>
149
+ extract (
150
+ url: string,
151
+ rules: Record<string, unknown>,
152
+ options?: Options
153
+ ): Promise<Record<string, unknown>>
154
+ screenshot (url: string, options?: ScreenshotOptions): Promise<Asset>
155
+ pdf (url: string, options?: PdfOptions): Promise<Asset>
156
+ embed (url: string, options?: EmbedOptions): Promise<Embed>
157
+ technologies (url: string, options?: Options): Promise<unknown[]>
158
+ lighthouse (
159
+ url: string,
160
+ options?: LighthouseOptions
161
+ ): Promise<Record<string, unknown>>
162
+ search: GoogleClient
163
+ function<T = unknown> (
164
+ url: string,
165
+ code: FunctionInput,
166
+ options?: Options
167
+ ): Promise<FunctionResult<T>>
168
+ run: MicrolinkClient['function']
169
+ }
170
+
171
+ interface create {
172
+ (options?: Options): MicrolinkClient
173
+ MicrolinkError: typeof MicrolinkError
174
+ }
175
+
176
+ declare const create: create
177
+
178
+ export default create
package/src/index.js ADDED
@@ -0,0 +1,195 @@
1
+ 'use strict'
2
+
3
+ const createGoogleClient = require('@microlink/google')
4
+ const fn = require('@microlink/function')
5
+ const mql = require('@microlink/mql')
6
+
7
+ const CONTENT_KEYS = ['selector', 'selectorAll', 'type']
8
+
9
+ const COLLECTION_KEYS = ['selector', 'selectorAll', 'attr', 'type']
10
+
11
+ const SCREENSHOT_KEYS = [
12
+ 'fullPage',
13
+ 'type',
14
+ 'overlay',
15
+ 'element',
16
+ 'omitBackground',
17
+ 'optimizeForSpeed',
18
+ 'codeScheme',
19
+ 'animated',
20
+ 'palette'
21
+ ]
22
+
23
+ const PDF_KEYS = [
24
+ 'format',
25
+ 'margin',
26
+ 'scale',
27
+ 'landscape',
28
+ 'pageRanges',
29
+ 'width',
30
+ 'height',
31
+ 'printBackground'
32
+ ]
33
+
34
+ const EMBED_KEYS = ['maxWidth', 'maxHeight']
35
+
36
+ const LOGO_KEYS = ['square']
37
+
38
+ const LIGHTHOUSE_KEYS = ['onlyCategories', 'onlyAudits', 'skipAudits', 'output']
39
+
40
+ const isEmpty = obj => Object.keys(obj).length === 0
41
+
42
+ const create = (ctx = {}) => {
43
+ /**
44
+ * Split the single options bag into the three destinations mql has:
45
+ * `got.headers` (HTTP layer, 3rd arg), `sub` (capability nested keys)
46
+ * and `top` (top-level query params, 2nd arg). Client context is
47
+ * merged first so per-call options can override it.
48
+ */
49
+ const route = (opts, nested = []) => {
50
+ const top = {}
51
+ const sub = {}
52
+ let got
53
+ for (const [key, value] of Object.entries({ ...ctx, ...opts })) {
54
+ if (key === 'headers') got = { headers: value }
55
+ else if (nested.includes(key)) sub[key] = value
56
+ else top[key] = value
57
+ }
58
+ return { top, sub, got }
59
+ }
60
+
61
+ const content = field => (url, options) => {
62
+ const { top, sub, got } = route(options, CONTENT_KEYS)
63
+ return mql(
64
+ url,
65
+ { ...top, meta: false, data: { [field]: { attr: field, ...sub } } },
66
+ got
67
+ ).then(({ data }) => data[field])
68
+ }
69
+
70
+ const collection = (field, rule) => (url, options) => {
71
+ const { top, sub, got } = route(options, COLLECTION_KEYS)
72
+ return mql(
73
+ url,
74
+ { ...top, meta: false, data: { [field]: { ...rule, ...sub } } },
75
+ got
76
+ ).then(({ data }) => data[field] ?? [])
77
+ }
78
+
79
+ const capability = (field, nested) => (url, options) => {
80
+ const { top, sub, got } = route(options, nested)
81
+ return mql(
82
+ url,
83
+ { ...top, meta: false, [field]: isEmpty(sub) ? true : sub },
84
+ got
85
+ ).then(({ data }) => data[field])
86
+ }
87
+
88
+ /* Primary media detection (`data.video` / `data.audio`). */
89
+ const media = field => (url, options) => {
90
+ const { top, got } = route(options)
91
+ return mql(url, { ...top, meta: false, [field]: true }, got).then(
92
+ ({ data }) => data[field]
93
+ )
94
+ }
95
+
96
+ const googleClient = createGoogleClient()
97
+
98
+ const run = (url, code, options) => {
99
+ const { top, got } = route(options)
100
+ return fn(code, top, got)(url)
101
+ }
102
+
103
+ return {
104
+ metadata: (url, options) => {
105
+ const { top, got } = route(options)
106
+ return mql(url, top, got).then(({ data }) => data)
107
+ },
108
+ logo: (url, options) => {
109
+ const { top, sub, got } = route(options, LOGO_KEYS)
110
+ return mql(
111
+ url,
112
+ { ...top, meta: isEmpty(sub) ? true : { logo: sub } },
113
+ got
114
+ ).then(({ data }) => data.logo)
115
+ },
116
+ markdown: content('markdown'),
117
+ html: content('html'),
118
+ text: content('text'),
119
+ video: media('video'),
120
+ audio: media('audio'),
121
+ links: collection('links', { selectorAll: 'a', attr: 'href', type: 'url' }),
122
+ images: collection('images', {
123
+ selectorAll: 'img',
124
+ attr: 'src',
125
+ type: 'url'
126
+ }),
127
+ videos: collection('videos', {
128
+ selectorAll: ['video[src]', 'video source[src]'],
129
+ attr: 'src',
130
+ type: 'url'
131
+ }),
132
+ audios: collection('audios', {
133
+ selectorAll: ['audio[src]', 'audio source[src]'],
134
+ attr: 'src',
135
+ type: 'url'
136
+ }),
137
+ emails: collection('emails', {
138
+ selector: 'html',
139
+ attr: 'html',
140
+ type: 'email'
141
+ }),
142
+ extract: (url, rules, options) => {
143
+ const { top, got } = route(options)
144
+ return mql(url, { ...top, meta: false, data: rules }, got).then(
145
+ ({ data }) => data
146
+ )
147
+ },
148
+ screenshot: capability('screenshot', SCREENSHOT_KEYS),
149
+ pdf: capability('pdf', PDF_KEYS),
150
+ embed: (url, options) => {
151
+ const { top, sub, got } = route(options, EMBED_KEYS)
152
+ return mql(
153
+ url,
154
+ { ...top, meta: false, iframe: isEmpty(sub) ? true : sub },
155
+ got
156
+ ).then(({ data }) => data.iframe)
157
+ },
158
+ technologies: (url, options) => {
159
+ const { top, got } = route(options)
160
+ return mql(
161
+ url,
162
+ {
163
+ ...top,
164
+ meta: false,
165
+ insights: { technologies: true, lighthouse: false }
166
+ },
167
+ got
168
+ ).then(({ data }) => data.insights.technologies)
169
+ },
170
+ lighthouse: (url, options) => {
171
+ const { top, sub, got } = route(options, LIGHTHOUSE_KEYS)
172
+ return mql(
173
+ url,
174
+ {
175
+ ...top,
176
+ meta: false,
177
+ insights: {
178
+ technologies: false,
179
+ lighthouse: isEmpty(sub) ? true : sub
180
+ }
181
+ },
182
+ got
183
+ ).then(({ data }) => data.insights.lighthouse)
184
+ },
185
+ search: (query, options) => {
186
+ const { top } = route(options)
187
+ return googleClient(query, top)
188
+ },
189
+ function: run,
190
+ run
191
+ }
192
+ }
193
+
194
+ module.exports = create
195
+ module.exports.MicrolinkError = mql.MicrolinkError
package/src/main.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import create from './index.js'
2
+
3
+ export const MicrolinkError = create.MicrolinkError
4
+
5
+ export default create