Compare commits

..

10 Commits

Author SHA1 Message Date
Elysia
bf38318902 sob
Some checks failed
Lint / ESLint (push) Has been cancelled
2025-10-11 17:43:36 +07:00
Elysia
f5a0515731 chore: update UA & ws prop 2025-10-11 17:28:41 +07:00
Elysia
b519264a69 feat: more invite flags 2025-10-11 17:18:50 +07:00
Elysia
d9d11bd6b7 fix: Ensure discriminator detection respects webhooks too
#11062 djs
2025-10-11 17:13:27 +07:00
Elysia
4d4fa1b17d fix(GuildChannel): account for everyone base permissions
backport #11053
2025-09-13 18:13:14 +07:00
Elysia
2fe0ad486d fix: Remove trailing color references
#11007
2025-09-13 18:11:53 +07:00
Elysia
f0ec7d977c types(Invite): Approximate fields should be nullable
#10997
2025-09-13 18:05:19 +07:00
Elysia
6fbb62ac64 feat: support user guilds
backport #10995
2025-09-13 18:04:34 +07:00
Elysia
ad64e2be11 feat(MessageManager): New pinned messages routes
backport #10993 ?
Note: This commit does not fully implement the features of the new endpoint; it only updates the endpoint and automatically processes data to be compatible with the old endpoint.
2025-09-13 17:55:52 +07:00
Elysia
0d45d3ddb9 feat(User): add collectibles
#10939
2025-09-13 17:44:51 +07:00
17 changed files with 246 additions and 70 deletions

View File

