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.
+
+
+[![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