djs-selfbot-v13 3.7.34 → 3.7.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
1
  GNU GENERAL PUBLIC LICENSE
2
2
  Version 3, 29 June 2007
3
3
 
4
- Copyright (C) 2022 aiko-chan-ai and discordjs
4
+ Copyright (C) 2025 002-sans aiko-chan-ai and discordjs
5
5
  Everyone is permitted to copy and distribute verbatim copies
6
6
  of this license document, but changing it is not allowed.
7
7
 
@@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
632
632
  the "copyright" line and a pointer to where the full notice is found.
633
633
 
634
634
  <one line to give the program's name and a brief idea of what it does.>
635
- Copyright (C) <year> <name of author>
635
+ Copyright (C) 2025 002-sans aiko-chan-ai and discordjs
636
636
 
637
637
  This program is free software: you can redistribute it and/or modify
638
638
  it under the terms of the GNU General Public License as published by
@@ -647,12 +647,9 @@ the "copyright" line and a pointer to where the full notice is found.
647
647
  You should have received a copy of the GNU General Public License
648
648
  along with this program. If not, see <https://www.gnu.org/licenses/>.
649
649
 
650
- Also add information on how to contact you by electronic and paper mail.
650
+ notice like this when it starts in an interactive mode:
651
651
 
652
- If the program does terminal interaction, make it output a short
653
- notice like this when it starts in an interactive mode:
654
-
655
- <program> Copyright (C) <year> <name of author>
652
+ djs-selfbot-v13 Copyright (C) 2025 002-sans aiko-chan-ai and discordjs
656
653
  This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
654
  This is free software, and you are welcome to redistribute it