@@ -1,3 +1,10 @@
> [!IMPORTANT]
> ## Project Archival
>
> **This project is no longer actively maintained and this repository has been archived.**
>
> You can read the full announcement [here](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/discussions/1743)
<div align="center">
<br />
<p>
@@ -30,11 +37,6 @@
> [!CAUTION]
> **Using this on a user account is prohibited by the [Discord TOS](https://discord.com/terms) and can lead to the account block.**
## Project Status
> [!IMPORTANT]
> `discord.js-selfbot-v13` is currently in maintenance mode. New features are not actively being added but existing features and new versions of discord are supported as possible. There are some major architectural changes which need to be added to improve the stability and security of the project. I don't have as much spare time as I did when I started this project, so there is not currently any plan for these improvements.
### <strong>[Document Website](https://discordjs-self-v13.netlify.app/)</strong>
### <strong>[Example Code](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/tree/main/examples)</strong>

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
'use strict';
// No longer using 2captcha since the website no longer supports hCaptcha, which Discord uses.
const Captcha = require('2captcha');
const Discord = require('../src/index');

View File

@@ -1,5 +1,6 @@
'use strict';
// No longer using 2captcha since the website no longer supports hCaptcha, which Discord uses.
const Captcha = require('2captcha');
const Discord = require('../src/index');

View File

@@ -1,6 +1,6 @@
{
"name": "discord.js-selfbot-v13",
"version": "3.7.0",
"version": "3.7.1",
"description": "An unofficial discord.js fork for creating selfbots",
"main": "./src/index.js",
"types": "./typings/index.d.ts",

View File

@@ -82,9 +82,11 @@ class MessageManager extends CachedManager {
* .catch(console.error);
*/
async fetchPinned(cache = true) {
const data = await this.client.api.channels[this.channel.id].pins.get();
const data = await this.client.api.channels[this.channel.id].messages.pins.get({
query: { limit: 50 },
});
const messages = new Collection();
for (const message of data) messages.set(message.id, this._add(message, cache));
for (const message of data?.items || []) messages.set(message.id, this._add(message, cache));
return messages;
}
@@ -182,7 +184,7 @@ class MessageManager extends CachedManager {
message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).pins(message).put({ reason });
await this.client.api.channels(this.channel.id).messages.pins(message).put({ reason });
}
/**
@@ -195,7 +197,7 @@ class MessageManager extends CachedManager {
message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).pins(message).delete({ reason });
await this.client.api.channels(this.channel.id).messages.pins(message).delete({ reason });
}
/**

View File

@@ -59,7 +59,7 @@ class RoleManager extends CachedManager {
* @example
* // Fetch a single role
* message.guild.roles.fetch('222078108977594368')
* .then(role => console.log(`The role color is: ${role.color}`))
* .then(role => console.log(`The role color is: ${role.colors.primaryColor}`))
* .catch(console.error);
*/
async fetch(id, { cache = true, force = false } = {}) {

View File

@@ -244,10 +244,11 @@ class GuildChannel extends Channel {
return new Permissions(Permissions.ALL).freeze();
}
const basePermissions = new Permissions([role.permissions, role.guild.roles.everyone.permissions]);
const everyoneOverwrites = this.permissionOverwrites.cache.get(this.guild.id);
const roleOverwrites = this.permissionOverwrites.cache.get(role.id);
return role.permissions
return basePermissions
.remove(everyoneOverwrites?.deny ?? Permissions.defaultBit)
.add(everyoneOverwrites?.allow ?? Permissions.defaultBit)
.remove(roleOverwrites?.deny ?? Permissions.defaultBit)

View File

@@ -299,7 +299,7 @@ class GuildMember extends Base {
* @readonly
*/
get displayColor() {
return this.roles.color?.color ?? 0;
return this.roles.color?.colors.primaryColor ?? 0;
}
/**

View File

@@ -234,7 +234,7 @@ class Role extends Base {
* @readonly
*/
get hexColor() {
return `#${this.color.toString(16).padStart(6, '0')}`;
return `#${this.colors.primaryColor.toString(16).padStart(6, '0')}`;
}
/**

View File

@@ -143,41 +143,82 @@ class User extends Base {
*/
if (data.avatar_decoration_data) {
/**
* The user avatar decoration's data
* @type {?AvatarDecorationData}
*/
this.avatarDecorationData = {
asset: data.avatar_decoration_data.asset,
skuId: data.avatar_decoration_data.sku_id,
};
if (data.avatar_decoration_data) {
/**
* The user avatar decoration's data
* @type {?AvatarDecorationData}
*/
this.avatarDecorationData = {
asset: data.avatar_decoration_data.asset,
skuId: data.avatar_decoration_data.sku_id,
};
} else {
this.avatarDecorationData = null;
}
} else {
this.avatarDecorationData = null;
this.avatarDecorationData ??= null;
}
if ('primary_guild' in data && data.primary_guild) {
/**
* Primary Guild Structure
* @see {@link https://docs.discord.food/resources/user#primary-guild-structure}
* @typedef {Object} PrimaryGuild
* @property {?Snowflake} identityGuildId The ID of the user's primary clan
* @property {?boolean} identityEnabled Whether the user is displaying their guild tag
* @property {?string} tag The user's guild tag (max 4 characters)
* @property {?string} badge The guild tag badge hash
*/
/**
* The primary clan the user is in
* @type {?PrimaryGuild}
*/
this.primaryGuild = {
identityGuildId: data.primary_guild.identity_guild_id,
identityEnabled: data.primary_guild.identity_enabled,
tag: data.primary_guild.tag,
badge: data.primary_guild.badge,
};
/**
* @typedef {Object} UserPrimaryGuild
* @property {?Snowflake} identityGuildId The id of the user's primary guild
* @property {?boolean} identityEnabled Whether the user is displaying the primary guild's tag
* @property {?string} tag The user's guild tag. Limited to 4 characters
* @property {?string} badge The guild tag badge hash
*/
if ('primary_guild' in data) {
if (data.primary_guild) {
/**
* The primary guild of the user
* @type {?UserPrimaryGuild}
*/
this.primaryGuild = {
identityGuildId: data.primary_guild.identity_guild_id,
identityEnabled: data.primary_guild.identity_enabled,
tag: data.primary_guild.tag,
badge: data.primary_guild.badge,
};
} else {
this.primaryGuild = null;
}
} else {
this.primaryGuild ??= null;
}
/**
* @typedef {Object} NameplateData
* @property {Snowflake} skuId The id of the nameplate's SKU
* @property {string} asset The nameplate's asset path
* @property {string} label The nameplate's label
* @property {NameplatePalette} palette Background color of the nameplate
*/
/**
* @typedef {Object} Collectibles
* @property {?NameplateData} nameplate The user's nameplate data
*/
if (data.collectibles) {
if (data.collectibles.nameplate) {
/**
* The user's collectibles
* @type {?Collectibles}
*/
this.collectibles = {
nameplate: {
skuId: data.collectibles.nameplate.sku_id,
asset: data.collectibles.nameplate.asset,
label: data.collectibles.nameplate.label,
palette: data.collectibles.nameplate.palette,
},
};
} else {
this.collectibles = { nameplate: null };
}
} else {
this.collectibles ??= null;
}
}
/**
@@ -246,12 +287,21 @@ class User extends Base {
}
/**
* A link to the user's clan badge.
* A link to the user's guild tag badge.
* @returns {?string}
* @deprecated
*/
clanBadgeURL() {
if (!this.clan || !this.clan.identityGuildId || !this.clan.badge) return null;
return this.client.rest.cdn.ClanBadge(this.clan.identityGuildId, this.clan.badge);
return this.guildTagBadgeURL();
}
/**
* A link to the user's guild tag badge.
* @returns {?string}
*/
guildTagBadgeURL() {
if (!this.primaryGuild || !this.primaryGuild.identityGuildId || !this.primaryGuild.badge) return null;
return this.client.rest.cdn.GuildTagBadge(this.primaryGuild.identityGuildId, this.primaryGuild.badge);
}
/**
@@ -260,7 +310,10 @@ class User extends Base {
* @readonly
*/
get defaultAvatarURL() {
const index = this.discriminator === '0' ? Util.calculateUserDefaultAvatarIndex(this.id) : this.discriminator % 5;
const index =
this.discriminator === '0' || this.discriminator === '0000'
? Util.calculateUserDefaultAvatarIndex(this.id)
: this.discriminator % 5;
return this.client.rest.cdn.DefaultAvatar(index);
}
@@ -307,7 +360,7 @@ class User extends Base {
*/
get tag() {
return typeof this.username === 'string'
? this.discriminator === '0'
? this.discriminator === '0' || this.discriminator === '0000'
? this.username
: `${this.username}#${this.discriminator}`
: null;
@@ -367,7 +420,15 @@ class User extends Base {
this.banner === user.banner &&
this.accentColor === user.accentColor &&
this.avatarDecorationData?.asset === user.avatarDecorationData?.asset &&
this.avatarDecorationData?.skuId === user.avatarDecorationData?.skuId
this.avatarDecorationData?.skuId === user.avatarDecorationData?.skuId &&
this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.skuId &&
this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset &&
this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label &&
this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette &&
this.primaryGuild?.identityGuildId === user.primaryGuild?.identityGuildId &&
this.primaryGuild?.identityEnabled === user.primaryGuild?.identityEnabled &&
this.primaryGuild?.tag === user.primaryGuild?.tag &&
this.primaryGuild?.badge === user.primaryGuild?.badge
);
}
@@ -391,6 +452,18 @@ class User extends Base {
('avatar_decoration_data' in user
? this.avatarDecorationData?.asset === user.avatar_decoration_data?.asset &&
this.avatarDecorationData?.skuId === user.avatar_decoration_data?.sku_id
: true) &&
('collectibles' in user
? this.collectibles?.nameplate?.skuId === user.collectibles?.nameplate?.sku_id &&
this.collectibles?.nameplate?.asset === user.collectibles?.nameplate?.asset &&
this.collectibles?.nameplate?.label === user.collectibles?.nameplate?.label &&
this.collectibles?.nameplate?.palette === user.collectibles?.nameplate?.palette
: true) &&
('primary_guild' in user
? this.primaryGuild?.identityGuildId === user.primary_guild?.identity_guild_id &&
this.primaryGuild?.identityEnabled === user.primary_guild?.identity_enabled &&
this.primaryGuild?.tag === user.primary_guild?.tag &&
this.primaryGuild?.badge === user.primary_guild?.badge
: true)
);
}
@@ -451,6 +524,7 @@ class User extends Base {
json.avatarURL = this.avatarURL();
json.displayAvatarURL = this.displayAvatarURL();
json.bannerURL = this.banner ? this.bannerURL() : this.banner;
json.guildTagBadgeURL = this.guildTagBadgeURL();
return json;
}

View File

@@ -28,6 +28,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageActivityType}
*/
/**
* @external NameplatePalette
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/NameplatePalette}
*/
/**
* @external PollLayoutType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/PollLayoutType}

View File

@@ -9,7 +9,7 @@ const { Error, RangeError, TypeError } = require('../errors');
exports.MaxBulkDeletableMessageAge = 1_209_600_000;
exports.UserAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9198 Chrome/134.0.6998.205 Electron/35.3.0 Safari/537.36';
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9210 Chrome/134.0.6998.205 Electron/35.3.0 Safari/537.36';
/**
* Chrome TLS ciphers
@@ -97,7 +97,7 @@ exports.Endpoints = {
return makeImageUrl(`${root}/avatars/${userId}/${hash}`, { format, size });
},
AvatarDecoration: hash => makeImageUrl(`${root}/avatar-decoration-presets/${hash}`, { format: 'png' }),
ClanBadge: (guildId, hash) => `${root}/clan-badges/${guildId}/${hash}.png`,
GuildTagBadge: (guildId, hash) => `${root}/guild-tag-badges/${guildId}/${hash}.png`,
GuildMemberAvatar: (guildId, memberId, hash, format = 'webp', size, dynamic = false) => {
if (dynamic && hash.startsWith('a_')) format = 'gif';
return makeImageUrl(`${root}/guilds/${guildId}/users/${memberId}/avatars/${hash}`, { format, size });

View File

@@ -17,13 +17,18 @@ class InviteFlags extends BitField {}
/**
* Numeric the Discord invite flags. All available properties:
* * `GUEST`
* * `VIEWED`
* * `IS_GUEST_INVITE`
* * `IS_VIEWED`
* * `IS_ENHANCED`
* * `IS_APPLICATION_BYPASS`
* @type {Object}
* @see {@link https://docs.discord.food/resources/invite#invite-flags}
*/
InviteFlags.FLAGS = {
GUEST: 1 << 0,
VIEWED: 1 << 1,
IS_GUEST_INVITE: 1 << 0,
IS_VIEWED: 1 << 1,
IS_ENHANCED: 1 << 2,
IS_APPLICATION_BYPASS: 1 << 3,
};
module.exports = InviteFlags;

View File

@@ -196,7 +196,7 @@ class Options extends null {
os: 'Windows',
browser: 'Discord Client',
release_channel: 'stable',
client_version: '1.0.9198',
client_version: '1.0.9210',
os_version: '10.0.19044',
os_arch: 'x64',
app_arch: 'x64',
@@ -206,11 +206,12 @@ class Options extends null {
browser_user_agent: UserAgent,
browser_version: '35.3.0',
os_sdk_version: '19044',
client_build_number: 416613,
native_build_number: 65625,
client_build_number: 455964,
native_build_number: 69976,
client_event_source: null,
client_app_state: 'focused',
launch_signature: randomUUID(),
client_heartbeat_session_id: randomUUID(),
client_app_state: 'focused',
},
compress: false,
client_state: {

69
typings/enums.d.ts vendored
View File

@@ -266,6 +266,75 @@ export const enum NSFWLevels {
AGE_RESTRICTED = 3,
}
/**
* Background color of a nameplate.
* @see {@link https://docs.discord.food/resources/user#nameplate-color-palette}
*/
export enum NameplatePalette {
/**
* Value: none
* Name: None
*/
None = 'none',
/**
* Value: crimson
* Name: Crimson
*/
Crimson = 'crimson',
/**
* Value: berry
* Name: Berry
*/
Berry = 'berry',
/**
* Value: sky
* Name: Sky
*/
Sky = 'sky',
/**
* Value: teal
* Name: Teal
*/
Teal = 'teal',
/**
* Value: forest
* Name: Forest
*/
Forest = 'forest',
/**
* Value: bubble_gum
* Name: BubbleGum
*/
BubbleGum = 'bubble_gum',
/**
* Value: violet
* Name: Violet
*/
Violet = 'violet',
/**
* Value: cobalt
* Name: Cobalt
*/
Cobalt = 'cobalt',
/**
* Value: clover
* Name: Clover
*/
Clover = 'clover',
/**
* Value: lemon
* Name: Lemon
*/
Lemon = 'lemon',
/**
* Value: white
* Name: White
*/
White = 'white',
}
export const enum OverwriteTypes {
role = 0,
member = 1,

35
typings/index.d.ts vendored
View File

@@ -121,6 +121,7 @@ import {
MessageReferenceTypes,
SeparatorSpacingSizes,
ApplicationType,
NameplatePalette,
} from './enums';
import {
APIApplicationRoleConnectionMetadata,
@@ -2133,8 +2134,8 @@ export class Invite extends Base {
public inviterId: Snowflake | null;
public maxAge: number | null;
public maxUses: number | null;
public memberCount: number;
public presenceCount: number;
public memberCount: number | null;
public presenceCount: number | null;
public type: InviteTypes | null;
public targetApplication: IntegrationApplication | null;
public targetUser: User | null;
@@ -3715,11 +3716,11 @@ export class Typing extends Base {
};
}
export interface PrimaryGuild {
identityGuildId?: Snowflake;
identityEnabled?: boolean;
tag?: string;
badge?: string;
export interface UserPrimaryGuild {
badge: string | null;
identityEnabled: boolean | null;
identityGuildId: Snowflake | null;
tag: string | null;
}
export interface AvatarDecorationData {
@@ -3727,6 +3728,17 @@ export interface AvatarDecorationData {
skuId: Snowflake;
}
export interface NameplateData {
asset: string;
label: string;
palette: NameplatePalette;
skuId: Snowflake;
}
export interface Collectibles {
nameplate: NameplateData | null;
}
export class User extends PartialTextBasedChannel(Base) {
protected constructor(client: Client, data: RawUserData);
private _equals(user: APIUser): boolean;
@@ -3741,6 +3753,7 @@ export class User extends PartialTextBasedChannel(Base) {
public bot: boolean;
public readonly createdAt: Date;
public readonly createdTimestamp: number;
public collectibles: Collectibles | null;
public discriminator: string;
public readonly displayName: string;
public readonly defaultAvatarURL: string;
@@ -3757,13 +3770,15 @@ export class User extends PartialTextBasedChannel(Base) {
public readonly voice?: VoiceState;
public readonly relationship: RelationshipTypes;
public readonly friendNickname: string | null | undefined;
public primaryGuild: PrimaryGuild | null;
public primaryGuild: UserPrimaryGuild | null;
/** @deprecated Use {@link User.primaryGuild} instead */
public clan: PrimaryGuild | null;
public avatarURL(options?: ImageURLOptions): string | null;
public avatarDecorationURL(): string | null;
public bannerURL(options?: ImageURLOptions): string | null;
/** @deprecated Use {@link User.guildTagBadgeURL} instead */
public clanBadgeURL(): string | null;
public guildTagBadgeURL(options?: ImageURLOptions): string | null;
public createDM(force?: boolean): Promise<DMChannel>;
public deleteDM(): Promise<DMChannel>;
public displayAvatarURL(options?: ImageURLOptions): string;
@@ -4165,7 +4180,7 @@ export const Constants: {
dynamic: boolean,
): string;
AvatarDecoration(hash: string): string;
ClanBadge(guildId: Snowflake, hash: string): string;
GuildTagBadge(guildId: Snowflake, hash: string): string;
Banner(id: Snowflake, hash: string, format: DynamicImageFormat, size: AllowedImageSize, dynamic: boolean): string;
DefaultAvatar(index: number): string;
DiscoverySplash(guildId: Snowflake, hash: string, format: AllowedImageFormat, size: AllowedImageSize): string;
@@ -5060,7 +5075,7 @@ export type PurchasedFlagsString = 'NITRO_CLASSIC' | 'NITRO' | 'GUILD_BOOST' | '
export type PremiumUsageFlagsString = 'PREMIUM_DISCRIMINATOR' | 'ANIMATED_AVATAR' | 'PROFILE_BANNER';
export type InviteFlagsString = 'GUEST' | 'VIEWED';
export type InviteFlagsString = 'IS_GUEST_INVITE' | 'IS_VIEWED' | 'IS_ENHANCED' | 'IS_APPLICATION_BYPASS';
export type ActivitiesOptions = Omit<ActivityOptions, 'shardId'>;