From 461eb4dc7caa06f101c295ce9546fcda0e14ab31 Mon Sep 17 00:00:00 2001 From: DJj123dj <80536295+DJj123dj@users.noreply.github.com> Date: Tue, 14 May 2024 18:34:13 +0200 Subject: [PATCH] v1.0.0 --- .github/FUNDING.yml | 1 + .github/SECURITY.md | 38 +++++++ .gitignore | 2 + README.md | 131 +++++++++++++++++++++- package.json | 40 +++++++ src/index.ts | 267 ++++++++++++++++++++++++++++++++++++++++++++ test/config.json | 4 + test/example.js | 34 ++++++ tools/cleanup.js | 2 + tsconfig.json | 14 +++ 10 files changed, 531 insertions(+), 2 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/SECURITY.md create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 test/config.json create mode 100644 test/example.js create mode 100644 tools/cleanup.js create mode 100644 tsconfig.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9997452 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: DJj123dj \ No newline at end of file diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..202e23a --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,38 @@ +# Security Policy + +## Supported Versions +Below, you can find a list with the status of every Discord Alt Detector version. This list will change every update! + +- ✅ Supported **(bugs, errors, discord support, documentation)** +- 🟧 Deprecated / Partially Supported **(discord support, documentation)** +- ❌ Fully Deprecated / Not Supported **(sometimes documentation)** + +| Version | Supported | Until | +|-----------|-----------|-----------------------------| +| 1.0.0 | ✅ | Next Version | + +## Reporting a Vulnerability + +You can report Vulnerabilities, Errors & Bugs using one of the methods below: +- Create an issue on github +- Create a ticket in our [discord server](https://discord.dj-dj.be) +- Report the bug in DM on discord (djj123dj) +- Email [support@dj-dj.be](mailto:support@dj-dj.be) + +While reporting your problem, please be clear about what the problem is. +We would like to know the issue in as much detail as possible. +If possible, try to provide screenshots or evidence!
+**Always specify the version of discord alt detector that you are working with!** + +## Contributing +1. (optional) First create an issue explaining what you are going to add/edit. +2. Fork the repo & make your changes. +3. Open a pull request & explain all changes (again). +4. Wait for approval + +
+SECURITY POLICY - Last updated: 14/5/2024
+© DJdj Development

