-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
303 lines (277 loc) · 10.8 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
const express = require('express');
const tmi = require('tmi.js');
const fs = require('fs');
const path = require('path');
var {exec} = require('child_process');
const axios = require('axios');
const https = require("https");
/**
* Represents an instance of an HTTPS agent.
*
* @class
*/
const agent = new https.Agent({ rejectUnauthorized: false });
/**
* @description A variable that holds an instance of an Express application.
* @type {object}
*/
const app = express();
app.use(express.json()); // Enable JSON req.body parsing
/**
* Represents the secret key for encryption or authentication purposes.
*
* @type {string}
* @readonly
* @description This variable stores a randomly generated secret key that is used for encryption or authentication operations.
* The secret key is a string consisting of a combination of uppercase letters (A-Z), lowercase letters (a-z),
* digits (0-9), and special characters (!@#$%^&*()_+-=[]{};':"|,./<>?). It is important to keep this key
* confidential and secure to ensure the integrity and security of the system.
*/
const secretKey = '';
/**
* Represents the owner of the bot.
*
* @type {string}
*/
const botOwner = ''; // replace with your Twitch username
/**
* Represents the username of the bot.
*
* @type {string}
*/
const botUsername = ''; // replace with your bot's username
/**
* Represents the authentication token for the bot.
*
* @type {string}
*/
const botToken = 'oauth:'; // replace with your bot's oauth token
/**
* Represents the main channel that the bot is associated with.
*
* @type {string}
*/
const mainChannel = botUsername; // replace with the bot's main channel
/**
* The path to the 'channels.json' file.
*
* @type {string}
*/
const channelsFile = path.join(__dirname, 'channels.json');
/**
* URL of the API endpoint for accessing the RandomChat application.
*
* @type {string}
*/
const apiUrl = 'https://randomtwitch.chat/api'
// Read channels from JSON file
/**
* Reads the channels from the channels file and returns them.
*
* If the channels file doesn't exist, a new file is created and initialized
* with a single channel (mainChannel).
*
* If the channels array doesn't include the mainChannel, it is added to the array
* and the updated channels array is written back to the channels file.
*
* @return {Array} The channels array read from the channels file.
*/
function readChannels() {
if (!fs.existsSync(channelsFile)) {
fs.writeFileSync(channelsFile, JSON.stringify([mainChannel]));
}
const channels = JSON.parse(fs.readFileSync(channelsFile, 'utf8'));
if (!channels.includes(mainChannel)) {
channels.push(mainChannel);
writeChannels(channels);
}
return channels;
}
// Write channels to JSON file
/**
* Writes the given channels object to a file in JSON format.
*
* @param {object} channels - The channels object to be written to file.
*
* @return {void}
*/
function writeChannels(channels) {
fs.writeFileSync(channelsFile, JSON.stringify(channels, null, 2));
}
// Define the TMI client
/**
* Represents a Twitch client for interacting with the Twitch Messaging Interface (TMI).
* @class
*/
let client = new tmi.Client({
options: { debug: true },
identity: {
username: botUsername,
password: botToken,
},
channels: readChannels()
});
/**
* Reconnects the client by disconnecting it and then restarting the server.
*
* @function reconnectClient
* @returns {Promise} A Promise that resolves when the client is reconnected successfully.
*/
function reconnectClient() {
client.disconnect().then(() => {
exec("pm2 restart index.js")
});
}
client.connect().catch(console.error);
client.on('connected', () => {
console.log(`Connected to channels: ${client.getOptions().channels.join(', ')}`);
});
// all
client.on('message', (channel, tags, message, self) => {
if (self) return;
const command = message.trim();
const isBroadcaster = tags.badges && 'broadcaster' in tags.badges;
const isModerator = tags.mod || isBroadcaster;
// Ping command available in any channel
// if (command === '!ping') {
// client.say(channel, `Pong!`);
// }
// Check if user is mod or broadcaster
if (isModerator ?? isBroadcaster) {
if(command.startsWith('!blacklist ')) {
const username = command.split(' ')[1];
axios.post(apiUrl + '/blacklist', { username: username, key: secretKey, channel_name: channel.slice(1), status: true }, { httpsAgent: agent })
.then(response => {
client.say(channel, `${username} has been blacklisted from your channel.`);
})
.catch(error => {
console.error('Error adding user to blacklist:', error);
client.say(channel, `Failed to blacklist ${username}.`);
});
}
if(command.startsWith('!unblacklist ')) {
const username = command.split(' ')[1];
axios.post(apiUrl + '/blacklist', { username: username, key: secretKey, channel_name: channel.slice(1), status: false }, { httpsAgent: agent })
.then(response => {
client.say(channel, `${username} has been removed from your channels blacklist.`);
})
.catch(error => {
console.error('Error removing user from blacklist:', error);
client.say(channel, `Failed to unblacklist ${username}.`);
});
}
}
});
// bot
client.on('message', (channel, tags, message, self) => {
if (self) return;
const command = message.trim();
const username = tags.username;
if (channel === `#${mainChannel}`) {
if (command.startsWith('!join')) {
if (!client.getOptions().channels.includes(`#${username}`)) {
// Use API to add channel dynamically
axios.post(apiUrl + '/channels/add', { channel: username, key: secretKey }, { httpsAgent: agent })
.then(response => {
const channels = readChannels();
channels.push(username);
writeChannels(channels);
client.say(channel, `Joined ${username}'s channel!`);
reconnectClient();
})
.catch(error => {
console.error('Error adding channel:', error);
client.say(channel, `Failed to join ${username}'s channel.`);
});
} else {
client.say(channel, `Already in ${username}'s channel.`);
}
}
if (command.startsWith('!leave')) {
if (client.getOptions().channels.includes(`#${username}`)) {
// Use API to remove channel dynamically
axios.post(apiUrl + '/channels/remove', { channel: username, key: secretKey }, { httpsAgent: agent })
.then(response => {
let channels = readChannels();
channels = channels.filter(chan => chan !== username);
writeChannels(channels);
client.say(channel, `Left ${username}'s channel!`);
reconnectClient();
})
.catch(error => {
console.error('Error removing channel:', error);
client.say(channel, `Failed to leave ${username}'s channel.`);
});
} else {
client.say(channel, `Not in ${username}'s channel.`);
}
}
if (username === botOwner) {
if (command.startsWith('!forcejoin ')) {
const targetChannel = command.split(' ')[1];
if (targetChannel && !client.getOptions().channels.includes(`#${targetChannel}`)) {
axios.post(apiUrl + '/channels/add', { channel: targetChannel, key: secretKey }, { httpsAgent: agent })
.then(response => {
let channels = readChannels();
channels.push(targetChannel)
writeChannels(channels);
client.say(channel, `Force Joined ${targetChannel}'s channel!`);
reconnectClient();
})
.catch(error => {
console.error('Error removing channel:', error);
client.say(channel, `Failed to join ${targetChannel}'s channel.`);
});
} else {
client.say(channel, `Already in ${targetChannel}'s channel or invalid channel.`);
}
}
if (command.startsWith('!forceleave ')) {
const targetChannel = command.split(' ')[1];
if (targetChannel && client.getOptions().channels.includes(`#${targetChannel}`)) {
axios.post(apiUrl + '/channels/remove', { channel: targetChannel, key: secretKey }, { httpsAgent: agent })
.then(response => {
let channels = readChannels();
channels = channels.filter(chan => chan !== targetChannel);
writeChannels(channels);
client.say(channel, `Left ${username}'s channel!`);
reconnectClient();
})
.catch(error => {
console.error('Error removing channel:', error);
client.say(channel, `Failed to leave ${username}'s channel.`);
});
} else {
client.say(channel, `Not in ${targetChannel}'s channel or invalid channel.`);
}
}
}
}
});
app.post('/send', (req, res) => {
const { message, key, channel } = req.body;
// check the secret key
if (key !== secretKey) {
return res.status(401).send({ error: 'Unauthorized' });
}
if (!message) {
return res.status(400).send({ error: 'Message is required' });
}
if (!channel) {
return res.status(400).send({ error: 'Channel is required' });
}
client.say(channel, message)
.then(() => res.send({ status: 'Message sent' }))
.catch((err) => res.status(500).send({ error: 'Failed to send message', details: err.message }));
});
app.get('/', (req, res) => {
res.sendStatus(404);
});
/**
* The port variable determines the port number on which the application will listen for incoming requests.
* It is derived from the environment variable 'PORT', if set; otherwise, it defaults to 3000.
*
* @type {number}
*/
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server running on port ${port}`));