658
655
  under certain conditions; type `show c' for details.
package/README.md CHANGED
@@ -108,11 +108,6 @@ Github Discussion: [Here](https://github.com/002-sans/djs-selfbot-v13/discussion
108
108
  ## Credits
109
109
  - [Discord.js](https://github.com/discordjs/discord.js)
110
110
 
111
- ## <strong>Other project(s)
112
-
113
- - 📘 [***aiko-chan-ai/DiscordBotClient***](https://github.com/aiko-chan-ai/DiscordBotClient) <br/>
114
- A patched version of discord, with bot login support
115
-
116
111
  ## Star History
117
112
 
118
113
  [![Star History Chart](https://api.star-history.com/svg?repos=002-sans/djs-selfbot-v13&type=Date)](https://star-history.com/#002-sans/djs-selfbot-v13&Date)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "djs-selfbot-v13",
3
- "version": "3.7.34",
3
+ "version": "3.7.36",
4
4
  "description": "An unofficial discord.js fork for creating selfbots",
5
5
  "main": "./src/index.js",
6
6
  "types": "./typings/index.d.ts",
@@ -15,8 +15,8 @@
15
15
  "lint:typings:fix": "tslint typings/index.d.ts --fix",
16
16
  "format": "prettier --write src/**/*.js typings/**/*.ts",
17
17
  "lint:all": "npm run lint && npm run lint:typings",
18
- "docs": "docgen --source src --custom docs/index.yml --output docs/main.json",
19
- "docs:test": "docgen --source src --custom docs/index.yml",
18
+ "docs": "node scripts/generate-docs.js --source src --custom docs/index.yml --output docs/main.json",
19
+ "docs:test": "node scripts/generate-docs.js --source src --custom docs/index.yml",
20
20
  "build": "npm run lint:fix && npm run lint:typings:fix && npm run format && npm run docs"
21
21
  },
22
22
  "files": [
@@ -53,14 +53,44 @@ class WebRtcStreamSession extends EventEmitter {
53
53
  this._abort = new AbortController();
54
54
  this._playPromise = null;
55
55
  this._command = null;
56
+ this._ffmpegProc = null;
56
57
  this._started = false;
58
+ this._running = false;
59
+ this._paused = false;
60
+ this._stopped = false;
61
+ this._positionMs = 0;
62
+ this._runStartWall = 0;
63
+ this._pausedAt = 0;
64
+ this._pausedTotalMs = 0;
65
+ }
66
+
67
+ _getPositionMs() {
68
+ if (!this._running) return this._positionMs;
69
+ if (this._paused) return this._positionMs;
70
+ return this._positionMs + (Date.now() - this._runStartWall - this._pausedTotalMs);
71
+ }
72
+
73
+ _resetRunClock() {
74
+ this._runStartWall = Date.now();
75
+ this._pausedAt = 0;
76
+ this._pausedTotalMs = 0;
77
+ }
78
+
79
+ _signalFfmpeg(signal) {
80
+ const pid = this._ffmpegProc?.pid;
81
+ if (!pid) return;
82
+ try {
83
+ process.kill(pid, signal);
84
+ } catch {
85
+ // process already exited
86
+ }
57
87
  }
58
88
 
59
89
  /**
60
90
  * Construit la commande ffmpeg : transcode en H264 + Opus, sortie nut.
61
91
  * Pas de filtre azmq (compatibilité ffmpeg sans libzmq).
62
92
  */
63
- _buildFfmpeg(url) {
93
+ _buildFfmpeg(url, seekSec = 0) {
64
94
  const {
65
95
  fps,
66
96
  height = 720,
@@ -78,6 +108,8 @@ class WebRtcStreamSession extends EventEmitter {
78
108
  const output = new PassThrough();
79
109
  const command = ffmpeg(url);
80
110
 
111
+ if (seekSec > 0) command.seekInput(seekSec);
112
+
81
113
  const isHttp = typeof url === 'string' && /^https?:\/\//i.test(url);
82
114
  if (isHttp) {
83
115
  command.inputOptions([
@@ -123,13 +155,21 @@ class WebRtcStreamSession extends EventEmitter {
123
155
  return { command, output };
124
156
  }
125
157
 
126
- async _run(url, signal) {
158
+ async _run(url, signal, seekSec = 0) {
127
159
  await ensureFfmpegPath();
128
160
 
129
- const { command, output } = this._buildFfmpeg(url);
161
+ const { command, output } = this._buildFfmpeg(url, seekSec);
130
162
  this._command = command;
163
+ this._ffmpegProc = null;
164
+ this._running = true;
165
+ this._paused = false;
166
+ this._stopped = false;
167
+ this._resetRunClock();
131
168
 
132
- command.on('start', cmd => this.emit('debug', `[cmd] ${cmd}`));
169
+ command.on('start', cmd => {
170
+ this._ffmpegProc = command.ffmpegProc ?? null;
171
+ this.emit('debug', `[cmd] ${cmd}`);
172
+ });
133
173
  command.on('stderr', line => this.emit('debug', String(line)));
134
174
 
135
175
  command.on('error', (err, _stdout, stderr) => {
@@ -139,7 +179,12 @@ class WebRtcStreamSession extends EventEmitter {
139
179
  this.emit('error', new Error(stderr ? `${msg}\n${stderr}` : msg));
140
180
  });
141
181
 
142
- signal.addEventListener('abort', () => command.kill('SIGTERM'), { once: true });
182
+ const onAbort = () => {
183
+ this._running = false;
184
+ this._ffmpegProc = null;
185
+ command.kill('SIGTERM');
186
+ };
187
+ signal.addEventListener('abort', onAbort, { once: true });
143
188
  command.run();
144
189
 
145
190
  const { livestream } = this.options;
@@ -149,8 +194,17 @@ class WebRtcStreamSession extends EventEmitter {
149
194
  this._playPromise = playStream(output, this.streamer, playOptions, signal);
150
195
 
151
196
  this._playPromise
152
- .then(() => this.emit('finish'))
197
+ .then(() => {
198
+ this._running = false;
199
+ this._ffmpegProc = null;
200
+ if (!signal.aborted) {
201
+ this._positionMs = 0;
202
+ this.emit('finish');
203
+ }
204
+ })
153
205
  .catch(err => {
206
+ this._running = false;
207
+ this._ffmpegProc = null;
154
208
  if (signal.aborted || err?.name === 'AbortError') return;
155
209
  this.emit('error', err);
156
210
  });
@@ -171,14 +225,56 @@ class WebRtcStreamSession extends EventEmitter {
171
225
  void this._run(url, this._abort.signal);
172
226
  }
173
227
 
228
+ /**
229
+ * Met en pause la lecture (ffmpeg suspendu, connexion vocale conservée).
230
+ */
231
+ pause() {
232
+ if (!this._running || this._paused || this._stopped) return;
233
+ this._positionMs = this._getPositionMs();
234
+ this._pausedAt = Date.now();
235
+ this._signalFfmpeg('SIGSTOP');
236
+ this._paused = true;
237
+ }
238
+
239
+ /**
240
+ * Reprend la lecture (après pause ou stop).
241
+ */
242
+ async resume() {
243
+ if (this._stopped) {
244
+ const seekSec = this._positionMs / 1000;
245
+ this._stopped = false;
246
+ await this._run(this.options.url, this._abort.signal, seekSec);
247
+ return;
248
+ }
249
+ if (!this._running || !this._paused) return;
250
+ this._pausedTotalMs += Date.now() - this._pausedAt;
251
+ this._pausedAt = 0;
252
+ this._signalFfmpeg('SIGCONT');
253
+ this._paused = false;
254
+ }
255
+
256
+ /**
257
+ * Arrête la lecture en conservant la position et la connexion vocale.
258
+ */
174
259
  stop() {
260
+ if (!this._running) return;
261
+ this._positionMs = this._getPositionMs();
262
+ this._stopped = true;
263
+ this._paused = false;
264
+ this._running = false;
265
+ this._ffmpegProc = null;
175
266
  this._abort.abort();
267
+ this._abort = new AbortController();
176
268
  }
177
269
 
270
+ /**
271
+ * Relance depuis la dernière position enregistrée.
272
+ */
178
273
  async replay() {
179
- this.stop();
180
- this._abort = new AbortController();
181
- await this._run(this.options.url, this._abort.signal);
274
+ const seekSec = (this._running ? this._getPositionMs() : this._positionMs) / 1000;
275
+ if (this._running) this.stop();
276
+ this._stopped = false;
277
+ await this._run(this.options.url, this._abort.signal, seekSec);
182
278
  }
183
279
 
184
280
  disconnect() {
@@ -198,6 +198,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
198
198
  return this.edit({ rtcRegion }, reason);
199
199
  }
200
200
 
201
+
201
202
  /**
202
203
  * Sets the user limit of the channel.
203
204
  * @param {number} userLimit The new user limit
@@ -223,6 +224,30 @@ class BaseGuildVoiceChannel extends GuildChannel {
223
224
  return this.edit({ videoQualityMode }, reason);
224
225
  }
225
226
 
227
+ /**
228
+ * Sets the status of the voice channel.
229
+ * @param {?string} status The new status (max 500 characters). Set to `null` to remove the status
230
+ * @returns {Promise<BaseGuildVoiceChannel>}
231
+ * @example
232
+ * // Set the status of a voice channel
233
+ * voiceChannel.setStatus('Hello!')
234
+ * .then(channel => console.log(`Set status to ${channel.status} for ${channel.name}`))
235
+ * .catch(console.error);
236
+ * @example
237
+ * // Remove the status of a voice channel
238
+ * voiceChannel.setStatus(null);
239
+ */
240
+ setStatus(status) {
241
+ return this.client.api.channels(this.id, 'voice-status').put({
242
+ data: {
243
+ status,
244
+ },
245
+ }).then(() => {
246
+ this.status = status;
247
+ return this;
248
+ });
249
+ }
250
+
226
251
  }
227
252
 
228
253
  TextBasedChannel.applyToClass(BaseGuildVoiceChannel, true, ['lastPinAt']);
@@ -105,6 +105,13 @@ class VoiceChannel extends BaseGuildVoiceChannel {
105
105
  * @param {string} [reason] Reason for changing the camera video quality mode.
106
106
  * @returns {Promise<VoiceChannel>}
107
107
  */
108
+
109
+ /**
110
+ * Sets the status of the voice channel.
111
+ * @name VoiceChannel#setStatus
112
+ * @param {?string} status The new status (max 500 characters). Set to `null` to remove the status
113
+ * @returns {Promise<VoiceChannel>}
114
+ */
108
115
  }
109
116
 
110
117
  module.exports = VoiceChannel;
@@ -719,13 +719,14 @@ export class BaseGuildVoiceChannel extends TextBasedChannelMixin(GuildChannel, [
719
719
  public rateLimitPerUser: number | null;
720
720
  public userLimit: number;
721
721
  public videoQualityMode: VideoQualityMode | null;
722
- public status?: string;
722
+ public status: string | null;
723
723
  public createInvite(options?: CreateInviteOptions): Promise<Invite>;
724
724
  public setRTCRegion(rtcRegion: string | null, reason?: string): Promise<this>;
725
725
  public fetchInvites(cache?: boolean): Promise<Collection<string, Invite>>;
726
726
  public setBitrate(bitrate: number, reason?: string): Promise<VoiceChannel>;
727
727
  public setUserLimit(userLimit: number, reason?: string): Promise<VoiceChannel>;
728
728
  public setVideoQualityMode(videoQualityMode: VideoQualityMode | number, reason?: string): Promise<VoiceChannel>;
729
+ public setStatus(status: string | null): Promise<this>;
729
730
  }
730
731
 
731
732
  export class BaseMessageComponent {
@@ -918,7 +919,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
918
919
  }
919
920
  >
920
921
  >;
921
- public startStream(options: StartStreamOptions): Promise<StreamSession>;
922
+ public startStream(options: StartStreamOptions): Promise<WebRtcStreamSession>;
922
923
 
923
924
  public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): this;
924
925
  public on<S extends string | symbol>(
@@ -1208,10 +1209,30 @@ export interface StartStreamOptions {
1208
1209
  downloadHttp?: boolean;
1209
1210
  /** Max video bitrate in kbps. Default: bitrate * 1.5 */
1210
1211
  bitrateMax?: number;
1212
+ /** x264 tune (ex. film). */
1213
+ tune?: string;
1214
+ /** readrateInitialBurst pour sources live. */
1215
+ livestream?: boolean;
1211
1216
  /** Low-latency encoding and playback. Default true. */
1212
1217
  lowLatency?: boolean;
1213
1218
  }
1214
1219
 
1220
+ /** Session Go Live WebRTC retournée par {@link Client#startStream}. */
1221
+ export class WebRtcStreamSession extends EventEmitter {
1222
+ public readonly client: Client;
1223
+ public pause(): void;
1224
+ public resume(): Promise<void>;
1225
+ public stop(): void;
1226
+ public replay(): Promise<void>;
1227
+ public disconnect(): void;
1228
+ public on(event: 'finish', listener: () => void): this;
1229
+ public on(event: 'debug', listener: (message: string) => void): this;
1230
+ public on(event: 'error', listener: (error: Error) => void): this;
1231
+ public once(event: 'finish', listener: () => void): this;
1232
+ public once(event: 'debug', listener: (message: string) => void): this;
1233
+ public once(event: 'error', listener: (error: Error) => void): this;
1234
+ }
1235
+
1215
1236
  export class StreamSession extends EventEmitter {
1216
1237
  public readonly client: Client;
1217
1238
  public readonly voiceConnection: VoiceConnection;