87 lines
2.1 KiB
TypeScript
87 lines
2.1 KiB
TypeScript
import type { SearchResult } from "./types";
|
|
|
|
const NEGATIVE_WORDS = [
|
|
"不好用",
|
|
"雷",
|
|
"踩雷",
|
|
"失望",
|
|
"問題",
|
|
"壞掉",
|
|
"客服",
|
|
"退款",
|
|
"詐騙",
|
|
"不推",
|
|
"有人遇過嗎",
|
|
"怎麼辦",
|
|
];
|
|
|
|
const QUESTION_WORDS = [
|
|
"有人用過",
|
|
"好用嗎",
|
|
"推薦嗎",
|
|
"值得買嗎",
|
|
"怎麼選",
|
|
"求推薦",
|
|
"請問",
|
|
"疑問",
|
|
];
|
|
|
|
export interface NotifyRuleContext {
|
|
brandTerms?: string[];
|
|
productTerms?: string[];
|
|
accountTerms?: string[];
|
|
competitorTerms?: string[];
|
|
manualHighPriority?: boolean;
|
|
}
|
|
|
|
export interface NotifyMatch {
|
|
matched: boolean;
|
|
rules: string[];
|
|
}
|
|
|
|
function blob(result: SearchResult): string {
|
|
return `${result.title} ${result.snippet} ${result.url}`.toLowerCase();
|
|
}
|
|
|
|
export function evaluateNotifyRules(
|
|
result: SearchResult,
|
|
ctx: NotifyRuleContext
|
|
): NotifyMatch {
|
|
const text = blob(result);
|
|
const rules: string[] = [];
|
|
|
|
if (ctx.manualHighPriority) rules.push("high_priority_keyword");
|
|
|
|
for (const term of ctx.brandTerms ?? []) {
|
|
if (term && text.includes(term.toLowerCase())) rules.push("brand");
|
|
}
|
|
for (const term of ctx.productTerms ?? []) {
|
|
if (term && text.includes(term.toLowerCase())) rules.push("product");
|
|
}
|
|
for (const term of ctx.accountTerms ?? []) {
|
|
if (term && text.includes(term.toLowerCase().replace(/^@/, ""))) rules.push("account");
|
|
}
|
|
for (const term of ctx.competitorTerms ?? []) {
|
|
if (term && text.includes(term.toLowerCase())) rules.push("competitor");
|
|
}
|
|
for (const w of NEGATIVE_WORDS) {
|
|
if (text.includes(w)) rules.push("negative_word");
|
|
}
|
|
for (const w of QUESTION_WORDS) {
|
|
if (text.includes(w)) rules.push("question_word");
|
|
}
|
|
|
|
return { matched: rules.length > 0, rules: [...new Set(rules)] };
|
|
}
|
|
|
|
export function filterNotifiableResults(
|
|
results: SearchResult[],
|
|
ctx: NotifyRuleContext
|
|
): Array<SearchResult & { matchedRules: string[] }> {
|
|
return results
|
|
.map((r) => {
|
|
const verdict = evaluateNotifyRules(r, ctx);
|
|
return verdict.matched ? { ...r, matchedRules: verdict.rules } : null;
|
|
})
|
|
.filter((r): r is SearchResult & { matchedRules: string[] } => r !== null);
|
|
} |