mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 12:25:02 +00:00
feat: Phisherman integration
This commit is contained in:
parent
f92ee9ba4f
commit
13c94a81cc
18 changed files with 681 additions and 17 deletions
|
@ -30,6 +30,7 @@ import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
|
|||
import { pluginInfo } from "./info";
|
||||
import { availableTriggers } from "./triggers/availableTriggers";
|
||||
import { AutomodPluginType, ConfigSchema } from "./types";
|
||||
import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -183,6 +184,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
|
|||
ModActionsPlugin,
|
||||
MutesPlugin,
|
||||
CountersPlugin,
|
||||
PhishermanPlugin,
|
||||
],
|
||||
|
||||
configSchema: ConfigSchema,
|
||||
|
|
|
@ -9,10 +9,13 @@ import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions
|
|||
import { automodTrigger } from "../helpers";
|
||||
import { mergeRegexes } from "../../../utils/mergeRegexes";
|
||||
import { mergeWordsIntoRegex } from "../../../utils/mergeWordsIntoRegex";
|
||||
import { PhishermanPlugin } from "../../Phisherman/PhishermanPlugin";
|
||||
import { phishermanDomainIsSafe } from "../../../data/Phisherman";
|
||||
|
||||
interface MatchResultType {
|
||||
type: MatchableTextType;
|
||||
link: string;
|
||||
details?: string;
|
||||
}
|
||||
|
||||
const regexCache = new WeakMap<any, RegExp[]>();
|
||||
|
@ -28,6 +31,12 @@ export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
|||
exclude_words: tNullable(t.array(t.string)),
|
||||
include_regex: tNullable(t.array(TRegex)),
|
||||
exclude_regex: tNullable(t.array(TRegex)),
|
||||
phisherman: tNullable(
|
||||
t.type({
|
||||
include_suspected: tNullable(t.boolean),
|
||||
include_verified: tNullable(t.boolean),
|
||||
}),
|
||||
),
|
||||
only_real_links: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
|
@ -150,6 +159,25 @@ export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.phisherman) {
|
||||
const phishermanResult = await pluginData.getPlugin(PhishermanPlugin).getDomainInfo(normalizedHostname);
|
||||
if (phishermanResult != null && !phishermanDomainIsSafe(phishermanResult)) {
|
||||
if (
|
||||
(trigger.phisherman.include_suspected && !phishermanResult.verifiedPhish) ||
|
||||
(trigger.phisherman.include_verified && phishermanResult.verifiedPhish)
|
||||
) {
|
||||
const suspectedVerified = phishermanResult.verifiedPhish ? "verified" : "suspected";
|
||||
return {
|
||||
extra: {
|
||||
type,
|
||||
link: link.input,
|
||||
details: `using Phisherman (${suspectedVerified})`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,6 +186,11 @@ export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
|||
|
||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
||||
return `Matched link \`${Util.escapeInlineCode(matchResult.extra.link)}\` in ${partialSummary}`;
|
||||
let information = `Matched link \`${Util.escapeInlineCode(matchResult.extra.link)}\``;
|
||||
if (matchResult.extra.details) {
|
||||
information += ` ${matchResult.extra.details}`;
|
||||
}
|
||||
information += ` in ${partialSummary}`;
|
||||
return information;
|
||||
},
|
||||
});
|
||||
|
|
49
backend/src/plugins/Phisherman/PhishermanPlugin.ts
Normal file
49
backend/src/plugins/Phisherman/PhishermanPlugin.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { PluginOptions, typedGuildCommand } from "knub";
|
||||
import { GuildPingableRoles } from "../../data/GuildPingableRoles";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { ConfigSchema, PhishermanPluginType } from "./types";
|
||||
import {
|
||||
getPhishermanDomainInfo,
|
||||
hasPhishermanMasterAPIKey,
|
||||
phishermanApiKeyIsValid,
|
||||
reportTrackedDomainsToPhisherman,
|
||||
} from "../../data/Phisherman";
|
||||
import { mapToPublicFn } from "../../pluginUtils";
|
||||
import { getDomainInfo } from "./functions/getDomainInfo";
|
||||
import { pluginInfo } from "./info";
|
||||
|
||||
const defaultOptions: PluginOptions<PhishermanPluginType> = {
|
||||
config: {
|
||||
api_key: null,
|
||||
},
|
||||
overrides: [],
|
||||
};
|
||||
|
||||
export const PhishermanPlugin = zeppelinGuildPlugin<PhishermanPluginType>()({
|
||||
name: "phisherman",
|
||||
showInDocs: true,
|
||||
info: pluginInfo,
|
||||
|
||||
configSchema: ConfigSchema,
|
||||
defaultOptions,
|
||||
|
||||
// prettier-ignore
|
||||
public: {
|
||||
getDomainInfo: mapToPublicFn(getDomainInfo),
|
||||
},
|
||||
|
||||
async beforeLoad(pluginData) {
|
||||
pluginData.state.validApiKey = null;
|
||||
|
||||
if (!hasPhishermanMasterAPIKey()) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn("Could not load Phisherman plugin: master API key is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = pluginData.config.get().api_key;
|
||||
if (apiKey && (await phishermanApiKeyIsValid(apiKey).catch(() => false))) {
|
||||
pluginData.state.validApiKey = apiKey;
|
||||
}
|
||||
},
|
||||
});
|
20
backend/src/plugins/Phisherman/functions/getDomainInfo.ts
Normal file
20
backend/src/plugins/Phisherman/functions/getDomainInfo.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { PhishermanPluginType } from "../types";
|
||||
import { PhishermanDomainInfo } from "../../../data/types/phisherman";
|
||||
import { getPhishermanDomainInfo, phishermanDomainIsSafe, trackPhishermanCaughtDomain } from "../../../data/Phisherman";
|
||||
|
||||
export async function getDomainInfo(
|
||||
pluginData: GuildPluginData<PhishermanPluginType>,
|
||||
domain: string,
|
||||
): Promise<PhishermanDomainInfo | null> {
|
||||
if (!pluginData.state.validApiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const info = await getPhishermanDomainInfo(domain).catch(() => null);
|
||||
if (info != null && !phishermanDomainIsSafe(info)) {
|
||||
trackPhishermanCaughtDomain(pluginData.state.validApiKey, domain);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
41
backend/src/plugins/Phisherman/info.ts
Normal file
41
backend/src/plugins/Phisherman/info.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { trimPluginDescription } from "../../utils";
|
||||
import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint";
|
||||
|
||||
export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
|
||||
prettyName: "Phisherman",
|
||||
description: trimPluginDescription(`
|
||||
Match scam/phishing links using the Phisherman API. See https://phisherman.gg/ for more details!
|
||||
`),
|
||||
configurationGuide: trimPluginDescription(`
|
||||
### Getting started
|
||||
To get started, request an API key for Phisherman following the instructions at https://docs.phisherman.gg/#/api/getting-started?id=requesting-api-access.
|
||||
Then, add the api key to the plugin's config:
|
||||
|
||||
~~~yml
|
||||
phisherman:
|
||||
config:
|
||||
api_key: "your key here"
|
||||
~~~
|
||||
|
||||
### Note
|
||||
When using Phisherman features in Zeppelin, Zeppelin reports statistics about checked links back to Phisherman. This only includes the domain (e.g. zeppelin.gg), not the full link.
|
||||
|
||||
### Usage with Automod
|
||||
Once you have configured the Phisherman plugin, you are ready to use it with automod. Currently, Phisherman is available as an option in the \`match_links\` plugin:
|
||||
|
||||
~~~yml
|
||||
automod:
|
||||
config:
|
||||
rules:
|
||||
# Clean any scam links detected by Phisherman
|
||||
filter_scam_links:
|
||||
triggers:
|
||||
- match_links:
|
||||
phisherman:
|
||||
include_suspected: true # It's recommended to keep this enabled to catch new scam domains quickly
|
||||
include_verified: true
|
||||
actions:
|
||||
clean: true
|
||||
~~~
|
||||
`),
|
||||
};
|
16
backend/src/plugins/Phisherman/types.ts
Normal file
16
backend/src/plugins/Phisherman/types.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import { tNullable } from "../../utils";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
api_key: tNullable(t.string),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface PhishermanPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
|
||||
state: {
|
||||
validApiKey: string | null;
|
||||
};
|
||||
}
|
|
@ -34,6 +34,7 @@ import { UsernameSaverPlugin } from "./UsernameSaver/UsernameSaverPlugin";
|
|||
import { UtilityPlugin } from "./Utility/UtilityPlugin";
|
||||
import { WelcomeMessagePlugin } from "./WelcomeMessage/WelcomeMessagePlugin";
|
||||
import { ZeppelinGlobalPluginBlueprint, ZeppelinGuildPluginBlueprint } from "./ZeppelinPluginBlueprint";
|
||||
import { PhishermanPlugin } from "./Phisherman/PhishermanPlugin";
|
||||
|
||||
// prettier-ignore
|
||||
export const guildPlugins: Array<ZeppelinGuildPluginBlueprint<any>> = [
|
||||
|
@ -69,6 +70,7 @@ export const guildPlugins: Array<ZeppelinGuildPluginBlueprint<any>> = [
|
|||
TimeAndDatePlugin,
|
||||
CountersPlugin,
|
||||
ContextMenuPlugin,
|
||||
PhishermanPlugin,
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue