website-api 1.0.2 ā 1.0.4
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/dist/bin/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as o}from"node:fs";import{dirname as e,join as r}from"node:path";import{fileURLToPath as n}from"node:url";import{program as t}from"commander";import s from"chalk";import i from"cli-table3";import{getDefaultChromeDir as
|
|
2
|
+
import{readFileSync as o}from"node:fs";import{dirname as e,join as r}from"node:path";import{fileURLToPath as n}from"node:url";import{program as t}from"commander";import s from"chalk";import i from"cli-table3";import{getDefaultChromeDir as c}from"chrome-tools";import{queryWebsite as l,websites as a,loadAdapters as p}from"../src/website-api.js";const d=r(e(n(import.meta.url)),"..","..","package.json"),{version:m}=JSON.parse(o(d,"utf8"));process.on("unhandledRejection",o=>{console.error(o instanceof Error?o.message:"command not found"),process.exit(1)}),t.name("website-api").description("CLI to query website APIs using decrypted Chrome cookies on macOS").version(m),t.option("--profile <name>","specific Chrome profile directory (e.g., 'Default', 'Profile 1')").option("--current-profile","Show the currently resolved/selected Chrome profile directory and name").option("-u, --user-agent <string>","custom User-Agent header for HTTP requests"),t.command("list").description("List all supported website API adapters").action(async()=>{await p(),console.log(s.bold.green("\nš Supported Website APIs:\n"));const o=new i({head:[s.bold.cyan("ID"),s.bold.cyan("Name"),s.bold.cyan("Domain"),s.bold.cyan("Description")],colWidths:[18,25,20,50],wordWrap:!0,style:{head:[],border:[]}});for(const e of a)o.push([s.yellow(e.id),e.name,s.underline(e.domain),e.description]);console.log(o.toString()),console.log(`\nTo run an API query, execute: ${s.bold.cyan("npx website-api <id>")}\n`)}),t.argument("[website]","website ID or domain to query (e.g. 'chatgpt.com')").action(async o=>{const e=t.opts();if(e.currentProfile){const o=process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||c(),r=e.profile||process.env.PROFILE_NAME||"Default";return console.log(s.bold.green("\nš¤ Currently Resolved Profile:\n")),console.log(` ${s.bold("Path:")} ${o}`),void console.log(` ${s.bold("Name:")} ${r}\n`)}if(o)try{const r=await l(o,{profile:e.profile,userAgent:e.userAgent});console.log(JSON.stringify(r,null,2))}catch(o){console.error(o instanceof Error?o.message:"command not found"),process.exit(1)}else t.outputHelp()}),t.parse(process.argv);
|
package/dist/src/base-adapter.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const t={buildCookieString:t=>t.map(t=>`${t.name}=${t.value}`).join("; "),resolveUserAgent:t=>t.userAgent||process.env.userAgent||process.env.USER_AGENT||"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",async fetchJson(t,e){const n=await fetch(t,e);if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const r=await n.text();try{return JSON.parse(r)}catch{return{response:r}}},async fetchText(t,e){const n=await fetch(t,e);if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);return n.text()},async fetchHtml(t,e){return this.fetchText(t,e)}};async function e(t,e){if(!this.endpoints||0===this.endpoints.length)throw new Error(`Adapter "${this.id}" has no endpoints defined and no fetchData override`);const n=this.endpoints[0],r=this.buildCookieString(t),s=this.resolveUserAgent(e),o=await async function(t,e,n){const r=await fetch(t.url,{method:t.method||"GET",headers:{Cookie:e,"User-Agent":n,Accept:"html"===t.responseType?"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8":"text"===t.responseType?"text/plain,*/*;q=0.8":"application/json, text/plain, */*",...t.headers}});if(!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);const s=await r.text(),o=r.headers.get("content-type")?.toLowerCase()??"";if("json"===t.responseType||"text"!==t.responseType&&"html"!==t.responseType&&(o.includes("application/json")||o.includes("+json")))try{return JSON.parse(s)}catch(e){throw new Error(`Expected JSON from ${t.url}, but received invalid JSON: ${e instanceof Error?e.message:String(e)}`)}return s}(n,r,s);return n.transform?n.transform.call(this,o,t,e):o}export function defineAdapter(n){const r={...n,...t,fetchData:n.fetchData??e};return r.fetchData=r.fetchData.bind(r),r}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ export interface Endpoint {
|
|
|
9
9
|
method?: string;
|
|
10
10
|
/** Additional headers to include in the request. */
|
|
11
11
|
headers?: Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Expected response body type. Defaults to auto-detect from content-type.
|
|
14
|
+
* Use "html" for text/html responses that need parsing.
|
|
15
|
+
*/
|
|
16
|
+
responseType?: "auto" | "json" | "text" | "html";
|
|
17
|
+
/** Optional post-processing step for the parsed response body. */
|
|
18
|
+
transform?: (this: WebsiteAdapter, body: unknown, cookies: CookieEntry[], options: QueryOptions) => Promise<unknown> | unknown;
|
|
12
19
|
}
|
|
13
20
|
/**
|
|
14
21
|
* Configuration for defining a website adapter.
|
|
@@ -39,6 +46,10 @@ export interface WebsiteAdapter extends AdapterConfig {
|
|
|
39
46
|
resolveUserAgent(options: QueryOptions): string;
|
|
40
47
|
/** Fetches a URL and returns parsed JSON, with error handling. */
|
|
41
48
|
fetchJson(url: string, init?: RequestInit): Promise<any>;
|
|
49
|
+
/** Fetches a URL and returns the raw response text, with error handling. */
|
|
50
|
+
fetchText(url: string, init?: RequestInit): Promise<string>;
|
|
51
|
+
/** Fetches an HTML document as text, with error handling. */
|
|
52
|
+
fetchHtml(url: string, init?: RequestInit): Promise<string>;
|
|
42
53
|
}
|
|
43
54
|
/**
|
|
44
55
|
* Options passed when querying a website API.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineAdapter as e}from"../../base-adapter.js";export default e({id:"ollama-usage",name:"Ollama Usage",domain:"ollama.com",description:"Fetches Ollama plan and usage details from the authenticated settings page.",
|
|
1
|
+
import{defineAdapter as e}from"../../base-adapter.js";export default e({id:"ollama-usage",name:"Ollama Usage",domain:"ollama.com",description:"Fetches Ollama plan and usage details from the authenticated settings page.",endpoints:[{url:"https://ollama.com/settings",responseType:"html",headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},transform(e){const t="string"==typeof e?e:String(e),n=a(t,"Session usage"),i=a(t,"Weekly usage");return{time:(new Date).toISOString(),Plan:s(t),"Session Usage":n.usage,"Session Reset":n.reset,"Weekly Usage":i.usage,"Weekly Reset":i.reset}}}]});function s(e){const s=e.match(/Cloud Usage[\s\S]*?<\/span>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span/i);return s?.[1]?.trim()??"unknown"}function a(e,s){const a=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const t=new RegExp(`<div[\\s\\S]*?<span[^>]*>\\s*${a}\\s*<\\/span>[\\s\\S]*?aria-label="${a}\\s+([^"]+)"[\\s\\S]*?data-time="([^"]+)"`,"i"),n=e.match(t);return{usage:n?.[1]?.replace(/\s+used$/i,"").trim()??"unknown",reset:n?.[2]?.trim()??"unknown"}}
|
package/dist/src/website-api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdirSync as
|
|
1
|
+
import{readdirSync as t}from"node:fs";import{dirname as o,join as e}from"node:path";import{fileURLToPath as r,pathToFileURL as n}from"node:url";import{getCookies as i}from"chrome-tools";import{loadEnv as s}from"./env.js";import{createUniversalAdapter as c}from"./universal-adapter.js";s();const a=o(r(import.meta.url)),f=e(a,"website");export let websites=[];export async function loadAdapters(){if(!(websites.length>0))try{const o=t(f,{withFileTypes:!0});for(const r of o){if(!r.isDirectory())continue;const o=e(f,r.name),i=t(o).find(t=>t.endsWith("-adapter.js"));if(!i)continue;const s=n(e(o,i)).href,c=(await import(s)).default;c?.id&&websites.push(c)}}catch(t){}}export function getWebsite(t){if(!t)return null;const o=t.toLowerCase().trim();return websites.find(t=>t.id.toLowerCase()===o||t.id.toLowerCase().replace(".com","")===o)??null}export async function queryWebsite(t,o={}){await loadAdapters();const e=process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||void 0,r=o.profile||process.env.PROFILE_NAME||void 0;let n,s=getWebsite(t);if(!s){if(!function(t){return t.startsWith("http://")||t.startsWith("https://")||t.includes(".")||t.includes("/")||t.includes(":")}(t))throw new Error("command not found");if(s=c(t),!s)throw new Error("command not found")}try{n=i({chromeDir:e,profile:r,domain:s.domain,decrypt:!0})}catch{throw new Error("No login found in browser")}if(!n||0===n.length)throw new Error("No login found in browser");return s.fetchData(n,o)}
|