-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
610 lines (532 loc) · 25.9 KB
/
index.ts
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
// Dependecy imports
import axios from "axios";
import { execSync } from "child_process";
import { TextChannel, Message } from "discord.js";
import { config } from "dotenv";
const schedule = require('node-schedule');
import TelegramBot from 'node-telegram-bot-api';
const fs = require('fs');
const path = require('path');
import { createInvoiceREST } from './src/modules/donacioncilla';
// Load environment variables from .env file
config();
// Discord Client
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// CONSTANTS
const PRODILLO_FILE = path.join(__dirname, 'prodillo.json');
const BITCOIN_FILE = path.join(__dirname, 'bitcoin.json');
const TROFEILLOS_FILE = path.join(__dirname, 'trofeillos.json');
// Set time interval for automatic bot updates
const TIME_INTERVAL = 1000*210;
// Set time interval for prodillo game
const PRODILLO_TIME_INTERVAL = 1000*21;
// Discord bot token
client.login(process.env.DISCORD_TOKEN_ORIGINAL!);
// Telegram bot token
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!;
const bot = new TelegramBot(TELEGRAM_BOT_TOKEN!, {
polling: {
interval: 2100, // polling interval in ms
autoStart: true,
}
});
// Prodillo trophy
const trofeillo = '🏆 ';
// Define global variables
let lastReportedMax: number = 0;
let lastReportedMin: number = Infinity;
let telegramChats: { [key: number]: string } = {};
let discordChannels: { [key: string]: TextChannel } = {};
let prodillos: Record<string, { user: string; predict: number }>;
let isProdilleabe: boolean = false;
let bitcoinMax: number = 0;
let bitcoinMaxBlock: number = 0;
let bitcoinATH: number = 73757;
let bitcoinPrices = {
bitcoinATH: bitcoinATH,
lastReportedMax: lastReportedMax,
lastReportedMin: lastReportedMin,
bitcoinMax: bitcoinMax,
bitcoinMaxBlock: bitcoinMaxBlock,
};
let isTest: boolean = false;
let isWin: boolean = false;
let isWon: boolean = false;
let winnerName: string = '';
let trofeillos: Record<string, { champion: string; trofeillo: string[]}> = {};
let lastPrices: { price: number, min: number, max: number } = { price: 21, min: 21, max: 21 };
let lastDeadline = {
latestHeight: Infinity,
winnerDeadline: Infinity,
prodilleableDeadline: Infinity,
}
// Restores prodillos from JSON file
async function loadProdillos() {
try {
prodillos = JSON.parse(await fs.promises.readFile(PRODILLO_FILE, 'utf-8'));
} catch (e) {
throw new Error(`CRITICAL ERROR: Couldn't read prodillo.json file`);
}
}
// Restores Bitcoin prices from bitcoin.json file
async function loadValues() {
try {
bitcoinPrices = JSON.parse(await fs.promises.readFile(BITCOIN_FILE, 'utf-8'));
lastReportedMax = bitcoinPrices.lastReportedMax;
lastReportedMin = bitcoinPrices.lastReportedMin;
bitcoinMax = bitcoinPrices.bitcoinMax;
bitcoinATH = bitcoinPrices.bitcoinATH;
bitcoinMaxBlock = bitcoinPrices.bitcoinMaxBlock;
console.log('Initial values with bitcoin.json updated successfully:', bitcoinPrices);
} catch (e) {
throw new Error(`CRITICAL ERROR: Couldn't read bitcoin.json file`);
}
}
async function saveValues(value: number) {
try {
const data = JSON.parse(await fs.promises.readFile(BITCOIN_FILE, 'utf8'));
data.value = value;
await fs.promises.writeFile(BITCOIN_FILE, JSON.stringify(data, null, 2));
console.log(`${value} updated successfully:`, data.value);
} catch (err) {
console.error(`Failed to save ${value} value in bitcoin.json`);
}
}
// Initialize starting deadline for Prodillo game, next Bitcoin difficulty adjustment using mempool API
async function deadline() {
try {
const response = await axios.get('https://mempool.space/api/blocks/tip/height');
const latestHeight = Number(response.data);
lastDeadline = {
latestHeight: latestHeight,
winnerDeadline: 2015 - latestHeight % 2016, // 2016 is the Bitcoin difficulty adjustment
prodilleableDeadline: (2015 - latestHeight % 2016) - 690, // prodillos can be submitted 690 blocks before the difficulty adjustment
};
return lastDeadline;
} catch (error) {
console.error('deadline() error');
return lastDeadline;
};
}
// Define function that fetches the Bitcoin price using Binance API
async function getBitcoinPrices () {
try {
const { data } = await axios.get<{ last: string, low: string, high: string }>('https://www.bitstamp.net/api/v2/ticker/btcusd');
lastPrices = {
price: parseInt(data.last),
min: parseInt(data.low),
max: parseInt(data.high),
}
return lastPrices;
} catch (error) {
console.error('getBitcoinPrices() error');
return lastPrices;
}
};
// Define function that tracks the Bitcoin price at regular intervals and report the max and min only if values surpass old reported values
async function trackBitcoinPrice() {
while (true) {
try {
const { min, max } = await getBitcoinPrices();
// If price is higher than ATH...
if (max > bitcoinATH) {
bitcoinATH = max;
// Load bitcoin.json file and update bitcoinATH
saveValues(bitcoinATH);
// Sends ATH message to all Telegram and Discord chats
Object.keys(telegramChats).forEach(async chatId =>
(await hasSendPermission(chatId))
? bot.sendMessage(Number(chatId),`NUEVO ATH DE ₿: $${bitcoinATH}`)
: null);
Object.values(discordChannels).forEach(channel =>
channel.send(`NUEVO ATH DE ₿: $${bitcoinATH}`));
}
// If price is higher than reported max...
else if (max > lastReportedMax && max < bitcoinATH) {
lastReportedMax = max;
// Load bitcoin.json file and update lastReportedMax
saveValues(lastReportedMax);
// And sends daily high message to all Telegram and Discord chats
Object.keys(telegramChats).forEach(async chatId =>
(await hasSendPermission(chatId))
? bot.sendMessage(Number(chatId),`nuevo máximo diario de ₿: $${lastReportedMax}`)
: null);
Object.values(discordChannels).forEach(channel =>
channel.send(`nuevo máximo diario de ₿: $${lastReportedMax}`));
}
// If price is lower than reported min...
else if (min < lastReportedMin) {
lastReportedMin = min;
// Load bitcoin.json file and update lastReportedMin
saveValues(lastReportedMin);
// Sends daily low message to all Telegram and Discord chats
Object.keys(telegramChats).forEach(async chatId =>
(await hasSendPermission(chatId))
? bot.sendMessage(Number(chatId),`🐻 nuevo mínimo diario de ₿: $${lastReportedMin}`)
: null);
Object.values(discordChannels).forEach(channel =>
channel.send(`🐻 nuevo mínimo diario de ₿: $${lastReportedMin}`));
}
} catch (error) {
console.error('trackBitcoinPrice() error');
}
await new Promise(resolve => setTimeout(resolve, TIME_INTERVAL));
}
};
// Function to check if bot has permission to send messages
async function hasSendPermission(chatId: string) {
try {
const members = await bot.getChatAdministrators(chatId);
const botInfo = await bot.getMe();
const botMember = members.find(mem => mem.user.id === botInfo.id);
return botMember && botMember.can_send_messages;
} catch (error) {
console.error(`The bot hasn't permission to send messages in Telegram chat: ${chatId}`);
return false;
}
}
// Sends SE VIENE message at random intervals to all channels and chats where bot is
function seViene() {
const luckyNumber = Math.random();
const selectedMsg = luckyNumber <= 0.1
? '🫂 ABRACEN A SUS FAMILIAS!'
: luckyNumber <= 0.8 ? 'SE VIENE'
: '🔥 SE RECONTRA VIENE';
// Sends message to all Telegram and Discord chats
Object.keys(telegramChats).forEach(chatId =>
bot.sendMessage(Number(chatId),selectedMsg!));
Object.values(discordChannels).forEach(channel =>
channel.send(selectedMsg!));
setTimeout(seViene, Math.random() * ((69 - 1)*3600*1000) + 1 * 3600*1000); // Interval between 1 and 69 hours
};
// Define cron job to reset daily highs and lows at midnight (UTC = 00:00)
schedule.scheduleJob('0 21 * * *', async () => { // 21:00 at local time (UTC-3) = 00:00 UTC
lastReportedMax = 0;
lastReportedMin = Infinity;
// Load bitcoin.json file and update lastReportedMax/Min
const data = JSON.parse(await fs.promises.readFile(BITCOIN_FILE, 'utf8'));
data.lastReportedMax = lastReportedMax;
data.lastReportedMin = lastReportedMin;
await fs.promises.writeFile(BITCOIN_FILE, JSON.stringify(data, null, 2));
// Then send reset message to all Discord channels...
for (const channelId in discordChannels) {
discordChannels[channelId].send(`¡GN humanos!\n🔄 reiniciando máximos y mínimos diarios...`);
}
// And to all Telegram chats...
for (const chatId in telegramChats) {
bot.sendMessage(chatId, `¡GN humanos!\n🔄 reiniciando máximos y mínimos diarios...`);
}
});
// STARTING EVENT
// Detects automatically the Discord server where the bot is, detects the first text-based channel, store it and send a message to it
client.on('ready', async () => {
console.log(execSync('git log -1 --pretty=%B').toString().trim())
console.log(`${client.user?.tag} ready on Discord!`);
client.guilds.cache.forEach((guild: { channels: { cache: any[]; }; name: any; }) => {
guild.channels.cache.forEach(async (channel) => {
if (channel.isTextBased() && channel instanceof TextChannel) {
discordChannels[channel.id] = channel;
console.log(`Discord channel: ${guild.name} [${channel.id}]`);
}
});
});
// Starts main functions
await loadProdillos();
await loadValues();
await trackBitcoinPrice();
await prodilloInterval();
setTimeout(seViene, Math.random() * ((69 - 1)*3600*1000) + 1 * 3600*1000); // Interval between 1 and 69 hours
});
// Send Bitcoin price when user writes /precio, and max/min BTC price when user writes /hilo
client.on('messageCreate', async (message: { content: string; channel: TextChannel; }) => {
if (message.content === '/precio') {
const { price } = await getBitcoinPrices();
(message.channel as TextChannel).send(`precio de ₿: $${price} (${(100*(price/bitcoinATH)).toFixed(1)}% del ATH)`);
} else if (message.content === '/hilo') {
const { max, min } = await getBitcoinPrices();
(message.channel as TextChannel).send(`📈 máximo diario de ₿: $${max} (${(100*(max/bitcoinATH)).toFixed(1)}% del ATH)\n🐻 mínimo diario de ₿: $${min}\n🔺 Volatilidad diaria: $${max-min} (${(100*(max/min)-100).toFixed(1)}%)\n🚀 ATH de ₿: $${bitcoinATH}`);
}});
// Bot says GM every day at 8am (UTC-3)
schedule.scheduleJob('0 8 * * *', () => {
for (const channelId in discordChannels) {
discordChannels[channelId].send(`GM humanos 🧉`);
}
for (const chatId in telegramChats) {
bot.sendMessage(chatId, `GM humanos 🧉`);
}
});
// ------------TELEGRAM ONLY-------------
// Stores the chats where the bot is
bot.on('message', (msg) => {
if (!telegramChats.hasOwnProperty(msg.chat.id)) {
telegramChats[msg.chat.id] = msg.chat.title || msg.chat.first_name || 'Unknown';
console.log(`Added telegram chat: ${msg.chat.title || msg.chat.first_name} [${msg.chat.id}]`);
// Shows all the Telegram chats where the bot is
console.log("Current Chats:");
for (const [id, name] of Object.entries(telegramChats)) {
console.log(`- ${name} [${id}]`);
}
}
});
// Send Bitcoin price when user writes /precio
bot.onText(/\/precio(@botillo21_bot)?/, async (msg) => {
try {
const { price } = await getBitcoinPrices();
bot.sendMessage(msg.chat.id, `Precio actual de ₿: $${price} (${(100*(price/bitcoinATH)).toFixed(1)}% del ATH)`);
} catch (error) {
bot.sendMessage(msg.chat.id, 'Lo siento, hubo un error al obtener el precio de Bitcoin.');
console.error('error in Telegram command /precio');
}
});
// Send High and Low prices when user writes /hilo
bot.onText(/\/hilo(@botillo21_bot)?/, async (msg) => {
try {
const { max, min } = await getBitcoinPrices();
bot.sendMessage(msg.chat.id, `📈 máximo diario de ₿: $${max} (${(100*(max/bitcoinATH)).toFixed(1)}% del ATH)\n🐻 mínimo diario de ₿: $${min}\n🔺 Volatilidad diaria: $${max-min} (${(100*(max/min)-100).toFixed(1)}%)\n🚀 ATH de ₿: $${bitcoinATH}`);
} catch (error) {
bot.sendMessage(msg.chat.id, 'Lo siento, hubo un error al obtener el precio de Bitcoin.');
console.error('error in Telegram command /hilo');
}
});
// Welcome message constant
const welcome = (id: number, name: string | undefined) => bot.sendMessage(id, `¡GM ${name}!\n\nSoy Botillo, mira las cosas que puedo hacer por ti:\n\n- Reportar automaticamente el maximo o minimo mas reciente de Bitcoin\n/precio - Muestro el precio actual de Bitcoin\n/hilo - Muestro el máximo y mínimo en lo que va del dia\n/start - Muestro este mensaje\n\nProdillo: adivina el proximo maximo de BTC\n- Cada ronda dura 2016 bloques (un ajuste de dificultad)\n- Los jugadores pueden enviar prodillos hasta 420 bloques antes del fin de la ronda\n- El jugador que mas se aproxime al maximo de BTC de esa ronda sera el ganador\n/prodillo - Registra tu predicción del máximo de BTC de esta ronda\n/listilla - Muestra la lista de jugadores y sus prodillos\n/trofeillos - Muestra el salon de ganadores de prodillos\n\nPuedes mirar mi codigo en GitHub: https://github.com/Fierillo/botillo\n\n¡Gracias por usarme!`, {disable_web_page_preview: true});
// Sends welcome message when user writes /start
bot.onText(/^\/start$/, (msg) => welcome(msg.chat.id, msg.chat.title || msg.chat.first_name));
// Sends welcome message when bot joins new group
bot.on('new_chat_members', async (msg) => {
const botId = (await bot.getMe()).id;
msg.new_chat_members?.forEach(member => member.id === botId && welcome(msg.chat.id, msg.chat.title || msg.chat.first_name));
});
// Bot replies VERDADERO or FALSO when user asks it directly or tag it, finishing with a "?"
bot.on('message', async (msg) => {
if (msg.text && (msg.text.includes(`@${(await bot.getMe()).username}`) || msg.reply_to_message?.from?.id === (await bot.getMe()).id) && msg.text.endsWith('?')) {
bot.sendMessage(msg.chat.id, Math.random() < 0.5 ? '✅ VERDADERO' : '❌ FALSO');
}
});
// Bot replies ME CHUPA LA PIJA LA OPINION DE LAS KUKAS when users write "peron*", "kuka*", "kirchner*", "zurdo*"
bot.onText(/(?<=\s|^)(peron|kuka|kirchner|zurdo)\w*/i, (msg) => {
if (Math.random() <= 0.21) {
bot.sendMessage(msg.chat.id, msg.chat.id === -1001778459295 ? 'NO ME INTERESA LA OPINION DE LAS KUKAS' : 'ME CHUPA LA PIJA LA OPINION DE LAS KUKAS');
}
});
// Bot replies to shitcoiners
bot.onText(/(?<=\s|^)(eth|solana|sol |bcash|bch |polkadot|dot |cardano|ada )\w*/i, (msg) => {
if (Math.random() <= 0.21) {
bot.sendMessage(msg.chat.id, '🚨 ALERTA DU SHITCOINER 🚨');
}
});
/*bot.onText(/\/test/, (msg) => {
const test = msg.text?.split('/test ')[1];
if (test === 'on') {
isTest = true;
bot.sendMessage(msg.chat.id, '🟢 TEST ON');
} else if (test === 'off') {
isTest = false;
bot.sendMessage(msg.chat.id, '🔴 TEST OFF');
} else if (test === 'win') {
isWin = true;
bot.sendMessage(msg.chat.id, '🏆 WIN ON');
} else {
bot.sendMessage(msg.chat.id, `¡Ingresaste cualquier cosa loko!\n\n/test on - Activa el modo de prueba\n/test off - Desactiva el modo de prueba'\n/test win - Activa el evento de victoria`);
}
});*/
// Defines interval that checks deadlines and enable/disable prodillos. When deadline is over, sends a message to all Telegram chats to let them know the winner
async function prodilloInterval() {
while (true) {
// Calls deadline() values
const { winnerDeadline, prodilleableDeadline, latestHeight } = await deadline();
// Calls getBitcoinPrices() values
const { max } = await getBitcoinPrices();
// Check if deadline for prodillos is over
isProdilleabe = (prodilleableDeadline > 0);
// Check if winner has been announced and some blocks passed
if (isWon && !(winnerDeadline === 0) && (winnerDeadline < 210)) {
isWon = false
}
// Updates bitcoinMax to track maximum BTC price in the current round, also record it in a JSON file. Aditionally record the correspondent block height
if (max > bitcoinMax) {
bitcoinMax = max;
bitcoinMaxBlock = latestHeight;
// Load bitcoin.json file and update bitcoinMax/bitcoinMaxBlock
try {
const data = JSON.parse(await fs.promises.readFile(BITCOIN_FILE, 'utf8'));
data.bitcoinMax = bitcoinMax;
data.bitcoinMaxBlock = bitcoinMaxBlock;
await fs.promises.writeFile(BITCOIN_FILE, JSON.stringify(data, null, 2));
} catch (err) {
console.error('Failed to save bitcoinMax/bitcoinMaxBlock in bitcoin.json');
}
}
// Triggers win event if deadline is over (difficulty adjustment of Bitcoin)
if (((winnerDeadline === 0) && !isWon) || isWin) {
// Read prodillo.json file and store it in a local variable
prodillos = JSON.parse(await fs.promises.readFile(PRODILLO_FILE, 'utf-8'));
// Sort the prodillos by their difference from the current Max Bitcoin price of the round
const prodillosSorted = Object.entries(prodillos).sort(([,a],[,b]) =>
Math.abs(a.predict - bitcoinMax) - Math.abs(b.predict - bitcoinMax)
);
// Format the list of prodillos
const formattedList = prodillosSorted.map(([userId, { user, predict }]) => {
return `${user}: $${predict} (dif: ${(Math.abs(predict as unknown as number - bitcoinMax))})`}).join('\n');
// Stores the winner in local variables
const winnerId = prodillosSorted[0][0];
winnerName = prodillosSorted[0][1].user;
// Send a message to all Telegram chats
for (const chatId in telegramChats) {
await bot.sendMessage(chatId, `<pre>🏁 ¡LA RONDA A LLEGADO A SU FIN!\nMaximo de ₿ de esta ronda: $${bitcoinMax}\n------------------------------------------\n${formattedList}\n\nEl ganador es ${winnerName} 🏆</pre>`, { parse_mode: 'HTML' });
}
// Read trofeillos.json file and store it in a global variable
try {
trofeillos = JSON.parse(fs.readFileSync('trofeillos.json', 'utf-8'));
console.log('trofeillos.json file read successfully');
} catch (error) {
console.log('Could not read trofeillos.json file');
}
// Add the new trophy to winner
if (!trofeillos[winnerId]) {
trofeillos[winnerId] = {
champion: winnerName,
trofeillo: [`${trofeillo}[${bitcoinMaxBlock}]`],
};
} else {
trofeillos[winnerId].trofeillo.push(`${trofeillo}[${bitcoinMaxBlock}]`);
}
// Save the new trophy status in a JSON file
fs.writeFileSync(TROFEILLOS_FILE, JSON.stringify(trofeillos, null, 2));
// Restart max BTC price for the nex round
bitcoinMax = 0;
// Wipe bitcoin.json file
try {
fs.writeFileSync(BITCOIN_FILE, JSON.stringify({}, null, 2));
console.log('bitcoin.json file wiped successfully');
} catch (err) {
console.error('Failed to wipe bitcoin.json file');
}
// Wipe prodillo.json file
try {
fs.writeFileSync(PRODILLO_FILE, JSON.stringify({}, null, 2));
console.log('prodillo.json file wiped successfully');
} catch (err) {
console.error('Failed to wipe prodillo.json file');
}
try {
fs.writeFileSync(PRODILLO_FILE, JSON.stringify({'0': {user: 'Hal Finney', predict: 10000000}}, null, 2));
console.log('Hal Finney prediction added to prodillo.json file');
} catch (err) {
console.error('Failed to add Hal Finney prediction to prodillo.json file');
}
// Prevents that win event is triggered again for a while
isWon = true
}
await new Promise(resolve => setTimeout(resolve, PRODILLO_TIME_INTERVAL));
}
};
// Stores user predictions of BTC price in a JSON file and replies a reminder with the deadline
bot.onText(/\/prodillo(\s|\@botillo21_bot\s)(.+)/, async (msg, match) => {
// Calls deadline function and stores in local variables
const { winnerDeadline, prodilleableDeadline } = await deadline();
// If deadline for prodillos is over, returns a message to the users to let them know
if(!isProdilleabe && !isTest) {
return await bot.sendMessage(msg.chat.id, `Tarde loko!\nespera ${winnerDeadline} bloques que comience una nueva ronda de prodillos!`);
}
const userId = msg.from?.id;
const user = msg.from?.username;
const predictStr = (match as RegExpMatchArray)[2];
const predict = Math.round(Number(predictStr));
if ((isProdilleabe || isTest) && userId && user && !isNaN(predict) && predict >= 0 && isFinite(predict)) {
// try to read prodillo.json file
try {
const fileContent = await fs.promises.readFile(PRODILLO_FILE, 'utf-8');
prodillos = JSON.parse(fileContent);
} catch (error) {
console.error('error in Telegram command /prodillo');
}
// Check if the prediction already exists
const existingPredictions = Object.values(prodillos).map(p => p.predict);
if (existingPredictions.includes(predict)) {
return await bot.sendMessage(msg.chat.id, `Ese prodillo ya existe. ¡Elegi otro valor loko!`);
}
// If the prediction is lower than current Bitcoin max price in the round, returns a message to the user
if (predict < bitcoinMax) {
return await bot.sendMessage(msg.chat.id, `Tenes que ingresar un valor mayor a ${bitcoinMax} para tener alguna chance de ganar.\nMentalidad de tiburón loko!`);
}
// Stores user prediction in a prodillo.json file
prodillos[userId] = {
user: user,
predict: predict,
};
await fs.promises.writeFile(PRODILLO_FILE, JSON.stringify(prodillos, null, 2));
// Sends a reminder with the deadline
await bot.sendMessage(msg.chat.id, `Prodillo de ${user} registrado: $${predict}\n\n🟧⛏️ Tiempo restante para mandar prodillos: ${isProdilleabe? prodilleableDeadline : 0} bloques\n🏁 Tiempo restante para saber ganador: ${winnerDeadline} bloques`, {disable_web_page_preview: true});
console.log(`Registered prodillo of ${user} [${userId}]: ${predict}`);
} else await bot.sendMessage(msg.chat.id, '¡Ingresaste cualquier cosa loko!\n\n/prodillo <numero>');
});
// When user writes /lista, sends a list of all registered prodillos
bot.onText(/\/listilla/, async (msg) => {
try {
// Read prodillo.json file and store it in a local variable
prodillos = JSON.parse(await fs.promises.readFile(PRODILLO_FILE, 'utf-8'));
// Get the deadlines
const { winnerDeadline, prodilleableDeadline } = await deadline();
// Sort the prodillos by their difference from the current Max Bitcoin price
const sortedProdillos = Object.entries(prodillos).map(([userId, { user, predict }]) => {
return {user, predict, diff: Math.abs(predict - bitcoinMax)};
}).sort((a, b) => a.diff - b.diff);
const closestProdillo = sortedProdillos[0].predict;
let formattedList = `${('Usuario').padEnd(20, ' ')} | ${('Predicción').padEnd(10, ' ')} | Diferencia\n`;
formattedList += '-----------------------------------------------------\n';
sortedProdillos.forEach(({ user, predict, diff }) => {
if (predict < closestProdillo) {
formattedList += `<s>${user.padEnd(20, ' ')} | $${(predict.toString()).padStart(10, ' ')} | ${diff}</s> (REKT!)\n`;
} else {
formattedList += `${user.padEnd(20, ' ')} | $${(predict.toString()).padStart(10, ' ')} | ${diff}\n`;
}
});
await bot.sendMessage(msg.chat.id, `<pre><b>LISTA DE PRODILLOS:</b>\n\nPrecio máximo de ₿ en esta ronda: $${bitcoinMax}\n-----------------------------------------------------\n${formattedList}\n\n🟧⛏️ Tiempo restante para mandar prodillos: ${isProdilleabe ? prodilleableDeadline : 0} bloques\n🏁 Tiempo restante para saber ganador: ${winnerDeadline} bloques</pre>`, { parse_mode: 'HTML' });
} catch (error) {
console.error('Could not get the list of prodillos');
await bot.sendMessage(msg.chat.id, 'No se pudo obtener la lista de prodillos.');
}
});
// When user writes /trofeillos, sends a list of all winners of the game and the number of trophys of each one
bot.onText(/\/trofeillos/, (msg) => {
// Read trofeillos.json to get the list of winners
try {
trofeillos = JSON.parse(fs.readFileSync('trofeillos.json', 'utf-8'));
} catch (e) {
trofeillos = {};
}
let mensaje = "";
for (const [id, data] of Object.entries(trofeillos)) {
mensaje += `\n- ${data.champion}: ${data.trofeillo}`;
}
bot.sendMessage(msg.chat.id, `<pre><b>SALA DE TROFEILLOS</b>\n\nUltimo campeón: ${winnerName}\nCampeón: 🏆 [nro. de bloque]\n------------------------------------------------------------------------------${mensaje || 'No hay ganadores aún.'}</pre>`, { parse_mode: 'HTML' });
});
// Defines a function that creates an invoice for donations
bot.onText(/\/donacioncilla(\s|\@botillo21_bot\s)(.+)/, async (msg, match) => {
const chatId = msg.chat.id;
const userId = msg.from?.id;
const user = msg.from?.username;
const amountStr = (match as RegExpMatchArray)[2];
const amount = Math.round(Number(amountStr));
if (userId && user && !isNaN(amount) && amount >= 0 && isFinite(amount)) {
try {
// Create LND invoice
const invoice = await createInvoiceREST(amount, `Donación de ${amount} satoshis`);
console.log(`🟨 ¡User ${user} [${userId}] wants to donate ${amount} sats!`);
await bot.sendMessage(chatId, `🍾 ¡Gracias por querer donar ${amount} satoshi${amount > 1 ? 's' : ''} loko/a! 🙏\n\n¡Toma, paga aca!: ${invoice.request}`);
} catch (error) {
console.error(`❌ error when ${user} [${userId}] tried to donate ${amount} sats`, error);
await bot.sendMessage(chatId, '❌ Lo siento loko, hubo un error al generar el invoice, proba devuelta');
}
} else {
await bot.sendMessage(chatId, '❌ ¡Ingresaste cualquier cosa loko!\n\n/donacioncilla <monto en satoshis>');
}
});