website-api 1.0.4 → 1.0.6
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 +1 -1
- package/dist/src/adapter/base-adapter.d.ts +41 -0
- package/dist/src/adapter/base-adapter.js +1 -0
- package/dist/src/adapter/playwright-attatch-chrome-adapter.d.ts +16 -0
- package/dist/src/adapter/playwright-attatch-chrome-adapter.js +1 -0
- package/dist/src/adapter/playwright-core.d.ts +35 -0
- package/dist/src/adapter/playwright-core.js +1 -0
- package/dist/src/{universal-adapter.d.ts → adapter/universal-adapter.d.ts} +1 -1
- package/dist/src/types.d.ts +43 -1
- package/dist/src/util/args-parser.d.ts +13 -0
- package/dist/src/util/args-parser.js +1 -0
- package/dist/src/util/google-json.d.ts +32 -0
- package/dist/src/util/google-json.js +1 -0
- package/dist/src/util/login-helper.d.ts +22 -0
- package/dist/src/util/login-helper.js +1 -0
- package/dist/src/website/chase.com/account-helper.d.ts +20 -0
- package/dist/src/website/chase.com/account-helper.js +1 -0
- package/dist/src/website/chase.com/chase-adapter.d.ts +43 -0
- package/dist/src/website/chase.com/chase-adapter.js +1 -0
- package/dist/src/website/chase.com/download-helper.d.ts +33 -0
- package/dist/src/website/chase.com/download-helper.js +1 -0
- package/dist/src/website/chatgpt.com/chatgpt-adapter.js +1 -1
- package/dist/src/website/cursor.com/cursor-adapter.js +1 -1
- package/dist/src/website/example.com/example-adapter.d.ts +12 -0
- package/dist/src/website/example.com/example-adapter.js +1 -0
- package/dist/src/website/gemini.google.com/gemini-adapter.d.ts +12 -0
- package/dist/src/website/gemini.google.com/gemini-adapter.js +1 -0
- package/dist/src/website/google.com/google-adapter.d.ts +62 -0
- package/dist/src/website/google.com/google-adapter.js +1 -0
- package/dist/src/website/ollama.com/ollama-adapter.js +1 -1
- package/dist/src/website/perplexity.ai/perplexity-adapter.d.ts +2 -0
- package/dist/src/website/perplexity.ai/perplexity-adapter.js +1 -0
- package/dist/src/website/pseg.com/pseg-adapter.d.ts +45 -0
- package/dist/src/website/pseg.com/pseg-adapter.js +1 -0
- package/dist/src/website-api.js +1 -1
- package/package.json +5 -4
- package/dist/src/base-adapter.d.ts +0 -19
- package/dist/src/base-adapter.js +0 -1
- /package/dist/src/{universal-adapter.js → adapter/universal-adapter.js} +0 -0
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
|
|
2
|
+
import{readFileSync as o,writeFileSync as e}from"node:fs";import{dirname as n,join as t}from"node:path";import{fileURLToPath as s}from"node:url";import{program as r}from"commander";import i from"chalk";import l from"cli-table3";import{getDefaultChromeDir as a}from"chrome-tools";import{queryWebsite as c,websites as p,loadAdapters as d,getWebsite as f}from"../src/website-api.js";import{parseArgsForWebsite as u}from"../src/util/args-parser.js";const m=t(n(s(import.meta.url)),"..","..","package.json"),{version:g}=JSON.parse(o(m,"utf8"));process.on("unhandledRejection",o=>{console.error(o instanceof Error?o.message:"command not found"),process.exit(1)}),async function(){const o=process.argv.slice(2),n=o.find(o=>!o.startsWith("-"));if(n&&"list"!==n){await d();const t=f(n);if(t){const s=o.filter((e,t)=>t!==o.indexOf(n));let r;try{r=u(t.positionals,t.parameters,s)}catch(o){console.error(i.red(`Error: ${o instanceof Error?o.message:String(o)}`)),console.log(`Run ${i.cyan(`npx website-api ${t.id} --help`)} for usage details.`),process.exit(1)}if(r.helpRequested)return void function(o){console.log(i.bold.green(`\n🌐 Website API: ${i.white(o.name)} (${i.yellow(o.id)})\n`)),console.log(` ${i.italic(o.description)}\n`);let e=`npx website-api ${o.id}`;if(o.positionals&&o.positionals.length>0)for(const n of o.positionals)e+=n.required?` <${n.name}>`:` [${n.name}]`;if(e+=" [options]",console.log(`${i.bold("Usage:")} ${i.cyan(e)}\n`),o.positionals&&o.positionals.length>0){console.log(i.bold("Positional Arguments:"));for(const e of o.positionals)console.log(` ${i.cyan(e.name.padEnd(15))} ${e.description}`);console.log()}console.log(i.bold("Options:"));const n=[...o.parameters||[],{name:"profile",type:"string",description:"specific Chrome profile directory (e.g., 'Default')"},{name:"user-agent",type:"string",description:"custom User-Agent header for HTTP requests",short:"u"},{name:"debug",type:"boolean",description:"Print full HTTP request and response bodies for debugging"},{name:"help",type:"boolean",description:"Show help for this website adapter",short:"h"}];for(const o of n){let e=`--${o.name}`;"boolean"!==o.type&&(e+=" <value>"),e=o.short?`-${o.short}, ${e}`:` ${e}`;const n=void 0!==o.default?` (default: ${o.default})`:"";console.log(` ${i.yellow(e.padEnd(28))} ${o.description}${i.gray(n)}`)}console.log()}(t);try{const o=await c(t.id,r.options);let n;if(r.options.text&&o&&"object"==typeof o){const e=o.answer||o.text;n=void 0!==e?String(e):JSON.stringify(o,null,2)}else n="string"==typeof o?o:JSON.stringify(o,null,2);r.options.out?(e(r.options.out,n+"\n","utf8"),console.log(i.green(`Success! Decoded response written to ${r.options.out}`))):console.log(n)}catch(o){console.error(i.red(o instanceof Error?o.message:"command not found")),process.exit(1)}return}}r.name("website-api").description("CLI to query website APIs using decrypted Chrome cookies on macOS").version(g),r.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"),r.option("--debug","Print full HTTP request and response bodies for debugging"),r.command("list").description("List all supported website API adapters").action(async()=>{await d(),console.log(i.bold.green("\n🌐 Supported Website APIs:\n"));const o=new l({head:[i.bold.cyan("ID"),i.bold.cyan("Name"),i.bold.cyan("Domain"),i.bold.cyan("Description")],colWidths:[18,25,20,50],wordWrap:!0,style:{head:[],border:[]}});for(const e of p)o.push([i.yellow(e.id),e.name,i.underline(e.domain),e.description]);console.log(o.toString()),console.log(`\nTo run an API query, execute: ${i.bold.cyan("npx website-api <id>")}\n`)}),r.argument("[website]","website ID or domain to query (e.g. 'chatgpt.com')").action(async o=>{const e=r.opts();if(e.currentProfile){const o=process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||a(),n=e.profile||process.env.PROFILE_NAME||"Default";return console.log(i.bold.green("\n👤 Currently Resolved Profile:\n")),console.log(` ${i.bold("Path:")} ${o}`),void console.log(` ${i.bold("Name:")} ${n}\n`)}o?(console.error(i.red(`Error: website adapter "${o}" not found.`)),console.log(`Run ${i.cyan("npx website-api list")} to see all supported adapters.`),process.exit(1)):r.outputHelp()}),r.parse(process.argv)}().catch(o=>{console.error(o instanceof Error?o.message:"command not found"),process.exit(1)});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CookieEntry } from "chrome-tools";
|
|
2
|
+
import type { AdapterConfig, QueryOptions, WebsiteAdapter, Endpoint } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Base class for all website adapters, enabling class-based inheritance and clean reuse.
|
|
5
|
+
*/
|
|
6
|
+
export declare class BaseAdapter implements WebsiteAdapter {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
domain: string;
|
|
10
|
+
description: string;
|
|
11
|
+
endpoints?: Endpoint[];
|
|
12
|
+
optionalCookies?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Internal reference to transient options. Used to check the debug flag during fetch calls.
|
|
15
|
+
*/
|
|
16
|
+
_lastQueryOptions?: QueryOptions;
|
|
17
|
+
buildCookieString(cookies: CookieEntry[]): string;
|
|
18
|
+
resolveUserAgent(options: QueryOptions): string;
|
|
19
|
+
resolveCredentials(options: QueryOptions): {
|
|
20
|
+
username: string;
|
|
21
|
+
password: string;
|
|
22
|
+
};
|
|
23
|
+
resolveCookies(options: QueryOptions): CookieEntry[];
|
|
24
|
+
/**
|
|
25
|
+
* Centralized HTTP fetch handler.
|
|
26
|
+
* Unifies request setup, response parsing, debug logging, and error handling.
|
|
27
|
+
*/
|
|
28
|
+
private _fetch;
|
|
29
|
+
fetchText(url: string, init?: RequestInit): Promise<string>;
|
|
30
|
+
fetchHtml(url: string, init?: RequestInit): Promise<string>;
|
|
31
|
+
fetchJson(url: string, init?: RequestInit): Promise<any>;
|
|
32
|
+
/**
|
|
33
|
+
* Internal helper to fetch a specific endpoint with type-based accepting headers and content-type parsing.
|
|
34
|
+
*/
|
|
35
|
+
private _fetchEndpoint;
|
|
36
|
+
fetchData(cookies: CookieEntry[], options: QueryOptions): Promise<unknown>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Factory function to create a fully-equipped WebsiteAdapter from a minimal config.
|
|
40
|
+
*/
|
|
41
|
+
export declare function defineAdapter(config: AdapterConfig): WebsiteAdapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getCookies as e,getPasswords as t}from"chrome-tools";export class BaseAdapter{id;name;domain;description;endpoints;optionalCookies;_lastQueryOptions;buildCookieString(e){return e.map(e=>`${e.name}=${e.value}`).join("; ")}resolveUserAgent(e){return e.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"}resolveCredentials(e){const o=e.profilePath||process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||void 0,s=e.profile||process.env.PROFILE_NAME||void 0;let r=t({chromeDir:o,profile:s,search:this.domain});if(!r||0===r.length){const e=this.domain.split("."),n=e[e.length-2]||this.domain;r=t({chromeDir:o,profile:s,search:n})}if(!r||0===r.length)throw new Error(`No saved passwords found in Chrome for '${this.domain}'`);const{username:n,password:i}=r[0];if(!n||!i)throw new Error(`Found credentials for '${this.domain}' but username or password was empty`);return{username:n,password:i}}resolveCookies(t){const o=t.profilePath||process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||void 0,s=t.profile||process.env.PROFILE_NAME||void 0;let r=[];try{r=e({chromeDir:o,profile:s,domain:this.domain,decrypt:!0})}catch(e){if(!this.optionalCookies)throw new Error("No login found in browser")}if(!(r&&0!==r.length||this.optionalCookies))throw new Error("No login found in browser");return r}async _fetch(e,t){const o=this._lastQueryOptions?.debug;o&&console.log("[debug] Request:",{url:e,init:t});const s=await fetch(e,t),r=await s.text();if(o&&console.log("[debug] Response:",{url:e,status:s.status,statusText:s.statusText,headers:Array.from(s.headers.entries()),body:r}),!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);return{response:s,text:r}}async fetchText(e,t){const{text:o}=await this._fetch(e,t);return o}async fetchHtml(e,t){return this.fetchText(e,t)}async fetchJson(e,t){const{text:o}=await this._fetch(e,t);try{return JSON.parse(o)}catch{return{response:o}}}async _fetchEndpoint(e,t,o){const s="html"===e.responseType?"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8":"text"===e.responseType?"text/plain,*/*;q=0.8":"application/json, text/plain, */*",{response:r,text:n}=await this._fetch(e.url,{method:e.method||"GET",headers:{Cookie:t,"User-Agent":o,Accept:s,...e.headers}}),i=r.headers.get("content-type")?.toLowerCase()??"";if("json"===e.responseType||"text"!==e.responseType&&"html"!==e.responseType&&(i.includes("application/json")||i.includes("+json")))try{return JSON.parse(n)}catch(t){throw new Error(`Expected JSON from ${e.url}, but received invalid JSON: ${t instanceof Error?t.message:String(t)}`)}return n}async fetchData(e,t){if(!this.endpoints||0===this.endpoints.length)throw new Error(`Adapter "${this.id}" has no endpoints defined and no fetchData override`);const o=this.endpoints[0],s=this.buildCookieString(e),r=this.resolveUserAgent(t),n=await this._fetchEndpoint(o,s,r);return o.transform?o.transform.call(this,n,e,t):n}}export function defineAdapter(e){const t=new BaseAdapter;return Object.assign(t,e),e.fetchData&&(t.fetchData=e.fetchData.bind(t)),t}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseAdapter } from "./base-adapter.js";
|
|
2
|
+
import type { Page } from "playwright-core";
|
|
3
|
+
import type { QueryOptions } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Specialized base class for adapters that use Playwright CDP tab reuse.
|
|
6
|
+
* Provides a clean `connect(options)` method returning a disposable Page proxy.
|
|
7
|
+
*/
|
|
8
|
+
export declare class PlaywrightAdapter extends BaseAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* Connects to Chrome over CDP using the first endpoint's URL, returning a disposable Page proxy
|
|
11
|
+
* which automatically captures HTML and disposes of the connection upon scope exit.
|
|
12
|
+
*
|
|
13
|
+
* @param options The adapter options containing the debug flag.
|
|
14
|
+
*/
|
|
15
|
+
connect(options: QueryOptions): Promise<Page>;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{BaseAdapter as t}from"./base-adapter.js";import{runWithPlaywright as r}from"./playwright-core.js";export class PlaywrightAdapter extends t{async connect(t){const e=await r(this.endpoints,t);return await e.addInitScript(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),e}}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Browser, type Page } from "playwright-core";
|
|
2
|
+
export interface PlaywrightOptions {
|
|
3
|
+
cdpEndpoint?: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
close?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface PlaywrightTabResult {
|
|
8
|
+
page: Page;
|
|
9
|
+
browser: Browser;
|
|
10
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Connects to an existing Chrome instance via CDP and retrieves or creates a tab for the target URL.
|
|
14
|
+
*
|
|
15
|
+
* @param targetUrl The website URL to open/connect to.
|
|
16
|
+
* @param options Connection options.
|
|
17
|
+
*/
|
|
18
|
+
export declare function connectChrome(targetUrl: string, options?: PlaywrightOptions): Promise<PlaywrightTabResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Runs or prepares a Playwright task on a target URL inside an existing Chrome tab.
|
|
21
|
+
*
|
|
22
|
+
* Supports two modes:
|
|
23
|
+
* 1. Callback Mode (3 arguments): Connects, runs action, extracts HTML, and returns { html }.
|
|
24
|
+
* 2. Disposable Page Mode (2 arguments): Connects and returns a Page proxy implementing [Symbol.asyncDispose]
|
|
25
|
+
* which automatically extracts the HTML into options.htmlResult on disposal.
|
|
26
|
+
*
|
|
27
|
+
* @param endpoints The endpoints configuration of the adapter.
|
|
28
|
+
* @param options Query and debug options.
|
|
29
|
+
* @param action Optional action callback to interact with the page.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runWithPlaywright(endpoints: {
|
|
32
|
+
url: string;
|
|
33
|
+
}[] | undefined, options: {
|
|
34
|
+
debug?: boolean;
|
|
35
|
+
}, action?: (page: Page) => Promise<void>): Promise<any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=this&&this.__addDisposableResource||function(e,r,o){if(null!=r){if("object"!=typeof r&&"function"!=typeof r)throw new TypeError("Object expected.");var t,n;if(o){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");t=r[Symbol.asyncDispose]}if(void 0===t){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");t=r[Symbol.dispose],o&&(n=t)}if("function"!=typeof t)throw new TypeError("Object not disposable.");n&&(t=function(){try{n.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:r,dispose:t,async:o})}else o&&e.stack.push({async:!0});return r},r=this&&this.__disposeResources||function(e){return function(r){function o(o){r.error=r.hasError?new e(o,r.error,"An error was suppressed during disposal."):o,r.hasError=!0}var t,n=0;return function e(){for(;t=r.stack.pop();)try{if(!t.async&&1===n)return n=0,r.stack.push(t),Promise.resolve().then(e);if(t.dispose){var s=t.dispose.call(t.value);if(t.async)return n|=2,Promise.resolve(s).then(e,function(r){return o(r),e()})}else n|=1}catch(e){o(e)}if(1===n)return r.hasError?Promise.reject(r.error):Promise.resolve();if(r.hasError)throw r.error}()}}("function"==typeof SuppressedError?SuppressedError:function(e,r,o){var t=new Error(o);return t.name="SuppressedError",t.error=e,t.suppressed=r,t});import{chromium as o}from"playwright-core";export async function connectChrome(e,r={}){const t=r.cdpEndpoint||process.env.CDP_ENDPOINT||"http://localhost:9222",n=!!r.debug,s=await o.connectOverCDP(t),i=s.contexts()[0];if(!i)throw new Error("No active browser context found. Is Chrome running with remote debugging enabled?");let c=i.pages().find(r=>{try{const o=new URL(e).hostname.replace("www.","");return new URL(r.url()).hostname.endsWith(o)||r.url().startsWith(e)}catch{return r.url().startsWith(e)}}),a=!1;return c?n&&console.log(`Found existing tab with target URL: ${e}. Reusing it...`):(n&&console.log(`Target URL not found. Opening a new tab for: ${e}...`),c=await i.newPage(),await c.goto(e,{waitUntil:"domcontentloaded"}),a=!0),{page:c,browser:s,async[Symbol.asyncDispose](){if(a&&!1!==r.close){n&&console.log("Closing newly opened tab...");try{await c.close()}catch{}}n&&console.log("Disconnecting from Chrome CDP..."),await s.close()}}}export async function runWithPlaywright(o,t,n){if(!o||0===o.length)throw new Error("No endpoints defined in adapter.");const s=o[0].url;if(!n){const e=await connectChrome(s,{cdpEndpoint:process.env.CDP_ENDPOINT,debug:t.debug,close:t.close});return new Proxy(e.page,{get(r,o,n){if(o===Symbol.asyncDispose)return async()=>{try{const e=await r.content();t.htmlResult=e}catch(e){t.debug&&console.error("Failed to extract page content in disposable:",e)}await e[Symbol.asyncDispose]()};const s=Reflect.get(r,o,n);return"function"==typeof s?s.bind(r):s}})}{const o={stack:[],error:void 0,hasError:!1};try{const r=e(o,await connectChrome(s,{cdpEndpoint:process.env.CDP_ENDPOINT,debug:t.debug,close:t.close}),!0);await n(r.page);return{html:await r.page.content()}}catch(e){o.error=e,o.hasError=!0}finally{const e=r(o);e&&await e}}}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import type { CookieEntry } from "chrome-tools";
|
|
2
|
+
export interface ParameterDefinition {
|
|
3
|
+
/** Option name (e.g. "model", "timeout"). Matches option flag `--<name>`. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Value type. */
|
|
6
|
+
type: "string" | "boolean" | "number";
|
|
7
|
+
/** Description for CLI usage help. */
|
|
8
|
+
description: string;
|
|
9
|
+
/** Default value if not provided. */
|
|
10
|
+
default?: any;
|
|
11
|
+
/** Whether the option is strictly required. */
|
|
12
|
+
required?: boolean;
|
|
13
|
+
/** Short character flag (e.g. "m"). */
|
|
14
|
+
short?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PositionalDefinition {
|
|
17
|
+
/** Name of the positional argument (e.g. "question"). */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Description for CLI usage help. */
|
|
20
|
+
description: string;
|
|
21
|
+
/** Whether the positional is strictly required. */
|
|
22
|
+
required?: boolean;
|
|
23
|
+
/** If true, collects all remaining positional arguments. */
|
|
24
|
+
variadic?: boolean;
|
|
25
|
+
}
|
|
2
26
|
/**
|
|
3
27
|
* Defines an API endpoint to be fetched by an adapter.
|
|
4
28
|
*/
|
|
@@ -12,8 +36,9 @@ export interface Endpoint {
|
|
|
12
36
|
/**
|
|
13
37
|
* Expected response body type. Defaults to auto-detect from content-type.
|
|
14
38
|
* Use "html" for text/html responses that need parsing.
|
|
39
|
+
* Use "sse" for server-sent event stream responses.
|
|
15
40
|
*/
|
|
16
|
-
responseType?: "auto" | "json" | "text" | "html";
|
|
41
|
+
responseType?: "auto" | "json" | "text" | "html" | "sse";
|
|
17
42
|
/** Optional post-processing step for the parsed response body. */
|
|
18
43
|
transform?: (this: WebsiteAdapter, body: unknown, cookies: CookieEntry[], options: QueryOptions) => Promise<unknown> | unknown;
|
|
19
44
|
}
|
|
@@ -35,6 +60,12 @@ export interface AdapterConfig {
|
|
|
35
60
|
endpoints?: Endpoint[];
|
|
36
61
|
/** Override for complex multi-step flows (e.g. session token → API call). */
|
|
37
62
|
fetchData?: (this: WebsiteAdapter, cookies: CookieEntry[], options: QueryOptions) => Promise<unknown>;
|
|
63
|
+
/** If true, the adapter does not require valid cookies to function. Defaults to false. */
|
|
64
|
+
optionalCookies?: boolean;
|
|
65
|
+
/** Declares custom parameters/CLI flags for this website adapter. */
|
|
66
|
+
parameters?: ParameterDefinition[];
|
|
67
|
+
/** Declares expected positional arguments for this website adapter. */
|
|
68
|
+
positionals?: PositionalDefinition[];
|
|
38
69
|
}
|
|
39
70
|
/**
|
|
40
71
|
* A fully-instantiated website adapter with shared helper methods.
|
|
@@ -50,6 +81,13 @@ export interface WebsiteAdapter extends AdapterConfig {
|
|
|
50
81
|
fetchText(url: string, init?: RequestInit): Promise<string>;
|
|
51
82
|
/** Fetches an HTML document as text, with error handling. */
|
|
52
83
|
fetchHtml(url: string, init?: RequestInit): Promise<string>;
|
|
84
|
+
/** Resolves saved Chrome credentials for the adapter's target domain. */
|
|
85
|
+
resolveCredentials(options: QueryOptions): {
|
|
86
|
+
username: string;
|
|
87
|
+
password: string;
|
|
88
|
+
};
|
|
89
|
+
/** Resolves secure Chrome cookies for the adapter's target domain. */
|
|
90
|
+
resolveCookies(options: QueryOptions): CookieEntry[];
|
|
53
91
|
}
|
|
54
92
|
/**
|
|
55
93
|
* Options passed when querying a website API.
|
|
@@ -59,4 +97,8 @@ export interface QueryOptions {
|
|
|
59
97
|
profile?: string;
|
|
60
98
|
/** Custom User-Agent header for HTTP requests. */
|
|
61
99
|
userAgent?: string;
|
|
100
|
+
/** When true, print full HTTP request and response details for debugging. */
|
|
101
|
+
debug?: boolean;
|
|
102
|
+
/** Allows passing custom website-specific parameters. */
|
|
103
|
+
[key: string]: any;
|
|
62
104
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ParameterDefinition, PositionalDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses raw command-line arguments based on positional and parameter schemas of a website adapter.
|
|
4
|
+
* Handles defaults, types, validation, variadic trailing arguments, and maps kebab-case CLI options to camelCase.
|
|
5
|
+
*
|
|
6
|
+
* @param positionalDefs Expected positional arguments schema
|
|
7
|
+
* @param parameterDefs Expected custom parameter option flags schema
|
|
8
|
+
* @param argv Command line arguments (excluding node, cli paths and website id)
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseArgsForWebsite(positionalDefs: PositionalDefinition[] | undefined, parameterDefs: ParameterDefinition[] | undefined, argv: string[]): {
|
|
11
|
+
options: Record<string, any>;
|
|
12
|
+
helpRequested: boolean;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=[{name:"profile",type:"string",description:"specific Chrome profile directory (e.g., 'Default')"},{name:"user-agent",type:"string",description:"custom User-Agent header for HTTP requests",short:"u"},{name:"debug",type:"boolean",description:"Print full HTTP request and response bodies for debugging"},{name:"out",type:"string",description:"Write decoded response JSON/text to file instead of stdout",short:"o"}];export function parseArgsForWebsite(r=[],n=[],o){let i=!1;const s={},a=[...n,...e];for(const e of a){const r=t(e.name);void 0!==e.default?s[r]=e.default:"boolean"===e.type&&(s[r]=!1)}const c={};for(const e of r)c[e.name]=[];let f=0;for(let e=0;e<o.length;e++){const n=o[e];if("--help"!==n&&"-h"!==n)if(n.startsWith("-")){let r;const i=!n.startsWith("--"),c=i?n.slice(1):n.slice(2);if(r=i?a.find(e=>e.short===c):a.find(e=>e.name===c),!r)throw new Error(`Unknown option: ${n}`);const f=t(r.name);if("boolean"===r.type)s[f]=!0;else if("string"===r.type){const t=o[e+1];if(void 0===t||t.startsWith("-"))throw new Error(`Option ${n} requires a value`);s[f]=t,e++}else if("number"===r.type){const t=o[e+1];if(void 0===t||t.startsWith("-"))throw new Error(`Option ${n} requires a numeric value`);const r=Number(t);if(isNaN(r))throw new Error(`Option ${n} requires a valid numeric value, received: "${t}"`);s[f]=r,e++}}else if(f<r.length){const e=r[f];c[e.name].push(n),e.variadic||f++}else{const e=r[r.length-1];if(!e?.variadic)throw new Error(`Unexpected extra argument: "${n}"`);c[e.name].push(n)}else i=!0}for(const e of r){const r=c[e.name];if(e.required&&0===r.length&&!i)throw new Error(`Missing required argument: <${e.name}>`);s[t(e.name)]=r.length>0?r.join(" "):null}for(const e of a){const r=t(e.name);if(e.required&&void 0===s[r]&&!i)throw new Error(`Missing required option: --${e.name}`)}return{options:s,helpRequested:i}}function t(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal recursive schema defining how to extract fields from nested arrays.
|
|
3
|
+
*/
|
|
4
|
+
export interface SchemaField {
|
|
5
|
+
/** Array of indices specifying the path to traverse (e.g. [1, 0, 0]) */
|
|
6
|
+
path: number[];
|
|
7
|
+
/** Optional function to transform or format the extracted value */
|
|
8
|
+
transform?: (value: any) => any;
|
|
9
|
+
/** Optional nested schema to apply if this field contains an array of items */
|
|
10
|
+
items?: Record<string, SchemaField>;
|
|
11
|
+
}
|
|
12
|
+
export type UniversalGoogleSchema = Record<string, SchemaField>;
|
|
13
|
+
/**
|
|
14
|
+
* Parses and transforms Google batchexecute payload array using a universal schema mapping.
|
|
15
|
+
*
|
|
16
|
+
* @param payload The decoded RPC payload array.
|
|
17
|
+
* @param schema The universal schema mapping fields to array paths and options.
|
|
18
|
+
* @returns The normalized JSON structure.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseGoogleJsonWithSchema(payload: any, schema: UniversalGoogleSchema): any;
|
|
21
|
+
/**
|
|
22
|
+
* Decodes raw Google batchexecute response envelope and extracts the payload for a given RPC ID.
|
|
23
|
+
*
|
|
24
|
+
* @param rawResponse The raw HTTP response body from Google's batchexecute endpoint.
|
|
25
|
+
* @param rpcId The Google RPC ID to extract (e.g. 'jSf9Qc').
|
|
26
|
+
* @returns The parsed JSON payload for the requested RPC ID.
|
|
27
|
+
*/
|
|
28
|
+
export declare function decodeGoogleJson(rawResponse: string, rpcId: string): any;
|
|
29
|
+
export declare const decode_google_json: typeof decodeGoogleJson;
|
|
30
|
+
export declare const parse_google_json_with_schema: typeof parseGoogleJsonWithSchema;
|
|
31
|
+
export declare const decodeGoogleJsonWithSchema: (rawResponse: string, rpcId: string, schema: UniversalGoogleSchema) => any;
|
|
32
|
+
export declare const decode_google_json_with_schema: (rawResponse: string, rpcId: string, schema: UniversalGoogleSchema) => any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function o(o,e){let r=o;for(const o of e){if(null==r||!Array.isArray(r))return;r=r[o]}return r}export function parseGoogleJsonWithSchema(e,r){const n={};for(const[t,s]of Object.entries(r)){const r=o(e,s.path);s.items&&Array.isArray(r)?n[t]=r.map(o=>parseGoogleJsonWithSchema(o,s.items)):n[t]=s.transform?s.transform(r):r}return n}export function decodeGoogleJson(o,e){let r=o.trim();r.startsWith(")]}'")&&(r=r.substring(4).trim());const n=r.indexOf("\n");if(-1===n)throw new Error("Invalid Google JSON response format: No newline found");const t=r.substring(0,n).trim(),s=parseInt(t,10);if(isNaN(s))throw new Error(`Invalid Google JSON response chunk length: "${t}"`);let i=r.substring(n+1,n+1+s);const a=i.lastIndexOf("]]");-1!==a&&(i=i.substring(0,a+2));const c=JSON.parse(i).find(o=>Array.isArray(o)&&o[1]===e);if(!c)throw new Error(`RPC ID "${e}" payload not found in Google JSON response`);return JSON.parse(c[2])}export const decode_google_json=decodeGoogleJson;export const parse_google_json_with_schema=parseGoogleJsonWithSchema;export const decodeGoogleJsonWithSchema=(o,e,r)=>parseGoogleJsonWithSchema(decodeGoogleJson(o,e),r);export const decode_google_json_with_schema=decodeGoogleJsonWithSchema;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
export interface LoginOptions {
|
|
3
|
+
page: Page;
|
|
4
|
+
emailSelector: string;
|
|
5
|
+
emailValue: string;
|
|
6
|
+
passwordSelector: string;
|
|
7
|
+
passwordValue: string;
|
|
8
|
+
submitButtonSelector: string;
|
|
9
|
+
delayMs?: number;
|
|
10
|
+
intendedUrl?: string;
|
|
11
|
+
loginUrl?: string;
|
|
12
|
+
expectedRedirectUrlPattern?: string;
|
|
13
|
+
debug?: boolean;
|
|
14
|
+
pwdSelector?: string;
|
|
15
|
+
dashboardSelectors?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reusable utility to automate form logins via Playwright.
|
|
19
|
+
* Handles checking if login is required, filling credentials, waiting a custom delay,
|
|
20
|
+
* clicking the submit button, and waiting for redirection.
|
|
21
|
+
*/
|
|
22
|
+
export declare function performFormLogin(options: LoginOptions): Promise<boolean>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export async function performFormLogin(t){const{page:i,emailSelector:e,emailValue:o,passwordSelector:a,passwordValue:n,submitButtonSelector:r,delayMs:s=1e3,intendedUrl:l,loginUrl:c,expectedRedirectUrlPattern:u=l,debug:p=!1,pwdSelector:d='input[type="password"]',dashboardSelectors:f=[]}=t;if(l){if(!i.url().includes(l)){p&&console.log(`[LoginHelper] Navigating to intended URL: ${l}`);try{await i.goto(l,{waitUntil:"domcontentloaded"})}catch(t){p&&console.warn(`[LoginHelper] Warning navigating to ${l}:`,t)}}}try{await i.waitForLoadState("domcontentloaded")}catch(t){}let g=!1,w=i;p&&console.log("[LoginHelper] Waiting for session state to settle (detecting logon form or dashboard)...");const b=Date.now();for(;Date.now()-b<15e3;){try{if(await i.locator(d).first().isVisible()){g=!0,w=i;break}}catch{}let t=!1;for(const e of i.frames())try{if(await e.locator(d).first().isVisible()){g=!0,w=e,t=!0;break}}catch{}if(t)break;let e=!1;for(const t of f)try{if(await i.locator(t).first().isVisible()){g=!1,e=!0;break}}catch{}if(e)break;await i.waitForTimeout(500)}if(!g&&Date.now()-b>=15e3)try{await i.waitForSelector(e,{state:"visible",timeout:1e3}),g=!0,w=i}catch{try{await i.waitForSelector(a,{state:"visible",timeout:1e3}),g=!0,w=i}catch{g=!1}}if(!g)return p&&console.log("[LoginHelper] Already logged in (Dashboard active). Skipping login flow."),!1;let m=e,y=a,h=r;const L=[e,"input#userId-input","input#userId",'input[name="usr_name"]','input[type="text"][id*="user"]','input[type="text"][id*="Id"]','input[type="text"]','input[type="email"]'];for(const t of L)try{const i=w.locator(t).first();if(await i.isVisible()){m=t;break}}catch{}const k=[a,"input#password-input","input#password",'input[type="password"]','input[name*="password"]'];for(const t of k)try{const i=w.locator(t).first();if(await i.isVisible()){y=t;break}}catch{}const S=[r,'button[type="submit"]',"#signin-button","button#signin-button",'input[type="submit"]'];for(const t of S)try{const i=w.locator(t).first();if(await i.isVisible()){h=t;break}}catch{}if(p&&console.log(`[LoginHelper] Login form detected inside frame: ${w===i?"main":w.url()}. Proceeding using email='${m}', password='${y}', submit='${h}'...`),await w.fill(m,o),await w.fill(y,n),s>0&&(p&&console.log(`[LoginHelper] Waiting ${s}ms before submission...`),await w.waitForTimeout(s)),p&&console.log(`[LoginHelper] Clicking submit button '${h}'...`),await w.click(h),u){p&&console.log(`[LoginHelper] Waiting for redirection matching '${u}'...`);try{await i.waitForURL(u,{timeout:15e3})}catch{try{await i.waitForNavigation({waitUntil:"networkidle",timeout:8e3})}catch{}}}return!0}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ChaseAccount {
|
|
2
|
+
name: string;
|
|
3
|
+
last4: string;
|
|
4
|
+
balance: number | string;
|
|
5
|
+
type: string;
|
|
6
|
+
category: string;
|
|
7
|
+
raw?: any;
|
|
8
|
+
}
|
|
9
|
+
export interface ChaseAccountGroup {
|
|
10
|
+
groupName: string;
|
|
11
|
+
accounts: ChaseAccount[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Recursively parses and extracts account-like elements from the Chase REST API JSON payload.
|
|
15
|
+
*/
|
|
16
|
+
export declare function decodeChaseAccounts(jsonResult: any): ChaseAccountGroup[];
|
|
17
|
+
/**
|
|
18
|
+
* Returns a beautiful, premium explained summary of the decoded Chase bank accounts.
|
|
19
|
+
*/
|
|
20
|
+
export declare function explainAccounts(groups: ChaseAccountGroup[], rawJson?: any): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function decodeChaseAccounts(t){const e=[];if(!t||"object"!=typeof t)return e;const a=t=>{if(!t||"object"!=typeof t)return[];let e=[];Array.isArray(t.productInfos)&&(e=e.concat(t.productInfos));for(const n of Object.keys(t))"object"==typeof t[n]&&(e=e.concat(a(t[n])));return e},n=a(t),c=new Map;for(const t of n)t.accountId&&c.set(String(t.accountId),t),t.mask&&c.set(String(t.mask),t);const o=t=>{if(!t||"object"!=typeof t)return[];let e=[];const a=c.get(String(t.accountId||t.id||""))||(t.mask?c.get(String(t.mask)):void 0),n="string"==typeof(t.nickname||t.accountName||t.name||t.displayName||a?.nickName||a?.cardDefaultNickName),i=function(t){if(!t||"object"!=typeof t)return;const e=t.ledgerBalance??t.availableBalance??t.currentBalance??t.balance??t.amount??t.accountValue;if("number"==typeof e)return e;const a=t.bankingAccountDetail?.availableBalance??t.bankingAccountDetail?.currentBalance??t.cardAccountDetail?.currentBalance??t.detail?.accountValue??t.detail?.currentBalance;return"number"==typeof a?a:void 0}(t);if(n&&void 0!==i)return[{...t,balance:i}];for(const a of Object.keys(t)){const n=t[a];if(Array.isArray(n))for(const t of n)e=e.concat(o(t));else"object"==typeof n&&(e=e.concat(o(n)))}return e},i=new Set,r=o(t),s=[],u=[],l=[],p=[];for(const t of r){const e=t.mask||t.last4||t.accountNumberLast4Digits||t.suffix||"N/A",a=c.get(String(t.accountId||t.id||""))||c.get(e),n=t.nickname||t.accountName||t.name||t.displayName||a?.nickName||a?.cardDefaultNickName||"Account",o=t.mask||t.last4||t.accountNumberLast4Digits||t.suffix||a?.mask||"N/A",r=`${n.toLowerCase()}_${o}`;if(i.has(r))continue;i.add(r);let m=t.accountType||t.accountTileDetailType||t.detailType||"Unknown",f=t.accountCategoryType||t.categoryType||t.accountTileType||t.groupType||"Unknown";if(a&&a.productId){const t=a.productId.split("-");t.length>=2&&(f=t[0],m=t[1])}const g={name:n,last4:o,balance:t.balance,type:m,category:f,raw:t};"DDA"===f||"CHK"===m?s.push(g):"CARD"===f||"BAC"===m?u.push(g):"INVESTMENT"===f||"BR2"===m?l.push(g):p.push(g)}return s.length>0&&e.push({groupName:"Bank accounts",accounts:s}),l.length>0&&e.push({groupName:"Investment accounts by J.P. Morgan",accounts:l}),u.length>0&&e.push({groupName:"Credit cards",accounts:u}),p.length>0&&e.push({groupName:"Other accounts",accounts:p}),e}export function explainAccounts(t,e){const a=["💼 Chase Account Summary (REST API Decoded):"];if(0===t.length)a.push("\n⚠️ No accounts could be successfully decoded from the JSON response."),e&&a.push("Please check the raw response below to verify the payload structure.");else{let e=0,n=0;for(const c of t){a.push(`\n📁 ${c.groupName}:`);for(const t of c.accounts){const c="number"==typeof t.balance?`$${t.balance.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`:String(t.balance);if(a.push(` 🔹 ${t.name} (...${t.last4}) - Balance: ${c} [Type: ${t.type}]`),"number"==typeof t.balance){("CARD"===t.category||"BAC"===t.type||"CARD"===t.raw?.categoryType||"CARD"===t.raw?.accountCategoryType||"CARD"===t.raw?.accountTileType)&&"CHK"!==t.type||t.category.toLowerCase().includes("debt")||t.balance<0?n+=Math.abs(t.balance):e+=t.balance}}}if(e>0||n>0){a.push("\n📈 Portfolio Overview:"),a.push(` 💸 Total Liquid Assets: $${e.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`),a.push(` 💳 Total Credit Debts: $${n.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`);const t=e-n;a.push(` 💎 Net Cash/Portfolio Value: $${t.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`)}}return e&&(a.push("\n📋 Raw REST API JSON Response:"),a.push(JSON.stringify(e,null,2))),a.join("\n")}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PlaywrightAdapter } from "../../adapter/playwright-attatch-chrome-adapter.js";
|
|
2
|
+
export default class extends PlaywrightAdapter {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
description: string;
|
|
7
|
+
optionalCookies: boolean;
|
|
8
|
+
endpoints: {
|
|
9
|
+
url: string;
|
|
10
|
+
}[];
|
|
11
|
+
positionals: {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
required: boolean;
|
|
15
|
+
variadic: boolean;
|
|
16
|
+
}[];
|
|
17
|
+
parameters: ({
|
|
18
|
+
name: string;
|
|
19
|
+
type: "boolean";
|
|
20
|
+
description: string;
|
|
21
|
+
short: string;
|
|
22
|
+
default: boolean;
|
|
23
|
+
} | {
|
|
24
|
+
name: string;
|
|
25
|
+
type: "number";
|
|
26
|
+
description: string;
|
|
27
|
+
short?: undefined;
|
|
28
|
+
default?: undefined;
|
|
29
|
+
} | {
|
|
30
|
+
name: string;
|
|
31
|
+
type: "boolean";
|
|
32
|
+
description: string;
|
|
33
|
+
default: boolean;
|
|
34
|
+
short?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
name: string;
|
|
37
|
+
type: "string";
|
|
38
|
+
description: string;
|
|
39
|
+
short?: undefined;
|
|
40
|
+
default?: undefined;
|
|
41
|
+
})[];
|
|
42
|
+
fetchData(cookies: any, options: any): Promise<string>;
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=this&&this.__addDisposableResource||function(e,o,r){if(null!=o){if("object"!=typeof o&&"function"!=typeof o)throw new TypeError("Object expected.");var t,s;if(r){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");t=o[Symbol.asyncDispose]}if(void 0===t){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");t=o[Symbol.dispose],r&&(s=t)}if("function"!=typeof t)throw new TypeError("Object not disposable.");s&&(t=function(){try{s.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:o,dispose:t,async:r})}else r&&e.stack.push({async:!0});return o},o=this&&this.__disposeResources||function(e){return function(o){function r(r){o.error=o.hasError?new e(r,o.error,"An error was suppressed during disposal."):r,o.hasError=!0}var t,s=0;return function e(){for(;t=o.stack.pop();)try{if(!t.async&&1===s)return s=0,o.stack.push(t),Promise.resolve().then(e);if(t.dispose){var n=t.dispose.call(t.value);if(t.async)return s|=2,Promise.resolve(n).then(e,function(o){return r(o),e()})}else s|=1}catch(e){r(e)}if(1===s)return o.hasError?Promise.reject(o.error):Promise.resolve();if(o.hasError)throw o.error}()}}("function"==typeof SuppressedError?SuppressedError:function(e,o,r){var t=new Error(r);return t.name="SuppressedError",t.error=e,t.suppressed=o,t});import{PlaywrightAdapter as r}from"../../adapter/playwright-attatch-chrome-adapter.js";import{performFormLogin as t}from"../../util/login-helper.js";import{downloadAccounts as s}from"./download-helper.js";export default class extends r{id="chase";name="Chase Bank";domain="chase.com";description="Logs into Chase, lists downloadable accounts, and downloads statement/transaction CSV files.";optionalCookies=!0;endpoints=[{url:"https://www.chase.com/"}];positionals=[{name:"accounts",description:"Account indexes to select (e.g. 1 3). Leave empty for all.",required:!1,variadic:!0}];parameters=[{name:"list",type:"boolean",description:"List downloadable accounts only",short:"l",default:!1},{name:"download",type:"boolean",description:"Save selected account CSV file(s) in the current directory or --out-dir",short:"d",default:!1},{name:"limit",type:"number",description:"Only process the first n accounts"},{name:"first",type:"boolean",description:"Shortcut for --limit 1",default:!1},{name:"filename",type:"string",description:"Save one selected account to this filename (requires --download and exactly one account)"},{name:"activity",type:"string",description:"Activity option: current-display, all-transactions, date-range (default: all)"},{name:"range",type:"string",description:"Alias for --activity"},{name:"from",type:"string",description:"Start date for date-range (mm/dd/yyyy)"},{name:"to",type:"string",description:"End date for date-range (mm/dd/yyyy)"},{name:"out-dir",type:"string",description:"Write each CSV to <dir> instead of returning it"}];async fetchData(r,n){const a={stack:[],error:void 0,hasError:!1};try{const o=!!n.debug,{username:r,password:i}=this.resolveCredentials(n);o&&console.log(`[ChaseAdapter] Resolved credentials for ${r}`);const c=e(a,await this.connect({...n,close:!1}),!0);return o&&console.log("[ChaseAdapter] Performing form login using login helper..."),await t({page:c,intendedUrl:"https://secure.chase.com/web/auth/dashboard#/dashboard/overview",emailSelector:".public-logon.input.logon-xs-toggle",emailValue:r,passwordSelector:".public-logon.input-password.logon-xs-toggle",passwordValue:i,submitButtonSelector:"#signin-button",delayMs:1e3,pwdSelector:'input[type="password"], input[name*="password"], input[id*="password"], .public-logon.input-password',dashboardSelectors:[".accounts-group-container-bc","#account-groups-component-bc",'[data-testid="accounts-group-container"]',".innerTile","#DDA_ACCOUNTS"],debug:o}),await c.waitForTimeout(3e3),o&&console.log("[ChaseAdapter] Running statement downloader flow..."),await s(c,n)}catch(e){a.error=e,a.hasError=!0}finally{const e=o(a);e&&await e}}}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Page } from 'playwright-core';
|
|
2
|
+
export interface ChaseDownloadOptions {
|
|
3
|
+
list?: boolean;
|
|
4
|
+
download?: boolean;
|
|
5
|
+
limit?: number | null;
|
|
6
|
+
first?: boolean;
|
|
7
|
+
filename?: string | null;
|
|
8
|
+
activity?: string;
|
|
9
|
+
range?: string;
|
|
10
|
+
from?: string | null;
|
|
11
|
+
to?: string | null;
|
|
12
|
+
outDir?: string | null;
|
|
13
|
+
accounts?: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface ChaseDownloadAccount {
|
|
16
|
+
id: string;
|
|
17
|
+
summaryType: 'CARD' | 'DDA';
|
|
18
|
+
detailType: string;
|
|
19
|
+
nickname: string;
|
|
20
|
+
mask: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function normalizeActivity(value?: string): {
|
|
23
|
+
key: string;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function fetchDownloadOptions(page: Page): Promise<any>;
|
|
27
|
+
export declare function collectAccounts(payload: any): ChaseDownloadAccount[];
|
|
28
|
+
export declare function formatAccountLabel(account: ChaseDownloadAccount): string;
|
|
29
|
+
export declare function accountFileName(account: ChaseDownloadAccount): string;
|
|
30
|
+
export declare function summarizeAccounts(accounts: ChaseDownloadAccount[]): string;
|
|
31
|
+
export declare function selectAccounts(accounts: ChaseDownloadAccount[], opts: ChaseDownloadOptions): ChaseDownloadAccount[];
|
|
32
|
+
export declare function fetchAccountCsv(page: Page, account: ChaseDownloadAccount, opts: ChaseDownloadOptions, activityKey: string): Promise<string>;
|
|
33
|
+
export declare function downloadAccounts(page: Page, opts: ChaseDownloadOptions): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"node:fs/promises";import e from"node:path";import n from"node:process";const a={CARD:{mode:"cardGet",count:"/svc/rr/accounts/secure/gateway/credit-card/transactions/inquiry-maintenance/digital-transaction-activity/v1/transaction-counts",csv:"/svc/rr/accounts/secure/gateway/credit-card/transactions/inquiry-maintenance/digital-transaction-activity/v1/transaction-activities"},DDA:{mode:"formPost",count:"/svc/rr/accounts/secure/v1/account/activity/download/count/dda/list",csv:"/svc/rr/accounts/secure/v1/account/activity/download/dda/list"}},r=new Map([["current",{key:"current",label:"Current display, including filters"}],["current-display",{key:"current",label:"Current display, including filters"}],["current display",{key:"current",label:"Current display, including filters"}],["current display, including filters",{key:"current",label:"Current display, including filters"}],["all",{key:"all",label:"All transactions"}],["all-transactions",{key:"all",label:"All transactions"}],["all transactions",{key:"all",label:"All transactions"}],["date-range",{key:"date-range",label:"Choose a date range"}],["date range",{key:"date-range",label:"Choose a date range"}],["choose-date-range",{key:"date-range",label:"Choose a date range"}],["choose a date range",{key:"date-range",label:"Choose a date range"}]]);export function normalizeActivity(t){const e=String(t||"").trim().toLowerCase(),n=r.get(e||"all");if(!n)throw new Error(`Unknown activity option: ${t}`);return n}export async function fetchDownloadOptions(t){t.url().includes("secure.chase.com")||await t.goto("https://secure.chase.com/web/auth/dashboard#/dashboard/overview",{waitUntil:"domcontentloaded"});const e=await t.evaluate(async t=>{const e=await fetch(t.url,{method:"POST",credentials:"include",headers:t.headers,body:""});return{status:e.status,text:await e.text()}},{url:"/svc/rr/accounts/secure/v1/account/activity/download/options/list",headers:{"content-type":"application/x-www-form-urlencoded; charset=UTF-8","x-jpmc-channel":"id=C30","x-jpmc-csrf-token":"NONE"}});if(e.status<200||e.status>=300)throw new Error("Download options request failed with status "+e.status);return JSON.parse(e.text)}export function collectAccounts(t){return(t.downloadAccountActivityOptions||[]).map(t=>({id:String(t.accountId||"").trim(),summaryType:String(t.summaryType||"").trim(),detailType:String(t.detailType||"").trim(),nickname:String(t.nickName||"").trim(),mask:String(t.mask||"").trim()})).filter(t=>t.id&&("CARD"===t.summaryType||"DDA"===t.summaryType)&&t.detailType)}export function formatAccountLabel(t){const e=[];return t.nickname&&e.push(t.nickname),t.mask&&e.push(`****${t.mask}`),e.length||e.push(`${t.summaryType}/${t.detailType}/${t.id}`),e.join(" ")}export function accountFileName(t){return`${[t.nickname||"account",t.summaryType,t.detailType,t.id].map(t=>String(t).trim().replace(/[^A-Za-z0-9._-]+/g,"-")).filter(Boolean).join("-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"account"}.csv`}export function summarizeAccounts(t){const e=[];e.push(`Found ${t.length} downloadable account${1===t.length?"":"s"}:`);for(const[n,a]of t.entries())e.push(`${n+1}. ${formatAccountLabel(a)} | ${a.summaryType},${a.detailType},${a.id}`);return e.join("\n")}export function selectAccounts(t,e){let n=[];if(e.accounts&&(n=e.accounts.split(/[\s,]+/).map(Number).filter(t=>!isNaN(t))),!n.length){const n=void 0!==e.limit?e.limit:e.first?1:null;return t.slice(0,n||void 0)}const a=[],r=new Set;for(const e of n){if(e<1||e>t.length)throw new Error(`Account number ${e} is out of range. Run list to see 1-${t.length}.`);r.has(e)||(r.add(e),a.push(t[e-1]))}return a}function o(t){return`${t.getFullYear()}${String(t.getMonth()+1).padStart(2,"0")}${String(t.getDate()).padStart(2,"0")}`}function c(t){const e=String(t||"").match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);if(!e)throw new Error(`Invalid date "${t}". Use mm/dd/yyyy format.`);const[,n,a,r]=e;return`${r}${n.padStart(2,"0")}${a.padStart(2,"0")}`}function i(t,e){const n=String(t??""),a=n.trimStart();if(/^<!doctype html/i.test(a)||/^<html/i.test(a))throw new Error(`Download for ${e} returned HTML instead of CSV. The session may have expired.`);return n}export async function fetchAccountCsv(t,e,n,r){const i=function(t){const e=a[t.summaryType];if(!e)throw new Error(`Unsupported account type for ${formatAccountLabel(t)}: ${t.summaryType}`);return e}(e),s=function(t,e,n){if("CARD"===t.summaryType){const a=new Date,r=new Date(a);r.setFullYear(a.getFullYear()-2);const i={"account-activity-download-type-code":"CSV","digital-account-identifier":t.id};if("date-range"===n){if(!e.from||!e.to)throw new Error("date-range requires from and to in mm/dd/yyyy format");i["start-date"]=c(e.from),i["end-date"]=c(e.to)}else"all"===n&&(i["start-date"]=o(r),i["end-date"]=o(a),i["eligibility-indicator"]="true");return i}const a={transactionType:"ALL",filterTranType:"ALL",statementPeriodId:"ALL",downloadType:"CSV",accountId:t.id};if("all"===n&&"DDA"===t.summaryType&&(a.dateOption="LAST_24_MONTHS"),"date-range"===n){if(!e.from||!e.to)throw new Error("date-range requires from and to in mm/dd/yyyy format");a.dateOption="DATE_RANGE",a.dateLo=e.from,a.dateHi=e.to}return a}(e,n,r),d={body:s,countUrl:i.count,csvUrl:i.csv,csrfUrl:"/svc/rl/accounts/secure/v1/csrf/token/list",mode:i.mode,headers:{"content-type":"application/x-www-form-urlencoded; charset=UTF-8","x-jpmc-channel":"id=C30","x-jpmc-csrf-token":"NONE"}},l=await t.evaluate(async t=>{const e=t=>new URLSearchParams(t).toString(),n=(n,a)=>fetch(n+"?"+e(a),{method:"GET",credentials:"include",headers:t.headers}),a=(n,a,r=t.headers)=>fetch(n,{method:"POST",credentials:"include",headers:r,body:e(a)}),r="cardGet"===t.mode?await n(t.countUrl,t.body):await a(t.countUrl,t.body);if(!r.ok)throw new Error("Download count request failed with status "+r.status);const o=await fetch(t.csrfUrl,{method:"POST",credentials:"include",headers:t.headers,body:""});if(!o.ok)throw new Error("CSRF token request failed with status "+o.status);const c=await o.json(),i=c.csrfToken||c.response?.csrfToken;if(!i)throw new Error("CSRF token was not present in Chase token response");const s={...t.body,csrftoken:i,submit:"Submit"},d="cardGet"===t.mode?await n(t.csvUrl,s):await a(t.csvUrl,s,{"content-type":"application/x-www-form-urlencoded"});return{status:d.status,contentType:d.headers.get("content-type")||"",text:await d.text()}},d);if(l.status<200||l.status>=300)throw new Error(`Download request failed with status ${l.status} for ${formatAccountLabel(e)}`);if(l.contentType&&!/csv|text|octet-stream/i.test(l.contentType))throw new Error(`Download for ${formatAccountLabel(e)} returned ${l.contentType||"unknown content type"}`);return l.text}export async function downloadAccounts(a,r){const o=normalizeActivity(r.activity||r.range);if(!("date-range"!==o.key||r.from&&r.to))throw new Error("activity date-range requires from and to in mm/dd/yyyy format");const c=collectAccounts(await fetchDownloadOptions(a));if(!c.length)throw new Error("No downloadable accounts were found in the Chase download options response.");if(r.list)return summarizeAccounts(c);const s=selectAccounts(c,r),d=r.download?e.resolve(n.cwd(),r.outDir||"."):null;d&&await t.mkdir(d,{recursive:!0});const l=[];for(const[n,c]of s.entries()){const u=i(await fetchAccountCsv(a,c,r,o.key),formatAccountLabel(c));if(d){const n=r.filename&&1===s.length?r.filename:accountFileName(c),a=e.join(d,n);await t.writeFile(a,u,"utf8"),l.push(`Saved: ${a}`)}else l.push(`\n===== ${n+1}/${s.length}: ${formatAccountLabel(c)} =====\n`+(u.endsWith("\n")?u:`${u}\n`))}return l.join("\n")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineAdapter as e}from"../../base-adapter.js";export default e({id:"codex-usage",name:"ChatGPT / Codex Usage",domain:"chatgpt.com",description:"Fetches ChatGPT rate limit usage and quota details from the private wham/usage API.",async fetchData(e,t){const a=this.buildCookieString(e),s=this.resolveUserAgent(t),o=await this.fetchJson("https://chatgpt.com/api/auth/session",{headers:{Cookie:a,"User-Agent":s,Accept:"application/json"}});if(!o?.accessToken)throw new Error("No login found in browser");return this.fetchJson("https://chatgpt.com/backend-api/wham/usage",{headers:{authorization:`Bearer ${o.accessToken}`}})}});
|
|
1
|
+
import{defineAdapter as e}from"../../adapter/base-adapter.js";export default e({id:"codex-usage",name:"ChatGPT / Codex Usage",domain:"chatgpt.com",description:"Fetches ChatGPT rate limit usage and quota details from the private wham/usage API.",async fetchData(e,t){const a=this.buildCookieString(e),s=this.resolveUserAgent(t),o=await this.fetchJson("https://chatgpt.com/api/auth/session",{headers:{Cookie:a,"User-Agent":s,Accept:"application/json"}});if(!o?.accessToken)throw new Error("No ChatGPT login found in browser");return this.fetchJson("https://chatgpt.com/backend-api/wham/usage",{headers:{authorization:`Bearer ${o.accessToken}`}})}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineAdapter as r}from"../../base-adapter.js";export default r({id:"cursor-usage",name:"Cursor Usage",domain:"cursor.com",description:"Fetches the active Cursor usage summary from the private usage-summary API.",endpoints:[{url:"https://cursor.com/api/usage-summary"}]});
|
|
1
|
+
import{defineAdapter as r}from"../../adapter/base-adapter.js";export default r({id:"cursor-usage",name:"Cursor Usage",domain:"cursor.com",description:"Fetches the active Cursor usage summary from the private usage-summary API.",endpoints:[{url:"https://cursor.com/api/usage-summary"}]});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlaywrightAdapter } from "../../adapter/playwright-attatch-chrome-adapter.js";
|
|
2
|
+
export default class extends PlaywrightAdapter {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
description: string;
|
|
7
|
+
optionalCookies: boolean;
|
|
8
|
+
endpoints: {
|
|
9
|
+
url: string;
|
|
10
|
+
}[];
|
|
11
|
+
fetchData(cookies: any, options: any): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var r=this&&this.__addDisposableResource||function(r,e,o){if(null!=e){if("object"!=typeof e&&"function"!=typeof e)throw new TypeError("Object expected.");var s,t;if(o){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");s=e[Symbol.asyncDispose]}if(void 0===s){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");s=e[Symbol.dispose],o&&(t=s)}if("function"!=typeof s)throw new TypeError("Object not disposable.");t&&(s=function(){try{t.call(this)}catch(r){return Promise.reject(r)}}),r.stack.push({value:e,dispose:s,async:o})}else o&&r.stack.push({async:!0});return e},e=this&&this.__disposeResources||function(r){return function(e){function o(o){e.error=e.hasError?new r(o,e.error,"An error was suppressed during disposal."):o,e.hasError=!0}var s,t=0;return function r(){for(;s=e.stack.pop();)try{if(!s.async&&1===t)return t=0,e.stack.push(s),Promise.resolve().then(r);if(s.dispose){var n=s.dispose.call(s.value);if(s.async)return t|=2,Promise.resolve(n).then(r,function(e){return o(e),r()})}else t|=1}catch(r){o(r)}if(1===t)return e.hasError?Promise.reject(e.error):Promise.resolve();if(e.hasError)throw e.error}()}}("function"==typeof SuppressedError?SuppressedError:function(r,e,o){var s=new Error(o);return s.name="SuppressedError",s.error=r,s.suppressed=e,s});import{PlaywrightAdapter as o}from"../../adapter/playwright-attatch-chrome-adapter.js";export default class extends o{id="ExampleAdapter";name="Example Playwright Service";domain="example.com";description="Fetches and prints the HTML content of example.com using Playwright connecting to existing Chrome.";optionalCookies=!0;endpoints=[{url:"https://example.com"}];async fetchData(o,s){const t={stack:[],error:void 0,hasError:!1};try{r(t,await this.connect(s),!0)}catch(r){t.error=r,t.hasError=!0}finally{const r=e(t);r&&await r}}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlaywrightAdapter } from "../../adapter/playwright-attatch-chrome-adapter.js";
|
|
2
|
+
export default class extends PlaywrightAdapter {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
description: string;
|
|
7
|
+
optionalCookies: boolean;
|
|
8
|
+
endpoints: {
|
|
9
|
+
url: string;
|
|
10
|
+
}[];
|
|
11
|
+
fetchData(cookies: any, options: any): Promise<any>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=this&&this.__addDisposableResource||function(e,r,o){if(null!=r){if("object"!=typeof r&&"function"!=typeof r)throw new TypeError("Object expected.");var t,s;if(o){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");t=r[Symbol.asyncDispose]}if(void 0===t){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");t=r[Symbol.dispose],o&&(s=t)}if("function"!=typeof t)throw new TypeError("Object not disposable.");s&&(t=function(){try{s.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:r,dispose:t,async:o})}else o&&e.stack.push({async:!0});return r},r=this&&this.__disposeResources||function(e){return function(r){function o(o){r.error=r.hasError?new e(o,r.error,"An error was suppressed during disposal."):o,r.hasError=!0}var t,s=0;return function e(){for(;t=r.stack.pop();)try{if(!t.async&&1===s)return s=0,r.stack.push(t),Promise.resolve().then(e);if(t.dispose){var i=t.dispose.call(t.value);if(t.async)return s|=2,Promise.resolve(i).then(e,function(r){return o(r),e()})}else s|=1}catch(e){o(e)}if(1===s)return r.hasError?Promise.reject(r.error):Promise.resolve();if(r.hasError)throw r.error}()}}("function"==typeof SuppressedError?SuppressedError:function(e,r,o){var t=new Error(o);return t.name="SuppressedError",t.error=e,t.suppressed=r,t});import{PlaywrightAdapter as o}from"../../adapter/playwright-attatch-chrome-adapter.js";import{decodeGoogleJsonWithSchema as t}from"../../util/google-json.js";const s={planCode:{path:[0]},planName:{path:[0],transform:e=>2===e?"Gemini Pro":1===e?"Gemini Free":`Unknown (${e})`},limits:{path:[1],items:{rawLimit:{path:[0]},percentageUsed:{path:[1],transform:e=>parseFloat((100*e).toFixed(2))},tierName:{path:[2],transform:e=>1===e?"Hourly Usage Limit":2===e?"Weekly Usage Limit":`Unknown Tier (${e})`},resetTime:{path:[3,0,0],transform:e=>e?new Date(1e3*e).toISOString():null}}}};export default class extends o{id="gemini-usage";name="Gemini Usage";domain="gemini.google.com";description="Fetches Gemini account usage/quota details via browser-attached Playwright.";optionalCookies=!0;endpoints=[{url:"https://gemini.google.com/usage"}];async fetchData(o,i){const n={stack:[],error:void 0,hasError:!1};try{const r=e(n,await this.connect(i),!0),o=new Promise((e,o)=>{const t=setTimeout(()=>{o(new Error("Timeout waiting for Gemini usage RPC payload. Make sure you are logged into gemini.google.com in Chrome."))},15e3);r.on("response",async r=>{if(r.url().includes("jSf9Qc"))try{const o=await r.text();clearTimeout(t),e(o)}catch(e){}})});i.debug&&console.log("Triggering page reload/navigation to capture Gemini usage network request..."),await r.reload({waitUntil:"domcontentloaded"});const a=await o;return t(a,"jSf9Qc",s)}catch(e){n.error=e,n.hasError=!0}finally{const e=r(n);e&&await e}}}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PlaywrightAdapter } from "../../adapter/playwright-attatch-chrome-adapter.js";
|
|
2
|
+
export default class extends PlaywrightAdapter {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
description: string;
|
|
7
|
+
optionalCookies: boolean;
|
|
8
|
+
endpoints: {
|
|
9
|
+
url: string;
|
|
10
|
+
}[];
|
|
11
|
+
positionals: {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
required: boolean;
|
|
15
|
+
variadic: boolean;
|
|
16
|
+
}[];
|
|
17
|
+
parameters: ({
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
description: string;
|
|
21
|
+
default: number;
|
|
22
|
+
short?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
name: string;
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
short: string;
|
|
28
|
+
default?: undefined;
|
|
29
|
+
})[];
|
|
30
|
+
fetchData(cookies: any, options: any): Promise<{
|
|
31
|
+
question: any;
|
|
32
|
+
answer: string | null;
|
|
33
|
+
finalUrl: string;
|
|
34
|
+
endpoint: any;
|
|
35
|
+
searchPage: {
|
|
36
|
+
title: any;
|
|
37
|
+
url: any;
|
|
38
|
+
bodyText: string;
|
|
39
|
+
};
|
|
40
|
+
endpointResult: {
|
|
41
|
+
title: any;
|
|
42
|
+
url: any;
|
|
43
|
+
bodyText: string;
|
|
44
|
+
htmlPrefix: any;
|
|
45
|
+
decoded: any;
|
|
46
|
+
} | null;
|
|
47
|
+
requests: {
|
|
48
|
+
method: any;
|
|
49
|
+
type: any;
|
|
50
|
+
url: any;
|
|
51
|
+
status: any;
|
|
52
|
+
mimeType: any;
|
|
53
|
+
decodedFormat: string | null;
|
|
54
|
+
recordCount: number;
|
|
55
|
+
bodyPrefix: any;
|
|
56
|
+
}[];
|
|
57
|
+
}>;
|
|
58
|
+
private waitForAiAnswer;
|
|
59
|
+
private collectPageState;
|
|
60
|
+
private buildFolwrEndpoint;
|
|
61
|
+
private summarizeNetwork;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=this&&this.__addDisposableResource||function(e,t,r){if(null!=t){if("object"!=typeof t&&"function"!=typeof t)throw new TypeError("Object expected.");var o,n;if(r){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");o=t[Symbol.asyncDispose]}if(void 0===o){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");o=t[Symbol.dispose],r&&(n=o)}if("function"!=typeof o)throw new TypeError("Object not disposable.");n&&(o=function(){try{n.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:t,dispose:o,async:r})}else r&&e.stack.push({async:!0});return t},t=this&&this.__disposeResources||function(e){return function(t){function r(r){t.error=t.hasError?new e(r,t.error,"An error was suppressed during disposal."):r,t.hasError=!0}var o,n=0;return function e(){for(;o=t.stack.pop();)try{if(!o.async&&1===n)return n=0,t.stack.push(o),Promise.resolve().then(e);if(o.dispose){var s=o.dispose.call(o.value);if(o.async)return n|=2,Promise.resolve(s).then(e,function(t){return r(t),e()})}else n|=1}catch(e){r(e)}if(1===n)return t.hasError?Promise.reject(t.error):Promise.resolve();if(t.hasError)throw t.error}()}}("function"==typeof SuppressedError?SuppressedError:function(e,t,r){var o=new Error(r);return o.name="SuppressedError",o.error=e,o.suppressed=t,o});import{PlaywrightAdapter as r}from"../../adapter/playwright-attatch-chrome-adapter.js";export default class extends r{id="google-ai";name="Google AI Overview";domain="google.com";description="Fetches Google's AI Overview and AI Mode answers using browser-attached Playwright.";optionalCookies=!0;endpoints=[{url:"https://www.google.com"}];positionals=[{name:"question",description:"The search query or question to ask Google",required:!0,variadic:!0}];parameters=[{name:"raw-limit",type:"number",description:"Max raw search/endpoint response chars to include",default:12e3},{name:"timeout",type:"number",description:"Playwright timeout in milliseconds",default:9e4},{name:"text",type:"boolean",description:"Print only the extracted AI Overview answer text",short:"t"}];async fetchData(r,o){const n={stack:[],error:void 0,hasError:!1};try{const t=o.question,r=void 0!==o.rawLimit?Number(o.rawLimit):12e3,s=void 0!==o.timeout?Number(o.timeout):9e4,i=e(n,await this.connect(o),!0),c=await i.context().newCDPSession(i);await c.send("Network.enable",{maxTotalBufferSize:1e8,maxResourceBufferSize:1e8});const g=[],y=new Map,w=e=>{const t=function(e){const t=/^https?:\/\/([^/]+)(\/[^?#]*)?/i.exec(String(e??""));return t&&/(^|\.)google\.[^/]+$/i.test(t[1])?t[2]||"/":null}(e);return"/search"===t||t?.startsWith("/async/")||t?.includes("batchexecute")};c.on("Network.requestWillBeSent",e=>{const t=e.request||{};w(t.url)&&(y.set(e.requestId,g.length),g.push({id:e.requestId,type:e.type,method:t.method,url:t.url,postData:t.postData||null,status:null,mimeType:null,body:null}))}),c.on("Network.responseReceived",e=>{const t=y.get(e.requestId);null!=t&&(g[t].status=e.response.status,g[t].mimeType=e.response.mimeType)}),c.on("Network.loadingFinished",async e=>{const t=y.get(e.requestId);if(null!=t)try{const o=g[t].mimeType||"";if(!/text|json|html|javascript|x-protobuf/.test(o))return;const n=await c.send("Network.getResponseBody",{requestId:e.requestId});g[t].body=n.base64Encoded?null:(n.body||"").slice(0,r)}catch(e){}});const b=`https://www.google.com/search?${a({q:t,udm:"50"})}`;o.debug&&console.log(`Navigating to Google Search: ${b}`),await i.goto(b,{waitUntil:"domcontentloaded"});const k=await this.waitForAiAnswer(i,s),x=await this.collectPageState(i,r),S=await this.buildFolwrEndpoint(i);let v=null;if(S?.url){o.debug&&(console.log(`Discovered folwr endpoint: ${S.url}`),console.log("Querying folwr endpoint in-page via browser fetch..."));try{const e=await i.evaluate(async e=>{const t=await fetch(e);return await t.text()},S.url),t=function(e){try{const t=l(e).trimStart();if(t.startsWith("<")||t.includes("class=")||t.includes("id="))return m(t);const r=u(e);for(const e of r){const t=h(e.value);if(t)return m(t)}}catch(e){}return null}(e),o=p(e),n=!o||o.includes("<")||o.includes("class=")?null:o;v={title:x.title,url:S.url,bodyText:e,htmlPrefix:e.slice(0,r),answer:t||n,decoded:d({body:e,contentType:"text/plain"})}}catch(e){o.debug&&console.warn("Failed to query folwr endpoint in-page:",e)}}await i.waitForTimeout(500);return{question:t,answer:f(v?.answer||k||p(x.bodyText),r)||null,finalUrl:i.url(),endpoint:S||null,searchPage:{title:x.title,url:x.url,bodyText:f(x.bodyText,r)},endpointResult:v?{title:v.title,url:v.url,bodyText:f(v.bodyText,r),htmlPrefix:v.htmlPrefix,decoded:v.decoded}:null,requests:this.summarizeNetwork(g,r)}}catch(e){n.error=e,n.hasError=!0}finally{const e=t(n);e&&await e}}async waitForAiAnswer(e,t){const r=Date.now();for(;Date.now()-r<Math.min(t,45e3);){await e.waitForTimeout(750);const t=await e.evaluate(()=>{const e=Array.from(document.querySelectorAll('[jsname="KFl8ub"], [data-attrid], .kp-wholepage')).map(e=>e.innerText?.trim()).filter(Boolean).find(e=>!/^(Sources|Related|AI Mode response is ready)$/i.test(e));if(e)return e;const t=(document.body?.innerText||"").split("\n").map(e=>e.trim()).filter(Boolean),r=t.findIndex(e=>/AI Mode response is ready/i.test(e));return r>0?t[r-1]:null});if(t)return t}return null}async collectPageState(e,t){return await e.evaluate(e=>{const t=document.body?.innerText||"",r=document.documentElement?.outerHTML||"";return{title:document.title,url:document.location.href,bodyText:t.slice(0,e),htmlPrefix:r.slice(0,e)}},t)}async buildFolwrEndpoint(e){const t=await e.evaluate(()=>{const e=document.querySelector("[data-garc][data-lro-token][data-lro-signature][data-ei]");if(!e)return{url:null,error:"Missing AI Mode token container"};const t=document.getElementById("rKxeg")?.getAttribute("data-stkp")||null;return{origin:document.location.origin,search:document.location.search,stkp:t,fmt:document.querySelector("[data-madl]")?"madl":"adl",tokens:{ei:e.dataset.ei,garc:e.dataset.garc,lroToken:e.dataset.lroToken,lroSignature:e.dataset.lroSignature,xsrfFolwrToken:e.dataset.xsrfFolwrToken||null,srtst:e.dataset.srtst||null,hasGarc:!!e.dataset.garc,hasLroToken:!!e.dataset.lroToken,hasLroSignature:!!e.dataset.lroSignature,hasXsrf:!!e.dataset.xsrfFolwrToken,hasStkp:!!t}}});if(!t?.origin)return t;const r=function(e){const t={};for(const r of String(e??"").replace(/^\?/,"").split("&")){if(!r)continue;const e=r.indexOf("="),o=-1===e?r:r.slice(0,e),n=-1===e?"":r.slice(e+1);t[i(o)]=i(n)}return t}(t.search),n={},s=["q","udm","mstk","csuir","mtid","ved","vet","sei","dpr","hl","gl","source","vsrid","lns_img","cinpts"];for(const e of s)r[e]&&(n[e]=r[e]);t.tokens.srtst&&(n.srtst=t.tokens.srtst),n.garc=t.tokens.garc,n.mlro=t.tokens.lroToken,n.mlros=t.tokens.lroSignature,n.ei=t.tokens.ei,t.stkp&&(n.stkp=t.stkp);const l={_fmt:t.fmt};t.tokens.xsrfFolwrToken&&(l._xsrf=t.tokens.xsrfFolwrToken);const c=a(n),u=Object.entries(l).map(([e,t])=>`${o(e)}:${o(t)}`).join(",");return{url:`${t.origin}/async/folwr?${c}&async=${u}`,tokens:{ei:t.tokens.ei,hasGarc:t.tokens.hasGarc,hasLroToken:t.tokens.hasLroToken,hasLroSignature:t.tokens.hasLroSignature,hasXsrf:t.tokens.hasXsrf,hasStkp:t.tokens.hasStkp}}}summarizeNetwork(e,t){return e.map(e=>{const r=null==e.body?null:d({body:e.body,contentType:e.mimeType});return{method:e.method,type:e.type,url:e.url,status:e.status,mimeType:e.mimeType,decodedFormat:r?.format??null,recordCount:r?.records?.length??0,bodyPrefix:null==e.body?null:e.body.slice(0,t)}})}}function o(e){let t="";const r=String(e??"");for(let e=0;e<r.length;e++){const o=r.codePointAt(e);if(void 0===o)continue;const s=String.fromCodePoint(o);if(o>65535&&e++," "===s)t+="+";else if("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~".includes(s))t+=s;else for(const e of n(o))t+=`%${e.toString(16).toUpperCase().padStart(2,"0")}`}return t}function n(e){return e<=127?[e]:e<=2047?[192|e>>6,128|63&e]:e<=65535?[224|e>>12,128|e>>6&63,128|63&e]:[240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e]}function s(e){let t="";for(let r=0;r<e.length;r++){const o=e[r];let n=o;192==(224&o)?n=(31&o)<<6|63&e[++r]:224==(240&o)?n=(15&o)<<12|(63&e[++r])<<6|63&e[++r]:240==(248&o)&&(n=(7&o)<<18|(63&e[++r])<<12|(63&e[++r])<<6|63&e[++r]),t+=String.fromCodePoint(n)}return t}function i(e){const t=String(e??"").replace(/\+/g," ");let r="";for(let e=0;e<t.length;e++){if("%"!==t[e]||!/[0-9a-fA-F]{2}/.test(t.slice(e+1,e+3))){r+=t[e];continue}const o=[];for(;"%"===t[e]&&/[0-9a-fA-F]{2}/.test(t.slice(e+1,e+3));)o.push(Number.parseInt(t.slice(e+1,e+3),16)),e+=3;e--;try{r+=s(o)}catch{r+=o.map(e=>`%${e.toString(16).toUpperCase().padStart(2,"0")}`).join("")}}return r}function a(e){return Object.entries(e).filter(([,e])=>null!=e&&""!==e).map(([e,t])=>`${o(e)}=${o(t)}`).join("&")}function l(e){return e.replace(/^\s*\)\]\}'\s*\n?/,"")}function c(e){try{return JSON.parse(e)}catch{return}}function u(e){const t=[];for(const r of l(e).split(/\r?\n/)){const e=r.trim();if(!e)continue;const o=/^([a-zA-Z0-9_-]+);(.*)$/.exec(e);if(!o)continue;const n=o[2].trim();t.push({id:o[1],value:c(n)??n})}return t}function d({body:e,contentType:t=""}){const r=String(e??""),o=l(r).trimStart(),n=/^\s*\)\]\}'/.test(r),s=/^[\[{]/.test(o)?c(o):void 0,i=void 0===s?u(r):[];let a="text";return(t.includes("html")||/^\s*</.test(o))&&(a="html"),n&&void 0!==s?a="google-xssi-json":n&&i.length?a="google-xssi-record-stream":void 0!==s?a="json":i.length&&(a="google-record-stream"),{format:a,xssiPrefixed:n,parsed:s,records:i}}function f(e,t=4e3){return String(e??"").replace(/\u0000/g,"").replace(/[ \t]+\n/g,"\n").trim().slice(0,t)}function p(e){const t=String(e??"").split("\n").map(e=>e.trim()).filter(Boolean),r=t.findIndex(e=>/AI Mode response is ready/i.test(e));if(r>0)return t[r-1];const o=/^(Skip to main content|Accessibility help|Accessibility feedback|AI Mode|All|Images|Videos|News|More|Search Results|Sources|Related)$/i;return t.find(e=>!o.test(e))??null}function m(e){let t=e.replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").replace(/ /g," ");return t=function(e){let t="",r=0;for(;r<e.length;){const o=e.toLowerCase().indexOf("<style",r);if(-1===o){t+=e.slice(r);break}t+=e.slice(r,o);const n=e.toLowerCase().indexOf("</style>",o);if(-1===n)break;r=n+8}let o="";r=0;for(;r<t.length;){const e=t.toLowerCase().indexOf("<script",r);if(-1===e){o+=t.slice(r);break}o+=t.slice(r,e);const n=t.toLowerCase().indexOf("<\/script>",e);if(-1===n)break;r=n+9}return o}(t),t=t.replace(/<[^>]+>/g," "),t.replace(/\s+/g," ").trim()}function h(e){if(!e)return null;if("string"==typeof e){const t=e.trim();return t.startsWith("<")||t.includes("class=")||t.includes("id=")?e:null}if(Array.isArray(e))for(const t of e){const e=h(t);if(e)return e}else if("object"==typeof e){if("string"==typeof e.html)return e.html;if("string"==typeof e.aimc_block?.html)return e.aimc_block.html;if("string"==typeof e.value&&(e.value.startsWith("<")||e.value.includes("class=")))return e.value;for(const t of Object.values(e)){const e=h(t);if(e)return e}}return null}
|
|
@@ -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.",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"}}
|
|
1
|
+
import{defineAdapter as e}from"../../adapter/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"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{defineAdapter as t}from"../../adapter/base-adapter.js";const n="https://www.perplexity.ai/rest/sse/perplexity_ask";export default t({id:"perplexity",name:"Perplexity AI Ask",domain:"perplexity.ai",description:"Fetches live streaming answers from Perplexity AI using its private REST/SSE API.",positionals:[{name:"question",description:"The query or question to ask Perplexity AI",required:!0,variadic:!0}],parameters:[{name:"model",type:"string",description:"Model preference (e.g. 'claude46sonnet', 'sonar-reasoning')",default:"claude46sonnet",short:"m"},{name:"out",type:"string",description:"Write decoded response JSON to file instead of stdout"},{name:"timeout",type:"number",description:"Request timeout in milliseconds",default:75e3},{name:"text",type:"boolean",description:"Print only the extracted text answer",short:"t"}],async fetchData(t,r){const i=r.question,a=r.model||"claude46sonnet",c=void 0!==r.timeout?Number(r.timeout):75e3;if(!t||0===t.length)throw new Error("No login found in browser. Please log in to perplexity.ai in Google Chrome.");const d=this.buildCookieString(t),l={params:{attachments:[],language:"en-US",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"America/New_York",search_focus:"internet",sources:["web"],frontend_uuid:e(),mode:"copilot",model_preference:"claude46sonnet",is_related_query:!1,is_sponsored:!1,frontend_context_uuid:e(),prompt_source:"user",query_source:"home",is_incognito:!1,time_from_first_type:1,local_search_enabled:!1,use_schematized_api:!0,send_back_text_in_streaming_api:!1,supported_block_use_cases:["answer_modes","media_items","knowledge_cards","inline_entity_cards","place_widgets","finance_widgets","prediction_market_widgets","sports_widgets","flight_status_widgets","news_widgets","shopping_widgets","jobs_widgets","search_result_widgets","inline_images","inline_assets","placeholder_cards","diff_blocks","inline_knowledge_cards","entity_group_v2","refinement_filters","canvas_mode","maps_preview","answer_tabs","price_comparison_widgets","preserve_latex","generic_onboarding_widgets","in_context_suggestions","pending_followups","inline_claims","unified_assets","workflow_steps","background_agents"],client_coordinates:null,mentions:[],dsl_query:"",skip_search_enabled:!0,is_nav_suggestions_disabled:!1,source:"default",always_search_override:!1,override_no_search:!1,should_ask_for_mcp_tool_confirmation:!0,browser_agent_allow_once_from_toggle:!1,force_enable_browser_agent:!1,supported_features:["browser_agent_permission_banner_v1.1"],extended_context:!1,version:"2.18"},query_str:""};l.params.frontend_uuid=e(),l.params.frontend_context_uuid=e(),l.params.model_preference=a,l.params.dsl_query=i,l.params.time_from_first_type=1,l.query_str=i;const u=new AbortController,_=setTimeout(()=>u.abort(),c),p=this.resolveUserAgent(r);let f;try{f=await fetch(n,{method:"POST",headers:{accept:"text/event-stream","accept-language":"en-US,en;q=0.9","content-type":"application/json",origin:"https://www.perplexity.ai",referer:"https://www.perplexity.ai/","user-agent":p,"x-perplexity-request-endpoint":n,"x-perplexity-request-reason":"ask-query-state-provider","x-perplexity-request-try-number":"1","x-request-id":l.params.frontend_uuid,cookie:d},body:JSON.stringify(l),signal:u.signal})}catch(e){if(e instanceof Error&&"AbortError"===e.name)throw new Error(`Perplexity request timed out after ${c}ms.`);throw e}finally{clearTimeout(_)}const m=f.headers.get("content-type")||"";if(!f.ok){const e=await f.text();throw new Error(`HTTP ${f.status} (${f.statusText}): ${e}`)}if(m.includes("text/event-stream")){const e=await async function(e){const t=new TextDecoder,n=[],r={};let i=null,a="";function c(e){const t=[];for(const n of e.split("\n"))n.startsWith("data:")&&t.push(n.slice(5).trimStart());if(t.length)try{const e=JSON.parse(t.join("\n"));if(!e||"object"==typeof e&&0===Object.keys(e).length)return;n.push(e),function(e,t){for(const n of t?.blocks??[]){const t=n.intended_usage;for(const[r,s]of Object.entries(n))"intended_usage"!==r&&"diff_block"!==r&&t&&(e[t]=s);const r=n.diff_block;if(r?.field){e[r.field]??=null;for(const t of r.patches??[])e[r.field]=s(e[r.field],t)}}}(r,e),(e.final||"COMPLETED"===e.status||e.text_completed)&&(i=e)}catch{}}const d=e.body?.getReader();if(d)for(;;){const{value:e,done:n}=await d.read();if(n)break;for(a+=t.decode(e,{stream:!0}).replace(/\r\n/g,"\n");;){const e=a.indexOf("\n\n");if(-1===e)break;c(a.slice(0,e)),a=a.slice(e+2)}}else if(e.body&&Symbol.asyncIterator in e.body)for await(const n of e.body)for(a+=t.decode(n,{stream:!0}).replace(/\r\n/g,"\n");;){const e=a.indexOf("\n\n");if(-1===e)break;c(a.slice(0,e)),a=a.slice(e+2)}a+=t.decode(),a.trim()&&c(a);!i&&n.length&&(i=n[n.length-1]);return{chunks:n,state:r,final:i,answer:o(i,r)}}(f);return{endpoint:n,http_code:f.status,content_type:m,request:{query:i,model_preference:a,frontend_uuid:l.params.frontend_uuid,frontend_context_uuid:l.params.frontend_context_uuid},answer:e.answer,state:e.state,final:e.final,chunks:e.chunks}}{const e=await f.text();let t;try{t=JSON.parse(e)}catch{t=e}return{endpoint:n,http_code:f.status,content_type:m,request:{query:i,model_preference:a,frontend_uuid:l.params.frontend_uuid,frontend_context_uuid:l.params.frontend_context_uuid},body:t}}}});function r(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function s(e,t){const n=t.op,s=t.path??"";if(""===s){if("replace"===n||"add"===n)return t.value;if("remove"===n)return null}const o=s.split("/").slice(1).map(r);null==e&&(e=/^\d+$/.test(o[0]??"")?[]:{});let i=e;for(let e=0;e<o.length-1;e++){const t=o[e],n=/^\d+$/.test(o[e+1]??"");if(Array.isArray(i)){const e=Number(t);for(;i.length<=e;)i.push(n?[]:{});i=i[e]}else i[t]??=n?[]:{},i=i[t]}const a=o.at(-1);if(null==a)return e;if(Array.isArray(i)){const e="-"===a?i.length:Number(a);"add"===n?i.splice(e,0,t.value):"replace"===n?i[e]=t.value:"remove"===n&&i.splice(e,1)}else"remove"===n?delete i[a]:"add"!==n&&"replace"!==n||(i[a]=t.value);return e}function o(e,t){for(const t of e?.blocks??[]){const e=t.markdown_block;if(null!=e?.answer)return e.answer;if(Array.isArray(e?.chunks))return e.chunks.join("")}for(const e of["ask_text","ask_text_0_markdown","markdown_block"]){const n=t[e];if(null!=n?.answer)return n.answer;if(Array.isArray(n?.chunks))return n.chunks.join("")}return null}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { PlaywrightAdapter } from "../../adapter/playwright-attatch-chrome-adapter.js";
|
|
2
|
+
export default class extends PlaywrightAdapter {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
description: string;
|
|
7
|
+
optionalCookies: boolean;
|
|
8
|
+
endpoints: {
|
|
9
|
+
url: string;
|
|
10
|
+
}[];
|
|
11
|
+
positionals: {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
required: boolean;
|
|
15
|
+
}[];
|
|
16
|
+
parameters: ({
|
|
17
|
+
name: string;
|
|
18
|
+
type: "string";
|
|
19
|
+
description: string;
|
|
20
|
+
default: string;
|
|
21
|
+
short: string;
|
|
22
|
+
} | {
|
|
23
|
+
name: string;
|
|
24
|
+
type: "boolean";
|
|
25
|
+
description: string;
|
|
26
|
+
short: string;
|
|
27
|
+
default?: undefined;
|
|
28
|
+
})[];
|
|
29
|
+
fetchData(cookies: any, options: any): Promise<string>;
|
|
30
|
+
private gotoDashboardIfNeeded;
|
|
31
|
+
private ensurePropertyOverlayOpen;
|
|
32
|
+
private getProperties;
|
|
33
|
+
private selectPropertyByIndex;
|
|
34
|
+
private selectPropertyByName;
|
|
35
|
+
private openDownloadPanelIfNeeded;
|
|
36
|
+
private setNativeSelectValue;
|
|
37
|
+
private getSelectOptions;
|
|
38
|
+
private setDownloadOptions;
|
|
39
|
+
private fetchDownloadCsvText;
|
|
40
|
+
private normalizeInterval;
|
|
41
|
+
private intervalToValue;
|
|
42
|
+
private inferServiceTypeFromPropertyTitle;
|
|
43
|
+
private extractAddressNumber;
|
|
44
|
+
private formatPropertyLabel;
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=this&&this.__addDisposableResource||function(e,t,r){if(null!=t){if("object"!=typeof t&&"function"!=typeof t)throw new TypeError("Object expected.");var o,a;if(r){if(!Symbol.asyncDispose)throw new TypeError("Symbol.asyncDispose is not defined.");o=t[Symbol.asyncDispose]}if(void 0===o){if(!Symbol.dispose)throw new TypeError("Symbol.dispose is not defined.");o=t[Symbol.dispose],r&&(a=o)}if("function"!=typeof o)throw new TypeError("Object not disposable.");a&&(o=function(){try{a.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:t,dispose:o,async:r})}else r&&e.stack.push({async:!0});return t},t=this&&this.__disposeResources||function(e){return function(t){function r(r){t.error=t.hasError?new e(r,t.error,"An error was suppressed during disposal."):r,t.hasError=!0}var o,a=0;return function e(){for(;o=t.stack.pop();)try{if(!o.async&&1===a)return a=0,t.stack.push(o),Promise.resolve().then(e);if(o.dispose){var i=o.dispose.call(o.value);if(o.async)return a|=2,Promise.resolve(i).then(e,function(t){return r(t),e()})}else a|=1}catch(e){r(e)}if(1===a)return t.hasError?Promise.reject(t.error):Promise.resolve();if(t.hasError)throw t.error}()}}("function"==typeof SuppressedError?SuppressedError:function(e,t,r){var o=new Error(r);return o.name="SuppressedError",o.error=e,o.suppressed=t,o});import{PlaywrightAdapter as r}from"../../adapter/playwright-attatch-chrome-adapter.js";import{performFormLogin as o}from"../../util/login-helper.js";export default class extends r{id="pseg-usage";name="PSEG Usage";domain="mysmartenergy.nj.pseg.com";description="Downloads PSEG Smart Energy usage data (CSV) or lists available properties.";optionalCookies=!0;endpoints=[{url:"https://mysmartenergy.nj.pseg.com"}];positionals=[{name:"property",description:"Property name (e.g. '100 Electric') or 1-based index to download usage for.",required:!1}];parameters=[{name:"interval",type:"string",description:"Select interval: 15, 30, hourly, daily, weekly, monthly, billing",default:"billing",short:"i"},{name:"list",type:"boolean",description:"List all downloadable properties instead of downloading",short:"l"}];async fetchData(r,a){const i={stack:[],error:void 0,hasError:!1};try{const t=!!a.debug,{username:r,password:n}=this.resolveCredentials(a),s=e(i,await this.connect(a),!0);if(await o({page:s,intendedUrl:"https://mysmartenergy.nj.pseg.com/Dashboard",emailSelector:"#LoginEmail",emailValue:r,passwordSelector:"#LoginPassword",passwordValue:n,submitButtonSelector:"button.loginBtn",delayMs:1e3,debug:t}),a.list){t&&console.log("Listing downloadable properties...");const e=await this.getProperties(s,a);let r=["Downloadable properties:"];for(const t of e){const e=t.isCurrent?" [current]":"";r.push(`${this.formatPropertyLabel(t)}${e} | ${t.address} | ${t.owner}`)}return r.join("\n")}const l=a.property;if(!l)throw new Error("Missing required argument: <property> or --list");let c="Electric",d=this.normalizeInterval(a.interval),p=l;const u=String(l).trim();if(/^\d+$/.test(u)){const e=await this.selectPropertyByIndex(s,Number(u),a);c=e.serviceType,p=e.label}else{const e=await this.selectPropertyByName(s,u,a);c=e.serviceType,p=e.label}t&&console.log(`Opening dashboard and downloading ${p} usage CSV for ${d}...`),await this.gotoDashboardIfNeeded(s,a),await this.openDownloadPanelIfNeeded(s,t),d=await this.setDownloadOptions(s,c,d);return await this.fetchDownloadCsvText(s)}catch(e){i.error=e,i.hasError=!0}finally{const e=t(i);e&&await e}}async gotoDashboardIfNeeded(e,t){if(!e.url().toLowerCase().startsWith("https://mysmartenergy.nj.pseg.com/dashboard")){t.debug&&console.log(`Current URL '${e.url()}' is not in dashboard. Navigating to Dashboard...`),await e.goto("https://mysmartenergy.nj.pseg.com/Dashboard",{waitUntil:"domcontentloaded"});if(await e.locator("#LoginEmail").isVisible().catch(()=>!1)){t.debug&&console.log("Session expired or redirected to login. Re-authenticating...");const{username:r,password:a}=this.resolveCredentials(t);await o({page:e,intendedUrl:"https://mysmartenergy.nj.pseg.com/Dashboard",emailSelector:"#LoginEmail",emailValue:r,passwordSelector:"#LoginPassword",passwordValue:a,submitButtonSelector:"button.loginBtn",delayMs:1e3,debug:!!t.debug})}}}async ensurePropertyOverlayOpen(e,t){await this.gotoDashboardIfNeeded(e,t);if(!await e.locator('.selectPropertyContainer input[placeholder="Search"]').isVisible().catch(()=>!1)){const r=e.getByRole("link",{name:"Select Property"});await r.waitFor({state:"visible",timeout:5e3}).catch(()=>{});try{await r.click()}catch(r){t.debug&&console.warn("Failed to click 'Select Property' by role, trying text locator:",r),await e.locator('a:has-text("Select Property")').first().click()}await e.waitForSelector(".selectPropertyContainer",{state:"visible",timeout:5e3})}}async getProperties(e,t){return await this.ensurePropertyOverlayOpen(e,t),await e.evaluate(()=>Array.from(document.querySelectorAll(".selectPropertyContainer li")).map(e=>{const t=Array.from(e.querySelectorAll("h4"));return{propertyId:e.getAttribute("data-property-id")||"",propertyType:e.getAttribute("data-property-type")||"",title:e.querySelector("h2")?.textContent?.trim()||"",owner:t[0]?.textContent?.trim()||"",address:t[1]?.textContent?.trim()||"",isCurrent:e.classList.contains("current")}}))}async selectPropertyByIndex(e,t,r){const o=await this.getProperties(e,r);if(!Number.isInteger(t)||t<1||t>o.length)throw new Error(`Property index ${t} is out of range. Use 1-${o.length}.`);const a=o[t-1];return await e.goto(`https://mysmartenergy.nj.pseg.com/Dashboard/SetMeterGroup?meterGroupId=${a.propertyId}`,{waitUntil:"domcontentloaded"}),await e.waitForTimeout(1e3),{label:this.formatPropertyLabel(a),title:a.title,serviceType:this.inferServiceTypeFromPropertyTitle(a.title)}}async selectPropertyByName(e,t,r){const o=String(t).trim().toLowerCase();if(!o)throw new Error("Property name is required.");const a=await this.getProperties(e,r);let i=a.find(e=>String(e.title).trim().toLowerCase()===o);if(i||(i=a.find(e=>this.formatPropertyLabel(e).toLowerCase()===o)),!i){const e=a.map(e=>this.formatPropertyLabel(e)).filter(Boolean).join(", ");throw new Error(`Property not found: ${t}. Available properties: ${e}`)}return await e.goto(`https://mysmartenergy.nj.pseg.com/Dashboard/SetMeterGroup?meterGroupId=${i.propertyId}`,{waitUntil:"domcontentloaded"}),await e.waitForTimeout(1e3),{label:this.formatPropertyLabel(i),title:i.title,serviceType:this.inferServiceTypeFromPropertyTitle(i.title)}}async openDownloadPanelIfNeeded(e,t){const r="#downloadOptions";if(!await e.locator(r).isVisible().catch(()=>!1)){t&&console.log("Clicking 'Data' link...");try{await e.getByRole("link",{name:"Data"}).click()}catch(r){t&&console.warn("Failed to click 'Data' by role, trying text locator:",r),await e.locator('a:has-text("Data")').first().click()}try{return void await e.waitForSelector(r,{state:"visible",timeout:5e3})}catch{}t&&console.log("Clicking 'download' link...");try{await e.getByRole("link",{name:"download"}).click()}catch(r){t&&console.warn("Failed to click 'download' by role, trying text locator:",r),await e.locator('a:has-text("download")').first().click()}await e.waitForSelector(r,{state:"visible",timeout:1e4})}}async setNativeSelectValue(e,t,r){const o=`select[name="${t}"]`;return await e.evaluate(({selector:e,value:t})=>{const r=document.querySelector(e);if(!r)throw new Error(`${e} select not found`);return r.value=t,r.dispatchEvent(new Event("change",{bubbles:!0})),{value:r.value}},{selector:o,value:r})}async getSelectOptions(e,t){const r=`select[name="${t}"]`;return await e.evaluate(e=>{const t=document.querySelector(e);if(!t)throw new Error(`${e} select not found`);return{value:t.value,options:Array.from(t.options).map(e=>({value:e.value,text:e.textContent?.trim()||""}))}},r)}async setDownloadOptions(e,t,r){const o="Gas"===t?"4":"1";if("Gas"===t&&"Billing"!==r)throw new Error("Gas usage only supports the Billing interval.");const a=this.intervalToValue(r);if(!a)throw new Error(`Unsupported interval mapping: ${r}`);await e.waitForSelector('select[name="SelectedServiceType"]',{state:"attached",timeout:5e3});const i=await this.setNativeSelectValue(e,"SelectedServiceType",o);if(i.value!==o)throw new Error(`Failed to set service type: ${JSON.stringify(i)}`);await e.waitForTimeout(500),await e.waitForSelector('select[name="SelectedInterval"]',{state:"attached",timeout:5e3});const n=await this.getSelectOptions(e,"SelectedInterval");if(!new Set(n.options.map(e=>e.value)).has(a)){const e=n.options.map(e=>e.text).join(", ");throw new Error(`Interval ${r} is not available for ${t}. Available intervals: ${e}`)}const s=n.value===a?n:await this.setNativeSelectValue(e,"SelectedInterval",a);if(s.value!==a)throw new Error(`Failed to set interval: ${JSON.stringify(s)}`);return r}async fetchDownloadCsvText(e){return await e.evaluate(async()=>{const e=document.querySelector("#downloadOptions");if(!e)throw new Error("Download form not found");const t=await fetch(e.action,{method:"POST",body:new FormData(e),credentials:"same-origin"});if(!t.ok)throw new Error("Download request failed with status "+t.status+" "+t.statusText);return await t.text()})}normalizeInterval(e){const t={15:"15-Minute","15-minute":"15-Minute",30:"30-Minute","30-minute":"30-Minute",hourly:"Hourly",daily:"Daily",weekly:"Weekly",monthly:"Monthly",billing:"Billing"}[String(e||"").trim().toLowerCase()];if(!t)throw new Error(`Invalid interval: ${e}. Use 15, 30, hourly, daily, weekly, monthly, or billing.`);return t}intervalToValue(e){return{"15-Minute":"3","30-Minute":"4",Hourly:"5",Daily:"6",Weekly:"8",Monthly:"9",Billing:"7"}[e]}inferServiceTypeFromPropertyTitle(e){if(/\bgas\b/i.test(e))return"Gas";if(/\belectric\b/i.test(e))return"Electric";throw new Error(`Could not infer service type from property title: ${e}`)}extractAddressNumber(e){const t=String(e||"").match(/^(\d+)/);return t?t[1]:""}formatPropertyLabel(e){const t=String(e.title||"").trim();if(t)return t;const r=e.propertyType||this.inferServiceTypeFromPropertyTitle(e.title);return`${this.extractAddressNumber(e.address)||e.propertyId||"Unknown"} ${r}`}}
|
package/dist/src/website-api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdirSync as t}from"node:fs";import{dirname as
|
|
1
|
+
import{readdirSync as t}from"node:fs";import{dirname as e,join as o}from"node:path";import{fileURLToPath as r,pathToFileURL as i}from"node:url";import{loadEnv as n}from"./env.js";import{createUniversalAdapter as s}from"./adapter/universal-adapter.js";n();const a=e(r(import.meta.url)),c=o(a,"website");export let websites=[];export async function loadAdapters(){if(!(websites.length>0))try{const e=t(c,{withFileTypes:!0});for(const r of e){if(!r.isDirectory())continue;const e=o(c,r.name),n=t(e).find(t=>t.endsWith("-adapter.js"));if(!n)continue;const s=i(o(e,n)).href;let a=(await import(s)).default;if("function"==typeof a)try{a=new a}catch{}a?.id&&websites.push(a)}}catch(t){}}export function getWebsite(t){if(!t)return null;const e=t.toLowerCase().trim();return websites.find(t=>t.id.toLowerCase()===e||t.id.toLowerCase().replace(".com","")===e||t.domain.toLowerCase()===e||t.domain.toLowerCase().replace(".com","")===e)??null}export async function queryWebsite(t,e={}){await loadAdapters();let o=getWebsite(t);if(!o){if(!function(t){return t.startsWith("http://")||t.startsWith("https://")||t.includes(".")||t.includes("/")||t.includes(":")}(t))throw new Error("command not found");if(o=s(t),!o)throw new Error("command not found")}const r=o.resolveCookies(e);o._lastQueryOptions=e;try{const t=await o.fetchData(r,e);return void 0===t&&void 0!==e.htmlResult?{html:e.htmlResult}:t}finally{delete o._lastQueryOptions}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "website-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "CLI and library to fetch website API data",
|
|
5
5
|
"main": "./dist/src/website-api.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,13 +21,14 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"chalk": "^5.6.2",
|
|
24
|
-
"chrome-tools": "
|
|
24
|
+
"chrome-tools": "1.0.7",
|
|
25
25
|
"cli-table3": "^0.6.5",
|
|
26
|
-
"commander": "^14.0.3"
|
|
26
|
+
"commander": "^14.0.3",
|
|
27
|
+
"playwright-core": "^1.60.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^25.9.1",
|
|
30
|
-
"terser": "^5.
|
|
31
|
+
"terser": "^5.48.0",
|
|
31
32
|
"typescript": "^6.0.3"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { AdapterConfig, WebsiteAdapter } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Factory function to create a fully-equipped WebsiteAdapter from a minimal config.
|
|
4
|
-
*
|
|
5
|
-
* Simple adapters only need `id`, `name`, `domain`, `description`, and `endpoints`.
|
|
6
|
-
* Complex adapters can provide a custom `fetchData` that uses `this.buildCookieString()`,
|
|
7
|
-
* `this.resolveUserAgent()`, and `this.fetchJson()`.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* // Simple adapter (~5 lines of config):
|
|
11
|
-
* export default defineAdapter({
|
|
12
|
-
* id: "cursor.com",
|
|
13
|
-
* name: "Cursor Usage",
|
|
14
|
-
* domain: "cursor.com",
|
|
15
|
-
* description: "Fetches Cursor usage summary.",
|
|
16
|
-
* endpoints: [{ url: "https://cursor.com/api/usage-summary" }],
|
|
17
|
-
* });
|
|
18
|
-
*/
|
|
19
|
-
export declare function defineAdapter(config: AdapterConfig): WebsiteAdapter;
|
package/dist/src/base-adapter.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
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}
|
|
File without changes
|