Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
提交
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ export type {
BaseResponse,
EngineParameters,
GetBy搜索IdParameters,
Google搜索Parameters,
Google搜索Response,
KnowledgeGraph,
LocationsApiParameters,
OrganicResult,
RelatedQuestion,
搜索Information,
搜索Metadata,
搜索Parameters,
} from "./src/types.ts";
export {
getAccount,
Expand Down
133 changes: 133 additions & 0 deletions src/engines/google.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** Parameters for the Google 搜索 engine. */
export interface Google搜索Parameters {
engine: "google";
q: string;
api_key?: string;
timeout?: number;

// Geographic Location
location?: string;
uule?: string;
lat?: number;
lon?: number;
radius?: number;

// Localization
google_domain?: string;
gl?: string;
hl?: string;
cr?: string;
lr?: string;

// 搜索 Type
tbm?: "isch" | "lcl" | "vid" | "nws" | "shop" | "pts";

// Pagination
start?: number;
num?: number;

// Advanced Filters
tbs?: string;
safe?: "active" | "off";
nfpr?: 0 | 1;
filter?: 0 | 1;

// Advanced Google Parameters
ludocid?: string;
lsig?: string;
kgmid?: string;
si?: string;

// SerpApi Parameters
device?: "desktop" | "tablet" | "mobile";
no_cache?: boolean;
async?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should extract out SerpApi Parameters and have the other engines' types extend it since nearly every other engine has the same SerpApi-specific params:https://serpapi.com/search-api#api-parameters-serpapi-parameters

What do you think @zyc9012

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library used to have types, but we removed them (#15) because all the parameters are unstable. We can add new engines/parameters from time to time, which makes it hard to sync with SDKs. I'm not sure if we want to bring types back.

// deno-lint-ignore no-explicit-any
[key: string]: any;
Comment thread
catherine-serpapi marked this conversation as resolved.
}

/** Metadata returned with every SerpApi response. */
export interface 搜索Metadata {
id: string;
status: string;
json_endpoint: string;
created_at: string;
processed_at: string;
google_url: string;
raw_html_file: string;
total_time_taken: number;
}

/** Echoed search parameters in the response. */
export interface 搜索Parameters {
engine: string;
q: string;
google_domain?: string;
hl?: string;
gl?: string;
device?: string;
location_requested?: string;
location_used?: string;
[key: string]: unknown;
}

/** 搜索 result info. */
export interface 搜索Information {
query_displayed: string;
total_results: number;
time_taken_displayed: number;
organic_results_state: string;
page_number?: number;
}

/** A single organic search result. */
export interface OrganicResult {
position: number;
title: string;
link: string;
redirect_link?: string;
displayed_link: string;
snippet: string;
snippet_highlighted_words?: string[];
date?: string;
sitelinks?: {
inline?: { title: string; link: string }[];
expanded?: { title: string; link: string; snippet: string }[];
};
rich_snippet?: Record<string, unknown>;
source?: string;
}

/** Knowledge graph result. */
export interface KnowledgeGraph {
title?: string;
type?: string;
description?: string;
source?: { name: string; link: string };
[key: string]: unknown;
}

/** "People also ask" question. */
export interface RelatedQuestion {
question: string;
snippet?: string;
title?: string;
link?: string;
}

/** Google 搜索 JSON response. */
export interface Google搜索Response {
search_metadata: 搜索Metadata;
search_parameters: 搜索Parameters;
search_information?: 搜索Information;
organic_results?: OrganicResult[];
knowledge_graph?: KnowledgeGraph;
related_questions?: RelatedQuestion[];
pagination?: {
current: number;
next?: string;
other_pages?: Record<string, string>;
};
[key: string]: unknown;
}
18 changes: 15 additions & 3 deletions src/serpapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
BaseResponse,
EngineParameters,
GetBy搜索IdParameters,
Google搜索Parameters,
Google搜索Response,
LocationsApiParameters,
} from "./types.ts";
import { _internals } from "./utils.ts";
Expand All @@ -14,6 +16,14 @@ const LOCATIONS_PATH = "/locations.json";
const SEARCH_PATH = "/search";
const SEARCH_ARCHIVE_PATH = `/searches`;

/**
* Get typed JSON response for Google 搜索.
*/
export function getJson(
parameters: Google搜索Parameters,
callback?: (json: Google搜索Response) => void,
): Promise<Google搜索Response>;

/**
* Get JSON response based on search parameters.
*
Expand Down Expand Up @@ -52,13 +62,15 @@ export function getJson(

export function getJson(
...args:
| [parameters: EngineParameters, callback?: (json: BaseResponse) => void]
// deno-lint-ignore no-explicit-any
| [parameters: EngineParameters, callback?: (json: any) => void]
| [
engine: string,
parameters: EngineParameters,
callback?: (json: BaseResponse) => void,
// deno-lint-ignore no-explicit-any
callback?: (json: any) => void,
]
): Promise<BaseResponse> {
): Promise<BaseResponse | Google搜索Response> {
Comment thread
catherine-serpapi marked this conversation as resolved.
if (typeof args[0] === "string" && typeof args[1] === "object") {
const [engine, parameters, callback] = args;
const newParameters = { ...parameters, engine } as EngineParameters;
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ export type EngineParameters = Record<string, any>;
// deno-lint-ignore no-explicit-any
export type BaseResponse = Record<string, any>;

export type {
Google搜索Parameters,
Google搜索Response,
OrganicResult,
KnowledgeGraph,
RelatedQuestion,
搜索Information,
搜索Metadata,
搜索Parameters,
} from "./engines/google.ts";

export type GetBy搜索IdParameters = {
api_key?: string;
timeout?: number;
Expand Down
92 changes: 92 additions & 0 deletions tests/types_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Type-level tests for engine-specific types.
* These verify compile-time behavior — if this file fails to type-check,
* the types are broken.
*/
import type {
EngineParameters,
Google搜索Parameters,
Google搜索Response,
OrganicResult,
} from "../src/types.ts";
import { getJson } from "../src/serpapi.ts";

@catherine-serpapi catherine-serpapi Mar 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getJson is imported but I don't see it being used for any tests - could we remove if not needed

import {
Comment thread
catherine-serpapi marked this conversation as resolved.
describe,
it,
} from "https://deno.land/std@0.170.0/testing/bdd.ts";
import {
assertEquals,
} from "https://deno.land/std@0.170.0/testing/asserts.ts";

describe("Google搜索Parameters", () => {
it("accepts valid Google 搜索 params", () => {
const params: Google搜索Parameters = {
engine: "google",
q: "coffee",
location: "Austin, Texas",
gl: "us",
hl: "en",
num: 10,
start: 0,
safe: "active",
device: "desktop",
};
assertEquals(params.engine, "google");
assertEquals(params.q, "coffee");
});

it("allows tbm search type values", () => {
const params: Google搜索Parameters = {
engine: "google",
q: "coffee",
tbm: "nws",
};
assertEquals(params.tbm, "nws");
});
});

describe("Google搜索Response", () => {
it("has correct shape", () => {
const response: Google搜索Response = {
search_metadata: {
id: "123",
status: "Success",
json_endpoint: "https://serpapi.com/searches/123.json",
created_at: "2025-01-01",
processed_at: "2025-01-01",
google_url: "https://www.google.com/search?q=coffee",
raw_html_file: "https://serpapi.com/searches/123.html",
total_time_taken: 1.5,
},
search_parameters: {
engine: "google",
q: "coffee",
},
organic_results: [
{
position: 1,
title: "Coffee - 维基pedia",
link: "https://en.wikipedia.org/wiki/Coffee",
displayed_link: "en.wikipedia.org",
snippet: "Coffee is a brewed drink...",
},
],
};
assertEquals(response.search_metadata.status, "Success");

const firstResult: OrganicResult = response.organic_results![0];
assertEquals(firstResult.position, 1);
assertEquals(firstResult.title, "Coffee - 维基pedia");
});
});

describe("backwards compatibility", () => {
it("generic EngineParameters still works", () => {
const params: EngineParameters = {
engine: "bing",
q: "anything",
custom_field: 123,
};
assertEquals(params.engine, "bing");
});
});
Loading