+Website: https://www.dj-dj.be
+Discord: https://discord.dj-dj.be
+Email: support@dj-dj.be \ No newline at end of file diff --git a/.gitignore b/.gitignore index c6bba59..ac10388 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,5 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index 9df5e94..60f588f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,129 @@ -# discord-truster - A small library for discord.js to detect the trust level of a user. +Alt Detector + +[![discord](https://img.shields.io/badge/discord-join%20our%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-1.0.0-brightgreen.svg?style=flat-square)](https://github.com/DJj123dj/discord-alt-detector/releases/tag/v1.0.0) [![discord.js](https://img.shields.io/badge/discord.js-v14-CB3837.svg?style=flat-square&logo=npm)]() [![license](https://img.shields.io/badge/license-MIT-important.svg?style=flat-square)](https://github.com/DJj123dj/discord-alt-detector/blob/main/LICENSE) [![stars](https://img.shields.io/github/stars/djj123dj/discord-alt-detector?color=yellow&label=stars&logo=github&style=flat-square)](https://https://www.github.com/DJj123dj/discord-alt-detector) + +### Discord Alt Detector +Discord Alt Detector is a small [npm package](https://www.npmjs.com/package/discord-alt-detector) to catch alt accounts based on a first glimpse. It will check for badges, username, pfp, status & more just to detect alt & scam accounts! If you're having trouble setting the bot up, feel free to join our support server and we will help you further! + +**⚠️ The system isn't perfect, so be aware that there could be `false-positives` between the results! ⚠️** + +### [Install it using npm!](https://www.npmjs.com/package/discord-alt-detector) +``` +npm i discord-alt-detector +``` + +## 📌 Features +- 📊 75% detection success rate +- 📦 lightweight +- ✅ made with typescript +- ⚙️ advanced configuration using weights +- 📄 support for custom functions +- 🖥️ discord.js v14 +- ⭐️ [check more than just the age](#checked-properties) + +### Checked Properties +- account age +- pfp & banner +- has nitro / serverbooster +- profile badges +- username & displayname +- status & activity + +## 🛠️ Usage +### Dependencies +- `node.js` v18 or higher +- `discord.js` v14 or higher + +### Settings +In the settings, you can configure the **weight of each detector**. +- If you want a checker to stand out from the rest, you can increase the weight (e.g. `2`). +- If you don't want something to affect the score, you can set the value to `0`. +```js +const detector = new AltDetector({ + ageWeight:1, //account age + statusWeight:1, //user status (online, invisible, idle, dnd) + activityWeight:1, //user activity (playing/listening ...) + usernameWordsWeight:1, //suspicious words in username + usernameSymbolsWeight:1, //special characters in username + displaynameWordsWeight:1, //suspicious words in displayname + + displaynameCapsWeight:1, //caps characters in displayname + //the more, the better => scammers & alts usually don't have many caps + + displaynameSymbolsWeight:1, //special characters in displayname + flagsWeight:1, //profile badges (hypesquad, active dev, early supporter, ...) + boosterWeight:1, //is server booster + pfpWeight:1, //has non-default avatar + bannerWeight:1, //has nitro banner + customWeight:1 //weight for custom function +} +``` + +### Result +The result is an object with the `total` score & `categories` object. +In the `categories`, you can find the individual score per-category! + +**Weights are applied to all numbers in the result!** + +### Category +When using the `AltDetector.getCategory(result)` function, you get one of the following categories: + +|Category |Notes | +|-------------------------|-------------------------------| +|✅ `"highly-trusted"` |You can trust this person in all cases! (they could even apply for staff) | +|✅ `"trusted"` |You can trust this person very good! | +|✅ `"normal"` |A normal user, nothing to worry about! | +|🟠 `"newbie"` |A new user on discord, you might inspect him/her a little more! | +|🟠 `"suspicious"` |Be careful with this user, this might be an alt/spy account! | +|❌ `"highly-suspicious"` |Be really careful with this user, it's almost certainly an alt/scammer! | +|❌ `"mega-suspicious"` |This account meets all the requirements to be an alt/scam account! | + +### Example Code +```js +const discord = require("discord.js") +const { AltDetector } = require("discord-alt-detector") +const client = new discord.Client({ + //these intents are required for the bot to work! + intents:[ + discord.GatewayIntentBits.Guilds, + discord.GatewayIntentBits.GuildMembers, + discord.GatewayIntentBits.GuildMessages, + discord.GatewayIntentBits.MessageContent, + discord.GatewayIntentBits.GuildPresences + ] +}) + +const detector = new AltDetector({ + //settings + //change weights here +},(member,user) => { + //custom function (for extra score) + return 1 +}) + +client.on("guildMemberAdd",(member) => { + const result = altdetect.check(member) + const category = altdetect.getCategory(result) + console.log(member.user.displayName,result.total) //total score + console.log(category) //the level of trust based in categories (trusted,normal,suspicious,...) +}) +``` + +## 🩷 Sponsors +We don't have any sponsors yet! Would you like to sponsor? + +## 🛠️ Contributors +### Official Team +|Role |User (discord name)| +|-----------------|-------------------| +|Developer |djj123dj | + +### Community +We don't have any community contributors yet! + +## 📎 Links +current version: _v1.0.0_ +
changelog: [click here](https://www.github.com/DJj123dj/discord-alt-detector/releases) +
support: [click here](https://discord.dj-dj.be/) + +© 2024 - DJdj Development | [website](https://www.dj-dj.be) | [discord](https://discord.dj-dj.be) | [terms of service](https://www.dj-dj.be/terms) \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..78b5ba2 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "discord-alt-detector", + "author": "DJj123dj", + "version": "1.0.0", + "description": "A small library for discord.js to detect the suspiciousness of level of a user.", + + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + + "directories": { + "test": "test" + }, + "scripts": { + "build": "node ./tools/cleanup.js && tsc -p ./tsconfig.json" + }, + "keywords": [ + "discord", + "discord-moderation", + "moderation", + "discord.js", + "djs", + "verification", + "discord-verification", + "checker", + "anti-alt", + "account", + "discord-anti-alt", + "nodejs", + "node.js" + ], + + "license": "MIT", + "dependencies": { + "discord.js": "^14.15.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/DJj123dj/discord-alt-detector.git" + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2b211d7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,267 @@ +import * as discord from "discord.js" + +/**Settings for the Alt Detector */ +export interface AltDetectorSettings { + ageWeight:number, + statusWeight:number, + activityWeight:number, + usernameWordsWeight:number, + usernameSymbolsWeight:number, + displaynameWordsWeight:number, + displaynameCapsWeight:number, + displaynameSymbolsWeight:number, + flagsWeight:number, + boosterWeight:number, + pfpWeight:number, + bannerWeight:number, + customWeight:number +} + +/**The returned value of the check(member) function */ +export interface AltDetectorResult { + total:number, + categories:{ + age:number, + status:number, + activity:number, + usernameWords:number, + usernameSymbols:number, + displaynameWords:number, + displaynameCaps:number, + displaynameSymbols:number, + flags:number, + booster:number, + pfp:number, + banner:number, + custom:number + } +} + +/**The Category that this user would be part of. (ONLY APPLIES WHEN NO WEIGHTS USED!!!) */ +export type AltDetectorCategory = "highly-trusted"|"trusted"|"normal"|"newbie"|"suspicious"|"highly-suspicious"|"mega-suspicious" + +/**Discord Alt Detector. Use `AltDetector.check(member)` to check a server member! */ +export class AltDetector { + settings: AltDetectorSettings + constructor(settings?:Partial){ + this.settings = Object.assign({ + ageWeight:1, + statusWeight:1, + activityWeight:1, + usernameWordsWeight:1, + usernameSymbolsWeight:1, + displaynameWordsWeight:1, + displaynameCapsWeight:1, + displaynameSymbolsWeight:1, + flagsWeight:1, + boosterWeight:1, + pfpWeight:1, + bannerWeight:1, + customWeight:1 + },(settings ?? {})) + } + + check(member:discord.GuildMember, custom?:(member:discord.GuildMember,user:discord.User) => number): AltDetectorResult { + if (!(member instanceof discord.GuildMember)) throw new Error("member parameter isn't a valid GuildMember!") + const user = member.user + + //The higher the score, the better + //The lower the score, the more suspicious + const score: AltDetectorResult = { + total:0, + categories:{ + age:0, + status:0, + activity:0, + usernameWords:0, + usernameSymbols:0, + displaynameWords:0, + displaynameSymbols:0, + displaynameCaps:0, + flags:0, + booster:0, + pfp:0, + banner:0, + custom:0 + } + } + + //check for account age + // 3 days or less => -6 + // 1 week or less => -4 + // 1 month or less => -2 + // 1 year or less => 0 + // 2 years or less => +1 + // 3 years or less => +2 + // 3 years or more => +4 + + const ageMs = new Date().getTime() - user.createdAt.getTime() + const ageDays = Math.round((((ageMs/1000)/60)/60)/24) + if (ageDays <= 3) score.categories.age = score.categories.age - 6 + else if (ageDays <= 7) score.categories.age = score.categories.age - 4 + else if (ageDays <= 30) score.categories.age = score.categories.age - 2 + else if (ageDays <= 365) score.categories.age = score.categories.age + 0 + else if (ageDays <= 2*365) score.categories.age = score.categories.age + 1 + else if (ageDays <= 3*365) score.categories.age = score.categories.age + 2 + else if (ageDays > 3*365) score.categories.age = score.categories.age + 4 + + //check for status (presence intent required) + // offline => -1 + // online => +1 + // idle/dnd => +2 + + if (member.presence){ + if (member.presence.status == "offline") score.categories.status = score.categories.status - 1 + else if (member.presence.status == "invisible") score.categories.status = score.categories.status + 0 + else if (member.presence.status == "online") score.categories.status = score.categories.status + 1 + else if (member.presence.status == "idle") score.categories.status = score.categories.status + 2 + else if (member.presence.status == "dnd") score.categories.status = score.categories.status + 2 + } + + //check for activity + // no activity => -1 + // activity => +3 + + if (member.presence){ + if (member.presence.activities.length == 0) score.categories.activity = score.categories.activity - 1 + else score.categories.activity = score.categories.activity + 3 + } + + //check for username + // for every bad word => -2 [money, sell, scam, nitro, nitr0, alt, boost, discord, disc0rd, shop, hot, teen, nsfw, ...] + // when no bad words => +1 + // for every ending number => -1 + // for every ending dot/underscore => -2 + + const usernameWordRegex = /((n(?:i|l)tr(?:o|0)?)|mone?y|v?buc?ks?|acc?ounts?|sale|free|disc(?:o|0)r?d|boo?st|shop|hot|teens?|nsfw|alt|scam|sell)\b/gi + while (true){ + //test until all parts are tested + const result = usernameWordRegex.exec(user.username) + if (!result){ + score.categories.usernameWords = score.categories.usernameWords + 1 + break + }else score.categories.usernameWords = score.categories.usernameWords - 2 + } + const usernameEndRegex = /[0-9_\-\.]+$/i + const usernameEndResult = usernameEndRegex.exec(user.username) + if (usernameEndResult) score.categories.usernameSymbols = score.categories.usernameSymbols - usernameEndResult[0].length + + //check for display name + // no display name => -2 + // special symbols => -1 [!, ?, (), [], <3, _, -, ...] + // capital letter => +1 + // for every bad word => -2 [money, sell, scam, nitro, nitr0, alt, boost, discord, disc0rd, shop, hot, teen, nsfw, ...] + // when no bad words => +1 + + if (user.displayName == user.username) score.categories.displaynameWords = score.categories.displaynameWords - 2 + const displaynameCapsRegex = /[A-Z]/g + while (true){ + //test until all parts are tested + const result = displaynameCapsRegex.exec(user.displayName) + if (result){ + score.categories.displaynameCaps = score.categories.displaynameCaps + 1 + }else break + } + + const displaynameSpecialRegex = /[!?()\[\]{}"'&$*^%+=/:;,<>@#]/g + while (true){ + //test until all parts are tested + const result = displaynameSpecialRegex.exec(user.displayName) + if (result){ + score.categories.displaynameSymbols = score.categories.displaynameSymbols - 1 + }else break + } + + const displaynameWordRegex = /((n(?:i|l)tr(?:o|0)?)|mone?y|v?buc?ks?|acc?ounts?|sale|free|disc(?:o|0)r?d|boo?st|shop|hot|teens?|nsfw|alt|scam|sell)\b/gi + while (true){ + //test until all parts are tested + const result = displaynameWordRegex.exec(user.displayName) + if (!result){ + score.categories.displaynameWords = score.categories.displaynameWords + 1 + break + }else score.categories.displaynameWords = score.categories.displaynameWords - 2 + } + + //user flags + // staff/employe => +10 + // bughunter => +7 + // certified moderator => +6 + // verified developer => +6 + // partner => +6 + // early supporter => +6 + // active developer => +3 + // hypesquad => +2 + // quarantined => -7 + // spammer => -7 + + if (user.flags){ + if (user.flags.has("Staff")) score.categories.flags = score.categories.flags + 10 + if (user.flags.has("BugHunterLevel1")) score.categories.flags = score.categories.flags + 7 + if (user.flags.has("BugHunterLevel2")) score.categories.flags = score.categories.flags + 7 + if (user.flags.has("CertifiedModerator")) score.categories.flags = score.categories.flags + 6 + if (user.flags.has("VerifiedDeveloper")) score.categories.flags = score.categories.flags + 6 + if (user.flags.has("Partner")) score.categories.flags = score.categories.flags + 6 + if (user.flags.has("PremiumEarlySupporter")) score.categories.flags = score.categories.flags + 6 + if (user.flags.has("ActiveDeveloper")) score.categories.flags = score.categories.flags + 3 + if (user.flags.has("Hypesquad")) score.categories.flags = score.categories.flags + 2 + if (user.flags.has("Quarantined")) score.categories.flags = score.categories.flags - 7 + if (user.flags.has("Spammer")) score.categories.flags = score.categories.flags - 7 + } + + //other + // member.premiumSince => +2 [server booster when it exists] + + if (member.premiumSince) score.categories.booster = score.categories.booster + 2 + + //check pfp + // default => -3 [one of the colors] + // normal => +1 + // animated => +3 + + if (!user.avatar) score.categories.pfp = score.categories.pfp - 2 + else if (user.displayAvatarURL().endsWith(".gif")) score.categories.pfp = score.categories.pfp + 2 + else score.categories.pfp = score.categories.pfp + 1 + + //check pfp banner + // default => 0 + // image/gif => +2 + if (user.banner || user.bannerURL()) score.categories.banner = score.categories.banner + 1 + + if (custom) score.categories.custom = score.categories.custom + custom(member,user) + + //multiply categories by weight + const c = score.categories + c.age *= this.settings.ageWeight + c.status *= this.settings.statusWeight + c.activity *= this.settings.activityWeight + c.usernameWords *= this.settings.usernameWordsWeight + c.usernameSymbols *= this.settings.usernameSymbolsWeight + c.displaynameWords *= this.settings.displaynameWordsWeight + c.displaynameCaps *= this.settings.displaynameCapsWeight + c.displaynameSymbols *= this.settings.displaynameSymbolsWeight + c.flags *= this.settings.flagsWeight + c.booster *= this.settings.boosterWeight + c.pfp *= this.settings.pfpWeight + c.banner *= this.settings.bannerWeight + c.custom *= this.settings.customWeight + + //get total + score.total = c.age+c.status+c.activity+c.usernameWords+c.usernameSymbols+c.displaynameWords+c.displaynameSymbols+c.displaynameCaps+c.flags+c.booster+c.pfp+c.banner+c.custom + + return score + } + + /**Get the level of trust of a user. (ONLY APPLIES WHEN NO WEIGHTS USED!!!) */ + getCategory(score:AltDetectorResult){ + var category: AltDetectorCategory = "normal" + if (score.total > 10) category = "highly-trusted" + else if (score.total > 6) category = "trusted" + else if (score.total > 4) category = "normal" + else if (score.total > 1) category = "newbie" + else if (score.total > -1) category = "suspicious" + else if (score.total > -3) category = "highly-suspicious" + else if (score.total <= -3) category = "mega-suspicious" + + return category + } +} \ No newline at end of file diff --git a/test/config.json b/test/config.json new file mode 100644 index 0000000..ec2928a --- /dev/null +++ b/test/config.json @@ -0,0 +1,4 @@ +{ + "token":"test token", + "server":"test server" +} \ No newline at end of file diff --git a/test/example.js b/test/example.js new file mode 100644 index 0000000..0425ffb --- /dev/null +++ b/test/example.js @@ -0,0 +1,34 @@ +const altdetect = new (require("../dist/index")).AltDetector() +const discord = require("discord.js") +const config = require("./config.json") + +const gib = discord.GatewayIntentBits +const client = new discord.Client({ + //INTENTS ARE IMPORTANT!! + intents:[ + gib.Guilds, + gib.GuildMembers, + gib.GuildMessages, + gib.MessageContent, + gib.GuildPresences + ] +}) + +client.on("ready",async () => { + const server = client.guilds.cache.get(config.server) + if (!server) throw new Error("Server not found!") + + server.members.fetch().then((members) => { + members.forEach((member) => { + console.log(member.user.displayName,altdetect.check(member)) + }) + }) +}) + +client.on("guildMemberAdd",(member) => { + const result = altdetect.check(member) + const category = altdetect.getCategory(result) + console.log(member.user.displayName,result.total,category) +}) + +client.login(config.token) \ No newline at end of file diff --git a/tools/cleanup.js b/tools/cleanup.js new file mode 100644 index 0000000..e265b74 --- /dev/null +++ b/tools/cleanup.js @@ -0,0 +1,2 @@ +const fs = require('fs') +fs.rmSync("./dist",{recursive: true, force:true}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..110f11e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "strictNullChecks": true, + "strictPropertyInitialization": true, + "declaration": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist" + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file