mirror of
https://github.com/NohamR/pdfy.git
synced 2026-10-11 02:39:58 +00:00
Add automatic extension download
This commit is contained in:
@@ -18,12 +18,7 @@ Convert web articles to PDF using the [Reader View](https://chromewebstore.googl
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Extract the Reader View extension (get it using [CRX Downloader](https://chromewebstore.google.com/detail/crx-extractordownloader/ajkhmmldknmfjnmeedkbkkojgobmljda)):
|
The Reader View extension is downloaded and extracted automatically on first run.
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p extension/reader-view
|
|
||||||
unzip extension/Reader-View-Chrome-Web-Store.zip -d extension/reader-view
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,31 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import puppeteer from 'puppeteer'
|
import puppeteer from 'puppeteer'
|
||||||
|
import { downloadExtension } from './download-extension.js'
|
||||||
import logger from './logger.js'
|
import logger from './logger.js'
|
||||||
|
|
||||||
const EXTENSION_DIR = path.resolve('./extension/reader-view')
|
const EXTENSION_DIR = path.resolve('./extension/reader-view')
|
||||||
const MANIFEST_PATH = path.join(EXTENSION_DIR, 'manifest.json')
|
const MANIFEST_PATH = path.join(EXTENSION_DIR, 'manifest.json')
|
||||||
const READABILITY_PATH = path.join(EXTENSION_DIR, 'data/inject/Readability.js')
|
const READABILITY_PATH = path.join(EXTENSION_DIR, 'data/inject/Readability.js')
|
||||||
|
|
||||||
export function checkExtension () {
|
export async function checkExtension () {
|
||||||
if (!fs.existsSync(MANIFEST_PATH)) {
|
if (!fs.existsSync(MANIFEST_PATH)) {
|
||||||
logger.error(
|
logger.info('Extension not found. Downloading automatically...')
|
||||||
`Extension not found at: "${EXTENSION_DIR}"\n` +
|
try {
|
||||||
'Please extract the extension first:\n' +
|
await downloadExtension({ logger })
|
||||||
' mkdir -p extension/reader-view\n' +
|
} catch (err) {
|
||||||
' unzip extension/Reader-View-Chrome-Web-Store.zip -d extension/reader-view'
|
logger.error(`Failed to download extension: ${err.message}`)
|
||||||
)
|
process.exit(1)
|
||||||
process.exit(1)
|
}
|
||||||
|
if (!fs.existsSync(MANIFEST_PATH)) {
|
||||||
|
logger.error('Download completed but extension not found. Extraction may have failed.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(READABILITY_PATH)) {
|
if (!fs.existsSync(READABILITY_PATH)) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Readability.js not found at: "${READABILITY_PATH}"\n` +
|
`Readability.js not found at: "${READABILITY_PATH}"\n` +
|
||||||
'The extension may be incomplete. Please re-extract it.'
|
'The extension may be incomplete.'
|
||||||
)
|
)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/download-extension.js
Normal file
84
src/download-extension.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import process from 'node:process'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const EXTENSION_DIR = path.resolve(__dirname, '..', 'extension')
|
||||||
|
const EXTENSION_ID = 'ecabifbgmdmgdllomnfinbmaellmclnh'
|
||||||
|
const CRX_PATH = path.join(EXTENSION_DIR, 'Reader-View-Chrome-Web-Store.crx')
|
||||||
|
const ZIP_PATH = path.join(EXTENSION_DIR, 'Reader-View-Chrome-Web-Store.zip')
|
||||||
|
const EXTRACT_DIR = path.join(EXTENSION_DIR, 'reader-view')
|
||||||
|
|
||||||
|
export async function downloadExtension ({ logger } = {}) {
|
||||||
|
const log = logger || console
|
||||||
|
|
||||||
|
log.info('Downloading Reader View extension from Chrome Web Store...')
|
||||||
|
|
||||||
|
const url =
|
||||||
|
'https://clients2.google.com/service/update2/crx' +
|
||||||
|
'?response=redirect' +
|
||||||
|
'&prodversion=150.0.0.0' +
|
||||||
|
`&x=id%3D${EXTENSION_ID}%26installsource%3Dondemand%26uc` +
|
||||||
|
'&nacl_arch=arm' +
|
||||||
|
'&acceptformat=crx3'
|
||||||
|
|
||||||
|
const response = await fetch(url, { redirect: 'follow' })
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to download CRX: HTTP ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const crx = new Uint8Array(await response.arrayBuffer())
|
||||||
|
log.info(`Downloaded ${(crx.length / 1024).toFixed(0)} KB`)
|
||||||
|
|
||||||
|
if (crx[0] !== 0x43 || crx[1] !== 0x72 || crx[2] !== 0x32 || crx[3] !== 0x34) {
|
||||||
|
throw new Error('Not a valid CRX file (bad magic number)')
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = new DataView(crx.buffer, crx.byteOffset, crx.byteLength)
|
||||||
|
const version = view.getUint32(4, true)
|
||||||
|
|
||||||
|
let zipOffset
|
||||||
|
if (version === 2) {
|
||||||
|
const publicKeyLength = view.getUint32(8, true)
|
||||||
|
const signatureLength = view.getUint32(12, true)
|
||||||
|
zipOffset = 16 + publicKeyLength + signatureLength
|
||||||
|
} else if (version === 3) {
|
||||||
|
const headerLength = view.getUint32(8, true)
|
||||||
|
zipOffset = 12 + headerLength
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported CRX version: ${version}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const zip = crx.slice(zipOffset)
|
||||||
|
if (zip[0] !== 0x50 || zip[1] !== 0x4b) {
|
||||||
|
throw new Error('Failed to locate ZIP payload in CRX')
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.mkdir(EXTENSION_DIR, { recursive: true })
|
||||||
|
await fs.writeFile(CRX_PATH, crx)
|
||||||
|
await fs.writeFile(ZIP_PATH, zip)
|
||||||
|
|
||||||
|
log.info('Extracting extension...')
|
||||||
|
await fs.mkdir(EXTRACT_DIR, { recursive: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(`unzip -o "${ZIP_PATH}" -d "${EXTRACT_DIR}"`, { stdio: 'pipe' })
|
||||||
|
} catch {
|
||||||
|
throw new Error(
|
||||||
|
'Failed to extract the extension. Make sure "unzip" is installed.\n' +
|
||||||
|
`You can manually extract it: unzip "${ZIP_PATH}" -d "${EXTRACT_DIR}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('Extension ready.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
if (process.argv[1] && path.resolve(process.argv[1]) === __filename) {
|
||||||
|
downloadExtension().catch((err) => {
|
||||||
|
console.error('Download failed:', err.message)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ async function promptForInput () {
|
|||||||
async function main () {
|
async function main () {
|
||||||
const { url, opts } = parseArgs()
|
const { url, opts } = parseArgs()
|
||||||
|
|
||||||
checkExtension()
|
await checkExtension()
|
||||||
|
|
||||||
let preferences = {}
|
let preferences = {}
|
||||||
if (opts.prefs) {
|
if (opts.prefs) {
|
||||||
|
|||||||
Reference in New Issue
Block a user