35 |
36 |
37 |
38 | 
39 |
40 |
41 |
42 |
43 |
44 | ## 🤔 **What is this Drop Bot all about?**
45 |
46 | * Makes your drop experience as easy as possible.
47 | * No need to watch the stream in a browser, fully uses gql.
48 | * No need to care about who is online and when.
49 | * Saves your session providing you autologin.
50 | * Can watch every Drop / Campaign available.
51 | * Automatically claims your Drops.
52 | * Switches automatically to other games or drops if drop is claimed/claimable or offline.
53 |
54 |
Disclaimer - DropBot is not intended for:
55 |
56 | * Mining channel points - it's about the drops only.
57 | * Mining anything else besides Twitch drops.
58 | * Unattended operation.
59 | * 100% uptime application, due to the underlying nature of it, expect fatal errors to happen every so often.
60 | * Being hosted on a remote server as a 24/7 bot.
61 | * Being used with more than one managed account.
62 | * Any form of automatic restart when an error happens.
63 | * Using it with more than one managed account.
64 | * Making it possible to mine campaigns that the managed account isn't linked to.
65 | * Anything that increases the site processing load caused by the application.
66 | * Mining campaigns the managed account isn't linked to.
67 | * Being associated in any way with Twitch
68 |
69 |
70 |
71 |
72 | ## ⚡ **Installation**
73 |
74 |
Windows
75 |
76 | 1. Download the windows executable from the **[build branch](https://github.com/Zaarrg/DropBot/tree/build)** or **[release page](https://github.com/Zaarrg/DropBot/releases)**.
77 | 2. Move the executable to a folder.
78 | 3. **Execute** the `DropBot.exe`. The **settings** and **drop-session** will be generated right beside the executable.
79 |
80 |
Linux
81 |
82 | 1. Download the linux executable from the **[build branch](https://github.com/Zaarrg/DropBot/tree/build)** or **[release page](https://github.com/Zaarrg/DropBot/releases)**.
83 | 2. Move the executable to a folder.
84 | 3. Give the `DropBot-linux-x64` file permission to execute via chmod if needed.
85 | ```bash
86 | chmod +x ./DropBot-linux-x64
87 | ```
88 |
89 | 4. **Execute** the `DropBot-linux-x64`. The **settings** and **drop-session** will be generated right beside the executable.
90 |
91 | ```bash
92 | ./DropBot-linux-x64
93 | ```
94 |
Ubuntu
95 | Using Bot with No GUI - Only Command Line
96 |
97 | 1. Download the linux executable from the **[build branch](https://github.com/Zaarrg/DropBot/tree/build)** or **[release page](https://github.com/Zaarrg/DropBot/releases)**.
98 | 2. Drag and Drop a `settings.json` and `drop-session.json` file right beside the executable.
99 | 3. Make sure you have set `displayless` to `true` in your settings.json
100 | 4. **Execute** the `DropBot-linux-x64`.
101 |
102 | ```bash
103 | ./DropBot-linux-x64
104 | ```
105 | ⚠️ _If you want to specifiy wich games to watch use the **Prioritylist** setting_ ⚠️
106 |
107 | ⚠️ _If you want to watch Custom Channels drag and drop a `customchannels.json` to your executable location and set `ForceCustomChannel` in settings.json to `true`_ ⚠️
108 |
109 | ⚠️ _If you can't seem to get any progress on drops "always stuck" try loging in instead of copying drop-session.json._ ⚠️
110 |
111 |
Npm
112 |
113 | 1. Clone the **[Repository](https://github.com/Zaarrg/DropBot)**.
114 | ```bash
115 | git clone https://github.com/Zaarrg/DropBot
116 | ```
117 |
118 | 2. Install NPM packages.
119 | ```bash
120 | cd DropBot/
121 | npm install
122 | ```
123 | 3. Run the bot via npm scripts.
124 | ```bash
125 | npm run start:production
126 | OR
127 | npm run start:dev
128 | ```
129 |
130 |
Docker
131 |
132 | 1. Get your auth token
133 |
134 | ```bash
135 | docker run --rm -it ghcr.io/zaarrg/dropbot/dropbot:latest node ./build/index.js --showtoken
136 | ```
137 |
138 | 2. Login in, copy your auth token, and then exit the container with `Ctrl + C`
139 |
140 | 3. Create the container
141 |
142 | ```bash
143 | docker run -d --name dropbot \
144 | -e dropbot_displayless=true \
145 | -e dropbot_token=TokenFromStep1 \
146 | -e dropbot_games="Sea_of_Thieves Rust Lost_Ark No_Man's_Sky" \
147 | -e dropbot_autoclaim=true \
148 | ghcr.io/zaarrg/dropbot/dropbot:latest
149 | ```
150 | ---
151 |
152 | ## 📚 **How to use the Bot?**
153 |
154 |
Step by Step Usage: Drops
155 |
156 | **1. Step**
157 |
158 |
159 | Select the way you want to Log in into your account.
160 |
161 |
162 |
163 | ⚠️ If you cant login directly because of CAPTCHA use the browser method. ⚠️
164 | ⚠️ Only Chromium Browsers are supported like Brave and Chrome . ⚠️
165 |
172 | Select Drops to watch a Campaign or Custom Channels if you want to add your own channels. Refer to Step by Step Usage: Custom Channels for those.
173 |
246 | Login if necessary, and choose any app name you want, select your region and click Deploy app
247 | After that let Heroku go through the build process and then click on Manage App
248 |
269 | Now run the command node ./build/index.js --showtoken in the Terminal.
270 | Login Directly via command Line, until you see your auth token and copy it.
271 |
278 | Close the Terminal and go to Settings then Reveal Config Vars
279 | Now type in as key dropbot_token and as value your copied token and click add
280 | You can find more environment variables
281 | here
282 |
288 | 🎉 Thats it Enjoy! You are successfully watching.
289 | To check if its working click on more in the top right corner then view logs.
290 | Give it some time to start up, and you should see the bot working.
291 |
292 |
293 | 
294 |
295 |
296 | ---
297 |
298 | ## 📝 **Settings**
299 |
300 | Down below you can find the settings Variables and what they do.
301 |
302 | ### Chromeexe
303 | - The path of your Browser: Linux: google-chrome | Windows: C:\Program Files\Google\Chrome\Application\chrome.exe
304 |
305 | ### UserDataPath
306 | - Providing a userdatapath, will give the loginpage the option to use cookies out of your browser. Option not really needed anymore.
307 | - You can find the UserdataPath under chrome://version then under Profile Path
308 |
309 | ### Webhook
310 | - The Discord Webhook URL: https://discord.com/api/webhooks/...
311 |
312 | ### WebHookEvents
313 | - Set what events should be send via webhook.
314 | - Defaults to: ["requestretry", "claim", "newdrop", "offline", "newgame", "get", "getresult", "progress", "start", "error", "warn", "info"]
315 |
316 | ### Debug
317 | - Will log important values to the console for debugging.
318 |
319 | ### Displayless
320 | - Give the ability to use the bot fully automated with no user input needed. Especially useful for gui-less systems. See [Ubuntu - No Gui](https://github.com/Zaarrg/DropBot/#ubuntu)
321 |
322 | ### ForceCustomChannel
323 | - Force the bot to watch Custom Channels, only useful for display-less mode.
324 |
325 | ### ProgressCheckInterval
326 | - The time in ms, in what interval the progress should be checked. Recommended is `60000 ms - 60 s` anything under could cause blocking your request.
327 |
328 | ### RetryDelay
329 | - The time in ms, in what interval failed requests should be retried. Recommended is `60000 ms - 60 s` anything under could cause blocking your request.
330 |
331 | ### WaitforChannels
332 | - If set to false the Bot will no longer wait 5 Minutes for new Channels to come online. It will switch to another game instead.
333 |
334 | ### Prioritylist
335 | - A list of Games the bot should watch / prioritize. Only Provide games with active Drop Campaigns in this Format:
336 | `["Rust","Fortnite", "Elite: Dangerous"]`
337 | - You can get the valid name from
338 | - If provided the bot will only watch the games listed.
339 |
340 | ### AutoClaim
341 | - Allow the bot to autoClaim or not
342 |
343 | ### LogToFile
344 | - Log the Console to a file.
345 |
346 | ### UseKeepAlive
347 | - If activated uses Express to the keepalive the bot useful for stuff like Replit.
348 |
349 |
350 |
351 | ---
352 |
353 | ## ✏️ **Start Arguments**
354 |
355 | All available start Arguments, basically everything which is also in the [settings.json](https://github.com/Zaarrg/DropBot#-settings) file.
356 |
357 | ```bash
358 | ./DropBot.exe --help
359 |
360 | Usage: ./DropBot or index.js --arg...
361 |
362 | Options:
363 | --help Show help. [boolean]
364 | --version Show version number. [boolean]
365 | -c, --chrome The path to your Chrome executable. [string]
366 | -u, --userdata The path to your userdata folder location. [string]
367 | --webhook, --wh The Discord Webhook URL. [string]
368 | --webhookevents Set what events should be send via webhook. [array]
369 | -i, --interval The progress interval in ms. [number]
370 | --retryinterval, --retry The retry interval in ms. [number]
371 | -g, --games The Games the bot should watch. [array]
372 | --token Your auth_token. [string]
373 | -d, --debug Enable Debug logging. [boolean]
374 | --displayless, --dl Enable Displayless mode. [boolean]
375 | --forcecustomchannel Force Custom Channels. Only useful for
376 | display-less mode. [boolean]
377 | --waitforchannels, --waitonline Disable waitforchannels, forcing the bot to not wait
378 | for other channels with drops instead switch the game. [boolean]
379 | --autoclaim Enable autoclaim. [boolean]
380 | --log Enable logging to file. [boolean]
381 | --usekeepalive Enable Express KeepAlive. [boolean]
382 | --tray Start app in the tray. [boolean]
383 |
384 | Examples:
385 | --chrome C:path:to:chrome.exe Sets your chrome path.
386 | --userdata C:path:to:userdata-folder Sets your userdata path.
387 | --webhook https:discord.com:api:webh.... Sets your webhook url.
388 | --webhookevents requestretry claim Defaults to the events in this
389 | newdrop offline newgame get getresult example provided.
390 | progress start error warn info
391 | --interval 30000 Sets the progress interval to 30s.
392 | --retryinterval 30000 Sets the retry interval to 30s.
393 | --games Rust Krunker 'Elite: Dangerous' Sets the Prioritylist to Rust,
394 | Krunker and Elite: Dangerous.
395 | --token yourkindalongtoken Sets the your current auth
396 | token, overwriting any in
397 | drop-session.json.
398 |
399 | ```
400 |
401 | ## ✏️ **Environment variables**
402 |
403 | All these Start Arguments also work as environment variable:
404 |
405 | ```bash
406 | dropbot_chrome = YourPath
407 | dropbot_userdata = YourPath
408 | dropbot_webhook = DiscordWebhookURL
409 | dropbot_interval = 60000
410 | dropbot_games = Game1 Game2 Game3... ⚠️ Black Desert -> Black_Desert ⚠️
411 | dropbot_debug = true || false
412 | dropbot_displayless = true || false
413 | dropbot_forcecustomchannel = true || false
414 | dropbot_waitforchannels = true || false
415 | dropbot_autoclaim = true || false
416 | dropbot_log = true || false
417 | dropbot_usekeepalive = true || false
418 | dropbot_retryinterval = 60000
419 | dropbot_webhookevents = Event1 Event2 Event3...
420 | dropbot_showtoken = true || false Usefull for System were you cant access your drop-session.json
421 | dropbot_token = YourToken
422 | ```
423 |
424 | ## 📘 Adding Custom Channels
425 |
426 |
427 |
428 | 
429 |
430 | ### Name
431 | - The Name can be any String like `Rainbow Six, Best Ch ever etc...`
432 |
433 | ### Url
434 | - The Url is very important, never use the same Url twice, it has to be a valid Channel link and has always to start with `https://www..tv/`. Example for a Valid Url: `https://www..tv/rainbow6tw`
435 |
436 | ### How the Channel should be Watched
437 |
438 | `Watch until the time runs out:`
439 | - Watches the channel until the left time reaches 0 then switches to other custom channel.
440 |
441 | `Watch indefinitely:`
442 | - Watches the channel until it goes offline, then switches.
443 |
444 | ### Farm Points
445 | - Pretty simple, should the bot farm Points or not.
446 |
447 | ### Editing already Added Channel's
448 | - You can always edit Channel's which are already added in the [CustomChannels.json]('https://github.com/Zaarrg/DropBot/#example-customchannelsjson').
449 |
450 |
451 | ---
452 |
453 | ## 📄 Json Files Examples
454 |
455 | ### Example Settings.json
456 | ```json
457 | {
458 | "Chromeexe": "",
459 | "UserDataPath": "",
460 | "WebHookURL": "",
461 | "WebHookEvents": [],
462 | "debug": false,
463 | "displayless": false,
464 | "ProgressCheckInterval": 60000,
465 | "RetryDelay": 60000,
466 | "WaitforChannels": true,
467 | "Prioritylist": [],
468 | "AutoClaim": true,
469 | "LogToFile": true,
470 | "ForceCustomChannel": false,
471 | "UseKeepAlive": false
472 | }
473 | ```
474 |
475 | ### Example CustomChannels.json
476 | ```json
477 | [
478 | {
479 | "Name": "tarik",
480 | "Link": "https://www..tv/tarik",
481 | "WatchType": "Watch until time runs out",
482 | "Time": "50",
483 | "Points": true
484 | }
485 | ]
486 | ```
487 |
488 | ### Example Session
489 | ```json
490 | [
491 | {
492 | "name": "auth-token",
493 | "value": "yourtoken"
494 | }
495 | ]
496 | ```
497 |
498 | ⚠️ _Never share your **Token** with anyone, because it gives full access to your account_ ⚠️
499 |
500 |
501 |
502 | ---
503 |
504 | ## 🎉 Enjoy the bot and hopefully its helpful!
505 |
506 | [](https://github.com/Zaarrg)
507 | [](https://github.com/Zaarrg/DropBot/stargazers)
508 | [](https://github.com/Zaarrg/DropBot/watchers)
509 | [](https://github.com/Zaarrg/DropBot/network/members)
510 |
511 | If you like my work feel free to buy me a coffee. ☕
512 |
513 | [](https://ko-fi.com/zaarrg)
514 |
515 | Have fun and Enjoy! 😃
516 |
517 | ---
518 |
519 | ## 🍰 Contact
520 |
521 | **_Quickest Response:_**
522 | Discord Server: https://discord.gg/rV26FZ2upF
523 |
524 | **_Slow Response:_**
525 | Discord: - Zarg#8467
526 |
527 |
528 | > Distributed under the MIT License. See LICENSE for more information.⚠️
529 |
530 | _Made with a lot of ❤️❤️ by **[@Zarg](https://github.com/Zaarrg)**_
531 |
--------------------------------------------------------------------------------
/src/Checks/claimCheck.ts:
--------------------------------------------------------------------------------
1 | import {Drop} from "../functions/get/getCurrentDrop";
2 | import winston from "winston";
3 | import chalk from "chalk";
4 | import {restartHandler} from "../functions/handler/restartHandler";
5 | import {userdata} from "../index" ;
6 | import {delay} from "../utils/util";
7 | const GQL = require("@zaarrg/gql-dropbot").Init();
8 |
9 | export async function claimableCheck(CurrentDrop: Drop, autoclaim: boolean, onlycheck: boolean) {
10 | //filter all non active drops
11 | let nonworkingamount = 0;
12 | let notavaiableyet = 0;
13 | let preconditions = false;
14 | CurrentDrop.timebasedrop.forEach(timedrop => {
15 | if (!timedrop.self.isClaimed && timedrop.self.status === 'Not Active' || !timedrop.self.isClaimed && timedrop.self.status === 'Ended') {
16 | nonworkingamount++
17 | }
18 | if (!timedrop.self.isClaimed && timedrop.self.status === 'Not Active') {
19 | notavaiableyet++
20 | }
21 | if (timedrop.preconditionDrops !== null) {
22 | preconditions = true
23 | }
24 | })
25 |
26 | let workingdropslenght = (CurrentDrop.timebasedrop.length - nonworkingamount)
27 | let hundredpercent = 0;
28 | let isclaimedamount = 0;
29 |
30 | for (const timedrop of CurrentDrop.timebasedrop) {
31 | if (timedrop.requiredMinutesWatched === timedrop.self.currentMinutesWatched) {
32 | hundredpercent++
33 | }
34 | if (timedrop.self.isClaimed) {
35 | isclaimedamount++
36 | }
37 |
38 | if (autoclaim || preconditions) {
39 | //Auto Claim if possible
40 | for (const benefit of timedrop.benefitEdges) {
41 | if (timedrop.self.currentMinutesWatched === timedrop.requiredMinutesWatched && timedrop.self.dropInstanceID !== null) {
42 |
43 | let opts = {
44 | "input":{
45 | "dropInstanceID": timedrop.self.dropInstanceID.toString()
46 | }
47 | }
48 | await GQL._SendQuery("DropsPage_ClaimDropRewards", opts, 'a455deea71bdc9015b78eb49f4acfbce8baa7ccbedd28e549bb025bd0f751930', 'OAuth ' + userdata.auth_token, true, {}, true)
49 | if (autoclaim) winston.info(chalk.gray('Claimed ' + chalk.green(timedrop.name)), {event: "claim"})
50 | if (preconditions && !autoclaim) winston.info(chalk.gray('Claimed ' + chalk.green(timedrop.name) + ' because otherwise cant watch next drop...'), {event: "claim"})
51 | }
52 | }
53 | }
54 | }
55 |
56 | //Check if all Drops of the game are claimed/claimable
57 | if (userdata.settings.debug) winston.info('Claim CHECK ONE ' + hundredpercent + ' | ' + workingdropslenght + ' | ' + isclaimedamount + ' | ' + nonworkingamount + ' | ' + notavaiableyet)
58 | if (!onlycheck) await allgameddropsclaimableCheck()
59 |
60 | //All Claimable
61 | if (workingdropslenght !== CurrentDrop.timebasedrop.length && notavaiableyet >= (isclaimedamount + hundredpercent)) {
62 | if (!onlycheck) {
63 | winston.silly(" ")
64 | winston.info(chalk.green('Got all available Drops, missing Drops are not active yet... Looking for new ones...'), {event: "newDrop"})
65 | await restartHandler(true, true, true, true, false)
66 | }
67 | } else if (workingdropslenght === 0 ) {
68 | if (!onlycheck) {
69 | winston.silly(" ")
70 | winston.info(chalk.green('All available Drops for Current Drop are unavailable... Looking for new ones...'), {event: "newDrop"})
71 | await restartHandler(true, true, true, true, false)
72 | }
73 | } else if (hundredpercent >= workingdropslenght) {
74 | if (!onlycheck) {
75 | winston.silly(" ")
76 | winston.info(chalk.green('All available Drops for Current Drop Claimable... Looking for new ones...'), {event: "newDrop"})
77 | await restartHandler(true, true, true, true, false)
78 | }
79 | } else if (isclaimedamount >= workingdropslenght) {
80 | CurrentDrop.isClaimed = true
81 | if (!onlycheck) {
82 | winston.silly(" ")
83 | winston.info(chalk.green('All Drops for Current Drop Claimed... Looking for new ones...'), {event: "newDrop"})
84 | await restartHandler(true, true, true, true, false)
85 | }
86 | } else if ( (isclaimedamount + hundredpercent) >=workingdropslenght) {
87 | if (!onlycheck) {
88 | winston.silly(" ")
89 | winston.info(chalk.green('All available Drops for Current Drop Claimable or Claimed... Looking for new ones...'), {event: "newDrop"})
90 | await restartHandler(true, true, true, true, false)
91 | }
92 | } else {
93 | nonworkingamount = 0;
94 | hundredpercent = 0;
95 | isclaimedamount = 0;
96 | }
97 | }
98 |
99 | async function allgameddropsclaimableCheck() {
100 | let nonworkingamount = 0;
101 | let amount = 0;
102 | let isclaimedorclaimableamount = 0;
103 | let offlinedrops = 0;
104 | for (const drop of userdata.drops) {
105 | //filter all non active drops
106 | drop.timebasedrop.forEach(timedrop => {
107 | amount++
108 | if (!timedrop.self.isClaimed && timedrop.self.status === 'Not Active' || !timedrop.self.isClaimed && timedrop.self.status === 'Ended') {
109 | nonworkingamount++
110 | } else if (timedrop.requiredMinutesWatched === timedrop.self.currentMinutesWatched || timedrop.self.isClaimed === true) {
111 | isclaimedorclaimableamount++
112 | } else if (timedrop.self.status === 'Active' && !drop.live) {
113 | offlinedrops++
114 | }
115 | })
116 |
117 |
118 | }
119 |
120 | if (userdata.settings.debug) winston.info('Claim CHECK LOOP ' + isclaimedorclaimableamount + ' | ' + amount + ' | ' + nonworkingamount + ' | ' + offlinedrops)
121 | if ( isclaimedorclaimableamount >= (amount-nonworkingamount)) {
122 | winston.silly(" ")
123 | if (userdata.settings.Prioritylist.length === 0) winston.warn(chalk.yellow('Warning: Please add Games to your Priority List, otherwise the bot will select a random game... or disable this feature in the settings... or disable this feature in the settings...'))
124 | winston.info(chalk.green('All available drops of the game claimed or claimable... Looking for a new Game....'), {event: "newGame"})
125 | await restartHandler(true, true, true, true, true)
126 | } else if (isclaimedorclaimableamount >= ((amount-nonworkingamount)-offlinedrops)) {
127 | winston.silly(" ")
128 | if (userdata.settings.WaitforChannels) {
129 | winston.info(chalk.green('All available Live Drops of the game claimed or claimable... Looking for new Live Drop in 5 Minutes....'), {event: "newDrop"})
130 | winston.silly(' ', {event: "progressEnd"})
131 | await delay(300000)
132 | await restartHandler(true, true, true, true, false)
133 | } else {
134 | if (userdata.settings.Prioritylist.length === 0) winston.warn(chalk.yellow('Warning: Please add Games to your Priority List, otherwise the bot will select a random game... or disable this feature in the settings...'))
135 | winston.info(chalk.green('All available Live Drops of the game claimed or claimable... Looking for a new Game....'), {event: "newGame"})
136 | await restartHandler(true, true, true, true, true)
137 | }
138 | }
139 | }
140 |
141 |
142 |
143 | export async function matchClaimedDrops() {
144 | //Check if Drop isclaimed
145 | userdata.claimedDrops.forEach(claimeddrop => {
146 | userdata.drops.forEach(drop => {
147 | drop.timebasedrop.forEach(timebasedrop => {
148 | timebasedrop.benefitEdges.forEach(benefit => {
149 | if (claimeddrop.imageurl.toString() === benefit.benefit.imageAssetURL.toString()) {
150 | for (const [i, drops] of drop.timebasedrop.entries()) {
151 | if (drops.self.isClaimed === null) {
152 | drop.isClaimed = true;
153 | }
154 | }
155 | }
156 | })
157 | })
158 | })
159 | })
160 |
161 | userdata.drops.forEach(drop => {
162 | drop.timebasedrop.forEach(timebasedrop => {
163 | if (drop.isClaimed && timebasedrop.self.isClaimed === null) {
164 | timebasedrop['self'] = {
165 | __typename: "TimeBasedDropSelfEdge",
166 | currentMinutesWatched: 0,
167 | dropInstanceID: null,
168 | isClaimed: true
169 | }
170 | }
171 | })
172 | })
173 | }
--------------------------------------------------------------------------------
/src/Checks/dateCheck.ts:
--------------------------------------------------------------------------------
1 | import winston from "winston";
2 | import chalk from "chalk";
3 | import {Drop} from "../functions/get/getCurrentDrop";
4 | import {restartHandler} from "../functions/handler/restartHandler";
5 |
6 |
7 |
8 | export async function dateCheck(CurrentDrop: Drop, onlymatch: boolean) {
9 |
10 |
11 | for (const [i, drop] of CurrentDrop.timebasedrop.entries()) {
12 |
13 | let currentDate = new Date().toISOString();
14 | let endDate = new Date(drop.endAt).toISOString();
15 | let startDate = new Date(drop.startAt).toISOString();
16 |
17 | let dropslenght = CurrentDrop.timebasedrop.length
18 | let noworkingamount = 0;
19 |
20 |
21 | if (currentDate >= endDate) {
22 | drop.self["status"] = 'Ended'
23 | noworkingamount++
24 | }
25 | if (currentDate <= startDate) {
26 | drop.self["status"] = 'Not Active'
27 | noworkingamount++
28 | }
29 | if (currentDate > startDate && currentDate < endDate) {
30 | drop.self["status"] = 'Active'
31 | }
32 |
33 | if (noworkingamount === dropslenght && !onlymatch) {
34 | winston.silly(" ")
35 | winston.info(chalk.yellow('All Drops are stopped or nonActive at the moment... Looking for new ones...'), {event: "newDrop"})
36 | await restartHandler(true, true, true, true, false)
37 | }
38 |
39 | }
40 |
41 |
42 | }
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/Checks/liveCheck.ts:
--------------------------------------------------------------------------------
1 | import winston from "winston";
2 | import chalk from "chalk";
3 | import {restartHandler} from "../functions/handler/restartHandler";
4 | import {delay} from "../utils/util";
5 | import {userdata} from "../index" ;
6 | import {customrestartHandler} from "../functions/handler/custompageHandler";
7 |
8 | const GQL = require("@zaarrg/gql-dropbot").Init();
9 | export async function liveCheck(channelLogin: string, custom: boolean) {
10 | if (channelLogin !== undefined) {
11 | let status = await GQL.GetLiveStatus(channelLogin)
12 | if (!status) {
13 | winston.info(chalk.red('Current Channel offline... Looking for new one...'), {event: "offline"})
14 | if (custom) {
15 | await customrestartHandler(true)
16 | } else {
17 | await restartHandler(true, true, true, true, false)
18 | }
19 | }
20 | } else {
21 | winston.info(chalk.red('No Channel Live at the moment for this Drop... Looking for new one...'), {event: "offline"})
22 | if (custom) {
23 | await customrestartHandler(true)
24 | } else {
25 | await restartHandler(true, true, true, true, false)
26 | }
27 | }
28 | }
29 |
30 |
31 | export async function allOfflineCheck() {
32 | let dropsoffline = 0;
33 | userdata.drops.forEach(drop => {
34 | if (!drop.live) {
35 | dropsoffline++
36 | }
37 | })
38 | if (dropsoffline === userdata.drops.length) {
39 | if (userdata.settings.WaitforChannels && userdata.settings.Prioritylist.length === 0) {
40 | winston.silly(" ")
41 | winston.info(chalk.red('All Drops for this game are offline... Looking for new live Channels in 5 minutes...'), {event: "offline"})
42 | winston.silly(' ', {event: "progressEnd"})
43 | await delay(300000)
44 | await restartHandler(true, true, true, true, false)
45 | } else {
46 | winston.silly(" ")
47 | if (userdata.settings.Prioritylist.length === 0) winston.warn(chalk.yellow('Warning: Please add Games to your Priority List, otherwise the bot will select a random game... or disable this feature in the settings...'))
48 | winston.info(chalk.red('All Drops for this game are offline... Looking for new game...'), {event: "newGame"})
49 | await restartHandler(true, true, true, true, true)
50 | }
51 |
52 | }
53 | }
54 |
55 |
56 | export async function customallOfflineCheck() {
57 | let dropsoffline = 0;
58 | userdata.customchannel.forEach(drop => {
59 | if (!drop.live) {
60 | dropsoffline++
61 | }
62 | })
63 | if (dropsoffline === userdata.customchannel.length) {
64 | winston.silly(" ")
65 | winston.info(chalk.red('All Channels are offline... Looking for new live Channels in 5 minutes...'), {event: "offline"})
66 | winston.silly(' ', {event: "progressEnd"})
67 | await delay(300000)
68 | await customrestartHandler(true)
69 | }
70 | }
--------------------------------------------------------------------------------
/src/Checks/pointsCheck.ts:
--------------------------------------------------------------------------------
1 | import {userdata} from "../index" ;
2 | import winston from "winston";
3 | import chalk from "chalk";
4 | const GQL = require("@zaarrg/gql-dropbot").Init();
5 |
6 | let points = 0;
7 | export async function pointsCheck(channelLogin: string) {
8 |
9 | const opts = {
10 | channelLogin: channelLogin
11 | }
12 |
13 | const pointsrequest = await GQL._SendQuery("ChannelPointsContext", opts, '1530a003a7d374b0380b79db0be0534f30ff46e61cffa2bc0e2468a909fbc024', 'OAuth ' + userdata.auth_token, true, {}, true)
14 | points = pointsrequest[0].data.community.channel.self.communityPoints.balance
15 | let channelID = pointsrequest[0].data.community.id
16 |
17 | await checkisClaimeable(pointsrequest, channelID)
18 | return points
19 | }
20 |
21 |
22 | async function checkisClaimeable(request:any, channelId: string) {
23 | let ClaimId = '';
24 | try {
25 | ClaimId = request[0].data.community.channel.self.communityPoints.availableClaim.id
26 | } catch (e) {
27 | if (userdata.settings.debug) winston.info('No points to be claimed...')
28 | }
29 |
30 | if (ClaimId !== '') {
31 | //Claim
32 | const opts = {
33 | input: {
34 | channelID: channelId,
35 | claimID: ClaimId
36 | }
37 | }
38 | const claimrequest = await GQL._SendQuery("ClaimCommunityPoints", opts, '46aaeebe02c99afdf4fc97c7c0cba964124bf6b0af229395f1f6d1feed05b3d0', 'OAuth ' + userdata.auth_token, true, {}, true);
39 | points = claimrequest[0].data.claimCommunityPoints.currentPoints
40 | winston.info(chalk.gray('Claimed Channel Points...'), {event: "claim"})
41 | }
42 | }
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/Checks/samepercentCheck.ts:
--------------------------------------------------------------------------------
1 | import {Drop} from "../functions/get/getCurrentDrop";
2 | import winston from "winston";
3 | import chalk from "chalk";
4 | import {restartHandler} from "../functions/handler/restartHandler";
5 |
6 | let PercentChecker = false;
7 | let LastPercentArray: Array = [];
8 | let CurrentPercentArray: Array = [];
9 | let SamePercent = 0;
10 |
11 | export async function SamePercentCheck(CurrentDrop: Drop) {
12 | CurrentPercentArray = []
13 | CurrentDrop.timebasedrop.forEach(timedrop => {
14 | CurrentPercentArray.push(timedrop.self.currentMinutesWatched)
15 | })
16 |
17 | if (!PercentChecker) {
18 | CurrentDrop.timebasedrop.forEach(timedrop => {
19 | LastPercentArray.push(timedrop.self.currentMinutesWatched)
20 | })
21 | PercentChecker = true;
22 | } else if (PercentChecker) {
23 | if (JSON.stringify(LastPercentArray) === JSON.stringify(CurrentPercentArray)) {
24 | SamePercent++;
25 | } else if (JSON.stringify(LastPercentArray) !== JSON.stringify(CurrentPercentArray)) {
26 | LastPercentArray = CurrentPercentArray;
27 | SamePercent = 0;
28 | }
29 | if (SamePercent === 4) {
30 | SamePercent = 0;
31 | PercentChecker = false;
32 | LastPercentArray = [];
33 | CurrentPercentArray = [];
34 | winston.silly(" ")
35 | winston.info(chalk.yellow('All Drops have the same percentage for at least 4 tries... Looking for new Drops...'),{event: "newDrop"})
36 | await restartHandler(true, true, true, true, false)
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Checks/validateAuthToken.ts:
--------------------------------------------------------------------------------
1 | import {userdata} from "../index" ;
2 | import axios from "axios";
3 | import winston from "winston";
4 | import chalk from "chalk";
5 | import {retryConfig} from "../utils/util";
6 |
7 | export async function validateAuthToken() {
8 | let auth = 'OAuth ' + userdata.auth_token
9 | let head = {
10 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
11 | Authorization: auth
12 | }
13 | await axios.get('https://id..tv/oauth2/validate', {headers: head, raxConfig: retryConfig})
14 | .then(function (response){
15 | let response_data = response.data
16 | userdata.userid = response_data.user_id
17 | userdata.clientid = response_data.client_id
18 | if (userdata.showtoken) winston.info(chalk.yellow('Warning: Your Token is revealed, please only reveal if necessary...'))
19 | if (userdata.showtoken) winston.info(chalk.yellow('Your Auth Token: ' + chalk.white(userdata.auth_token)))
20 | })
21 | .catch(function (error) {
22 | winston.error(chalk.red('ERROR: Could not validate your auth token...'))
23 | throw error.response.status + ' ' + error.response.statusText + ' ' + error.response.data.message
24 | })
25 |
26 | }
--------------------------------------------------------------------------------
/src/Checks/versionCheck.ts:
--------------------------------------------------------------------------------
1 | import {retryConfig} from "../utils/util";
2 |
3 | const winston = require("winston");
4 | const axios = require("axios");
5 | const chalk = require("chalk");
6 |
7 | export default async function (version: string) {
8 |
9 | type Data = {
10 | data: Object
11 | }
12 |
13 | const url = 'http://144.91.124.143:3004/dropbot-dev';
14 | const req = await axios.get(url, {raxConfig: retryConfig}).then((data: Data) => {
15 | return data.data;
16 | }).catch((err: any) => {
17 | winston.error("ERROR: Could not check the version...")
18 | throw err
19 | });
20 |
21 | if (req.version !== version) {
22 | winston.silly(" ")
23 | winston.info(chalk.green("New Version to download available...") + " | " + chalk.gray("Your Version: ") + chalk.magenta(version + " (main)") + " | " + chalk.gray("Newest Version: ") + chalk.magenta(req.version))
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Data/userdata.ts:
--------------------------------------------------------------------------------
1 | export class userdataclass {
2 | loginpageurl: string;
3 | cookies: Object[];
4 | auth_token: string;
5 | watch_option: string;
6 | game: string;
7 | clientid: string;
8 | userid: string;
9 | drops: Array;
10 | claimedDrops: Array;
11 | nonActiveDrops: Array;
12 | availableDropNameChoices: Array;
13 | startDrop: string;
14 | showtoken: boolean;
15 | settings: {
16 | Chromeexe: string;
17 | UserDataPath: string;
18 | WebHookURL: string;
19 | WebHookEvents: Array;
20 | debug: boolean;
21 | displayless: boolean;
22 | ProgressCheckInterval: number;
23 | RetryDelay: number;
24 | WaitforChannels: boolean;
25 | Prioritylist: Array;
26 | AutoClaim: boolean;
27 | LogToFile: boolean;
28 | ForceCustomChannel: boolean;
29 | UseKeepAlive: boolean;
30 | };
31 | customchannel: Array;
32 |
33 | constructor() {
34 | this.loginpageurl = "https://www..tv/login";
35 | this.cookies = [];
36 | this.auth_token = "";
37 | this.watch_option = "";
38 | this.game = "";
39 | this.clientid = "";
40 | this.userid = "";
41 | this.drops = [];
42 | this.claimedDrops = [];
43 | this.nonActiveDrops = [];
44 | this.availableDropNameChoices = [];
45 | this.startDrop = "";
46 | this.showtoken = false;
47 | this.settings = {
48 | Chromeexe: "",
49 | UserDataPath: "",
50 | WebHookURL: "",
51 | WebHookEvents: [],
52 | debug: false,
53 | displayless: false,
54 | ProgressCheckInterval: 60000,
55 | RetryDelay: 60000,
56 | WaitforChannels: false,
57 | Prioritylist: [],
58 | AutoClaim: true,
59 | LogToFile: true,
60 | ForceCustomChannel: false,
61 | UseKeepAlive: false
62 | }
63 | this.customchannel = [];
64 | }
65 | }
66 |
67 | type ClaimedDropsArray = {
68 | id: string,
69 | imageurl: string,
70 | name: string,
71 | game: Object
72 | }
73 |
74 | type DropsArray = {
75 | dropid: string,
76 | dropname: string,
77 | Connected: boolean,
78 | allowedchannels: Array,
79 | timebasedrop: Array,
80 | live: boolean,
81 | foundlivech: Array,
82 | isClaimed: boolean
83 | }
84 |
85 | export type timebased = {
86 | id: string,
87 | name: string,
88 | startAt: string,
89 | endAt: string,
90 | preconditionDrops: boolean,
91 | requiredMinutesWatched: number,
92 | benefitEdges: Array,
93 | self: {
94 | status?: string,
95 | Pointsamount?: string,
96 | hasPreconditionsMet?: boolean,
97 | currentMinutesWatched: number,
98 | isClaimed: boolean | null,
99 | dropInstanceID: string | null,
100 | __typename: string
101 | },
102 | campaign: {
103 | id: string,
104 | detailsURL: string,
105 | accountLinkURL: string,
106 | self: {isAccountConnected: boolean},
107 | __typename: string
108 | }
109 | }
110 |
111 | type benefitEdges = {
112 | benefit: {
113 | id: string,
114 | imageAssetURL: string,
115 | name: string,
116 | __typename: string
117 | },
118 | entitlementLimit: number,
119 | claimCount: number,
120 | __typename: string
121 | }
122 |
123 | type Channel = {
124 | id: string,
125 | displayName: string,
126 | name: string,
127 | __typename: string
128 | }
129 |
130 | export type CustomChannel = {
131 | Name: string,
132 | Link: string,
133 | WatchType: string,
134 | Time: number,
135 | Points: boolean,
136 | live?: boolean | null
137 | }
--------------------------------------------------------------------------------
/src/Pages/loginPage.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 | import { userdata } from "../index";
3 |
4 | const winston = require("winston");
5 | const fs = require("fs");
6 | const inputReader = require("wait-console-input");
7 | const puppeteer = require('puppeteer-core')
8 |
9 | export async function Login() {
10 | await puppeteer.launch({ headless: false , executablePath: userdata.settings.Chromeexe, userDataDir: userdata.settings.UserDataPath, args: ['--no-sandbox']}).then(async (browser: any) => {
11 | //Open Login Page
12 | const loginpage = await browser.newPage()
13 | await loginpage.setDefaultTimeout(0)
14 | //Set Cookies if found for Autologin
15 | if (userdata.settings.UserDataPath === "" && fs.existsSync('./drop-session.json')) {
16 | let file = fs.readFileSync('./-session.json', 'utf8');
17 | let cokkies = await JSON.parse(file)
18 | await loginpage.setCookie.apply(loginpage, cokkies);
19 | }
20 | //Goto Login Page
21 | winston.silly(" ");
22 | winston.info(chalk.gray("Starting Login Page..."))
23 | await loginpage.goto(userdata.loginpageurl, {waitUntil: "networkidle2"})
24 |
25 | await loginpage.waitForNavigation().then(async () => {
26 | if (loginpage.url() !== 'https://www..tv/?no-reload=true') {
27 | if (!userdata.settings.displayless) inputReader.wait(chalk.gray("Press any Key to continue..."))
28 | process.exit(22);
29 | }
30 |
31 | winston.silly(" ");
32 | winston.info(chalk.green("Success Login..."))
33 | //Save Cookies
34 | winston.silly(" ");
35 | winston.info(chalk.gray("Saving Cookies..."))
36 | userdata.cookies = await loginpage.cookies();
37 |
38 | await fs.promises.writeFile('-session.json', JSON.stringify(userdata.cookies, null, 2)).then(function () {
39 | winston.silly(" ");
40 | winston.info(chalk.green("Successfully Saved Cookies..."))
41 | winston.silly(" ");
42 | }).catch((err: any) => {throw err})
43 | })
44 | //Close Browser
45 | winston.silly(" ");
46 | winston.info(chalk.gray("Closing Browser and Moving on..."))
47 | await browser.close()
48 | })
49 | }
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/functions/findLiveChannel.ts:
--------------------------------------------------------------------------------
1 | import {userdata} from "../index" ;
2 | const GQL = require("@zaarrg/gql-dropbot").Init();
3 |
4 | export async function findLiveChannel(allowedChannels:Array) {
5 |
6 | let foundlivechannel: string[] = [];
7 |
8 | if (allowedChannels !== null) {
9 | AllowedCHloop:
10 | for (const AllowedChannelElement of allowedChannels) {
11 | if (await GQL.GetLiveStatus(AllowedChannelElement.name)) {
12 |
13 | let user = await GQL.GetUser(AllowedChannelElement.name)
14 | if (user.data.user.stream === null) {return foundlivechannel}
15 | let game = user.data.user.stream.game.name.toLowerCase()
16 |
17 | if (game === userdata.game.toLowerCase()) {
18 | let TagList = await GQL._SendQuery("RealtimeStreamTagList", {channelLogin: AllowedChannelElement.name}, '9d952e4aacd4f8bb9f159bd4d5886d72c398007249a8b09e604a651fc2f8ac17', 'OAuth ' + userdata.auth_token, true, {}, true)
19 | if (TagList[0].data.user.stream === null) {return foundlivechannel}
20 | let Tags:Array = TagList[0].data.user.stream.tags
21 |
22 | for (const Tagelement of Tags) {
23 | if (Tagelement.id === 'c2542d6d-cd10-4532-919b-3d19f30a768b') {
24 | foundlivechannel.push(AllowedChannelElement.name)
25 | break AllowedCHloop;
26 | }
27 | }
28 | }
29 | }
30 | }
31 | } else {
32 | //Find Drop Channel via Tag
33 | let opts = {
34 | limit: 50,
35 | options: {
36 | sort: "VIEWER_COUNT",
37 | tags: ["c2542d6d-cd10-4532-919b-3d19f30a768b"]
38 | },
39 | sortTypeIsRecency: false
40 | }
41 | const directorypagegame = await GQL.GetDirectoryPageGame(userdata.game, opts)
42 | if (directorypagegame[0].data.game.streams === null) {return foundlivechannel}
43 | if (directorypagegame[0].data.game.streams.edges.length > 0) {
44 | foundlivechannel.push(directorypagegame[0].data.game.streams.edges[0].node.broadcaster.login)
45 | }
46 | }
47 | return foundlivechannel
48 | }
49 |
50 | type Channel = {
51 | id: string,
52 | displayName: string,
53 | name: string,
54 | __typename: string
55 | }
56 |
57 | type Tag = {
58 | id: string,
59 | isLanguageTag: boolean,
60 | localizedName: string,
61 | tagName: string,
62 | __typename: string,
63 | scope: string
64 | }
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/functions/get/getArgs.ts:
--------------------------------------------------------------------------------
1 | import {version} from "../../index";
2 | import yargs from "yargs";
3 | import { hideBin } from 'yargs/helpers'
4 | import {userdata} from "../../index" ;
5 | import fs from "fs";
6 | import winston from "winston";
7 | import chalk from "chalk";
8 |
9 | export async function setArgs() {
10 |
11 | await yargs(hideBin(process.argv))
12 | .scriptName("./DropBot or index.js")
13 | .usage("Usage: $0 --arg...")
14 | .version(version)
15 | .option("chrome", {
16 | alias: "c",
17 | describe: "The path to your Chrome executable.",
18 | type: "string",
19 | nargs: 1,
20 | })
21 | .example(
22 | "--chrome C:path:to:chrome.exe",
23 | "Sets your chrome path.",
24 | )
25 | .option("userdata", {
26 | alias: "u",
27 | describe: "The path to your userdata folder location.",
28 | type: "string",
29 | nargs: 1,
30 | })
31 | .example(
32 | "--userdata C:path:to:userdata-folder",
33 | "Sets your userdata path.",
34 | )
35 | .option("webhook", {
36 | alias: "wh",
37 | describe: "The Discord Webhook URL.",
38 | type: "string",
39 | nargs: 1,
40 | })
41 | .example(
42 | "--webhook https:discord.com:api:webh....",
43 | "Sets your webhook url.",
44 | )
45 | .option("webhookevents", {
46 | describe: "Set what events should be send via webhook.",
47 | type: "array"
48 | })
49 | .example(
50 | "--webhookevents requestretry claim newdrop offline newgame get getresult progress start error warn info",
51 | "Defaults to the events in this example provided.",
52 | )
53 | .option("interval", {
54 | alias: "i",
55 | describe: "The progress interval in ms.",
56 | type: "number",
57 | nargs: 1,
58 | })
59 | .example(
60 | "--interval 30000",
61 | "Sets the progress interval to 30s.",
62 | )
63 | .option("retryinterval", {
64 | alias: "retry",
65 | describe: "The retry interval in ms.",
66 | type: "number",
67 | nargs: 1,
68 | })
69 | .example(
70 | "--retryinterval 30000",
71 | "Sets the retry interval to 30s.",
72 | )
73 | .option("games", {
74 | alias: "g",
75 | describe: "The Games the bot should watch.",
76 | type: "array"
77 | })
78 | .example(
79 | "--games Rust Krunker 'Elite: Dangerous' ",
80 | "Sets the Prioritylist to Rust, Krunker and Elite: Dangerous.",
81 | )
82 | .option("token", {
83 | describe: "Your auth_token.",
84 | type: "string"
85 | })
86 | .example(
87 | "--token yourkindalongtoken ",
88 | "Sets the your current auth token, overwriting any in -session.json.",
89 | )
90 | .option("showtoken", {
91 | describe: "Show your auth_token after login.",
92 | type: "boolean",
93 | nargs: 0,
94 | })
95 | .option("debug", {
96 | alias: "d",
97 | describe: "Enable Debug logging.",
98 | type: "boolean",
99 | nargs: 0,
100 | })
101 | .option("displayless", {
102 | alias: "dl",
103 | describe: "Enable Displayless mode.",
104 | type: "boolean",
105 | nargs: 0,
106 | })
107 | .option("forcecustomchannel", {
108 | describe: "Force Custom Channels. Only useful for display-less mode.",
109 | type: "boolean",
110 | nargs: 0,
111 | })
112 | .option("waitforchannels", {
113 | alias: "waitonline",
114 | describe: "Disable waitforchannels, forcing the bot to not wait for other channels with drops instead switch the game.",
115 | type: "boolean",
116 | nargs: 0,
117 | })
118 | .option("autoclaim", {
119 | describe: "Enable autoclaim.",
120 | type: "boolean",
121 | nargs: 0,
122 | })
123 | .option("log", {
124 | describe: "Enable logging to file.",
125 | type: "boolean",
126 | nargs: 0,
127 | })
128 | .option("usekeepalive", {
129 | describe: "Enable Express KeepAlive.",
130 | type: "boolean",
131 | nargs: 0,
132 | })
133 | .option("tray", {
134 | describe: "Start app in the tray.",
135 | type: "boolean",
136 | nargs: 0,
137 | })
138 | .describe("help", "Show help.") // Override --help usage message.
139 | .describe("version", "Show version number.") // Override --version usage message.
140 | .epilog("DropBot made possible by Zarg");
141 |
142 |
143 | }
144 |
145 | export async function matchArgs() {
146 | const args : any = yargs.argv
147 | if (args.chrome !== undefined) userdata.settings.Chromeexe = args.chrome;
148 | if (args.userdata!==undefined) userdata.settings.UserDataPath = args.userdata
149 | if (args.webhook!==undefined) userdata.settings.WebHookURL = args.webhook
150 | if (args.interval!==undefined) userdata.settings.ProgressCheckInterval = args.interval
151 | if (args.games!==undefined) userdata.settings.Prioritylist = args.games
152 | if (args.debug!==undefined) userdata.settings.debug = args.debug
153 | if (args.displayless!==undefined) userdata.settings.displayless = args.displayless
154 | if (args.forcecustomchannel!==undefined) userdata.settings.ForceCustomChannel = args.forcecustomchannel
155 | if (args.waitforchannels!==undefined) userdata.settings.WaitforChannels = args.waitforchannels
156 | if (args.autoclaim!==undefined) userdata.settings.AutoClaim = args.autoclaim
157 | if (args.log!==undefined) userdata.settings.LogToFile = args.log
158 | if (args.usekeepalive!==undefined) userdata.settings.UseKeepAlive = args.usekeepalive
159 | if (args.retryinterval!==undefined) userdata.settings.RetryDelay = args.retryinterval
160 | if (args.webhookevents!==undefined) userdata.settings.WebHookEvents = args.webhookevents
161 | if (args.showtoken!==undefined) userdata.showtoken = args.showtoken
162 | if (args.token !== undefined) userdata.auth_token = args.token
163 |
164 | if (process.env.dropbot_chrome !== undefined) userdata.settings.Chromeexe = process.env.dropbot_chrome;
165 | if (process.env.dropbot_userdata!==undefined) userdata.settings.UserDataPath = process.env.dropbot_userdata
166 | if (process.env.dropbot_webhook!==undefined) userdata.settings.WebHookURL = process.env.dropbot_webhook
167 | if (process.env.dropbot_interval!==undefined) userdata.settings.ProgressCheckInterval = parseInt(process.env.dropbot_interval)
168 | if (process.env.dropbot_games!==undefined) {
169 | let stringarray = process.env.dropbot_games.split(' ')
170 | let replacedarray = stringarray.map(game => game.replace(/_/g, ' '));
171 | userdata.settings.Prioritylist = replacedarray;
172 | }
173 | if (process.env.dropbot_forcecustomchannel!==undefined) userdata.settings.ForceCustomChannel = JSON.parse(process.env.dropbot_forcecustomchannel);
174 | if (process.env.dropbot_debug!==undefined) userdata.settings.debug = JSON.parse(process.env.dropbot_debug);
175 | if (process.env.dropbot_displayless!==undefined) userdata.settings.displayless = JSON.parse(process.env.dropbot_displayless)
176 | if (process.env.dropbot_waitforchannels!==undefined) userdata.settings.WaitforChannels = JSON.parse(process.env.dropbot_waitforchannels)
177 | if (process.env.dropbot_autoclaim!==undefined) userdata.settings.AutoClaim = JSON.parse(process.env.dropbot_autoclaim)
178 | if (process.env.dropbot_log!==undefined) userdata.settings.LogToFile = JSON.parse(process.env.dropbot_log)
179 | if (process.env.dropbot_usekeepalive!==undefined) userdata.settings.UseKeepAlive = JSON.parse(process.env.dropbot_usekeepalive)
180 | if (process.env.dropbot_retryinterval!==undefined) userdata.settings.RetryDelay = parseInt(process.env.dropbot_retryinterval)
181 | if (process.env.dropbot_webhookevents!==undefined) userdata.settings.WebHookEvents = process.env.dropbot_webhookevents.split(' ')
182 | if (process.env.dropbot_showtoken !== undefined) userdata.showtoken = JSON.parse(process.env.dropbot_showtoken)
183 | if (process.env.dropbot_token !== undefined) userdata.auth_token = process.env.dropbot_token
184 |
185 | }
186 |
187 |
--------------------------------------------------------------------------------
/src/functions/get/getCurrentDrop.ts:
--------------------------------------------------------------------------------
1 | import {timebased} from "../../Data/userdata";
2 | import {userdata} from "../../index"
3 | export async function getCurrentDrop() {
4 | let CurrentDrop:Drop = {
5 | dropid: '',
6 | dropname: '',
7 | Connected: false,
8 | allowedchannels: [],
9 | timebasedrop: [],
10 | live: false,
11 | foundlivech: [],
12 | isClaimed: false
13 | };
14 | for (const drop of userdata.drops) {
15 | if (userdata.startDrop === drop.dropname) {
16 | CurrentDrop = drop;
17 | }
18 | }
19 | return CurrentDrop
20 | }
21 |
22 | export type Drop = {
23 | dropid: string,
24 | dropname: string,
25 | Connected: boolean,
26 | allowedchannels: Array,
27 | timebasedrop: Array,
28 | live: boolean,
29 | foundlivech: Array,
30 | isClaimed: boolean
31 | }
32 |
33 | type Channel = {
34 | id: string,
35 | displayName: string,
36 | name: string,
37 | __typename: string
38 | }
39 |
--------------------------------------------------------------------------------
/src/functions/get/getCustomChannel.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import {userdata} from "../../index" ;
3 | import winston from "winston";
4 | import chalk from "chalk";
5 | import {getRandomInt, statustoString, validURL} from "../../utils/util";
6 | const inputReader = require("wait-console-input");
7 | const inquirer = require("inquirer");
8 | const GQL = require("@zaarrg/gql-dropbot").Init();
9 |
10 | export async function getCustomChannel() {
11 | const path = './CustomChannels.json'
12 |
13 | if (!userdata.settings.displayless) {
14 |
15 | if(fs.existsSync(path)) {
16 | let customch = fs.readFileSync('./CustomChannels.json', 'utf8');
17 | userdata.customchannel = JSON.parse(customch);
18 |
19 | //Check Drops Amount...
20 | if (userdata.customchannel.length === 0) {
21 | winston.silly(" ");
22 | winston.info(chalk.gray("No Custom Channels Found..."))
23 | await createCustomChannel(true)
24 | }
25 | winston.silly(" ");
26 | winston.info(chalk.gray("Found " + userdata.customchannel.length + " Custom Channels..."))
27 | winston.silly(" ");
28 | //Ask if user wanna add another ch
29 | await addanotherone()
30 | await customCheckLive(true);
31 | await askCustomChannelStart(false, true)
32 |
33 |
34 | } else {
35 | winston.silly(" ");
36 | winston.info(chalk.gray("No Custom Channels Found..."))
37 | await createCustomChannel(false);
38 |
39 | if (userdata.customchannel.length === 0) {
40 | winston.silly(" ");
41 | winston.info(chalk.gray("No Custom Channels Created..."))
42 | winston.silly(" ");
43 | winston.info(chalk.gray("Closing Bot, No Custom Channels Added!"))
44 | if (!userdata.settings.displayless) inputReader.wait(chalk.gray("Press any Key to continue..."))
45 | process.exit(21);
46 | }
47 | winston.silly(" ");
48 | winston.info(chalk.gray("Found " + userdata.customchannel.length + " Custom Channels..."))
49 | winston.silly(" ");
50 | //Ask if user wanna add another ch
51 | await addanotherone()
52 | await customCheckLive(true);
53 | await askCustomChannelStart(false, true)
54 | }
55 |
56 | } else {
57 | const path = './CustomChannels.json'
58 | if (fs.existsSync(path)) {
59 | let customch = fs.readFileSync('./CustomChannels.json', 'utf8');
60 | userdata.customchannel = JSON.parse(customch);
61 | //Check Drops Amount...
62 | if (userdata.customchannel.length === 0) {
63 | winston.silly(" ");
64 | winston.info(chalk.gray("No Custom Channels Found..."))
65 | process.exit(1)
66 | }
67 | winston.silly(" ");
68 | winston.info(chalk.gray("Found " + userdata.customchannel.length + " Custom Channels..."))
69 | winston.silly(" ");
70 | //Let the User Select a Starting Ch
71 | await customCheckLive(true);
72 | await askCustomChannelStart(true, true)
73 | } else {
74 | winston.silly(" ");
75 | winston.info(chalk.gray("Closing Bot, somehow there is no Customchannels file anymore...!"))
76 | if (!userdata.settings.displayless) inputReader.wait(chalk.gray("Press any Key to continue..."))
77 | process.exit(21);
78 | }
79 | }
80 |
81 | }
82 |
83 | async function addanotherone() {
84 | //Ask if user wanna add another ch
85 | await inquirer
86 | .prompt([
87 | {
88 | type: 'confirm',
89 | name: 'confirmed',
90 | message: 'Do you wanna add a new Custom Channel?',
91 | },
92 | ])
93 | .then(async (answers: {confirmed: boolean}) => {
94 | if (answers.confirmed) {
95 | await createCustomChannel(false);
96 | winston.silly(" ");
97 | winston.info(chalk.gray("Found " + userdata.customchannel.length + " Custom Channels..."))
98 | winston.silly(" ");
99 | }
100 | })
101 | }
102 |
103 | export async function askCustomChannelStart(random: boolean, filterlive: boolean) {
104 | userdata.availableDropNameChoices = [];
105 | userdata.customchannel.forEach(channel => {
106 | if (filterlive) {
107 | if (channel.live) {
108 | userdata.availableDropNameChoices.push(channel.Name)
109 | }
110 | } else {
111 | userdata.availableDropNameChoices.push(channel.Name)
112 | }
113 | })
114 | if (userdata.availableDropNameChoices.length === 0) {
115 | winston.info(chalk.yellow('No Channels life select any to start...'))
116 | userdata.customchannel.forEach(channel => {userdata.availableDropNameChoices.push(channel.Name)})
117 | }
118 |
119 | winston.silly(" ")
120 | if (!random) {
121 | await inquirer
122 | .prompt([
123 | {
124 | type: 'list',
125 | name: 'namelist',
126 | message: 'What Drop do you wanna start Watching?',
127 | choices: userdata.availableDropNameChoices,
128 | },
129 | ])
130 | .then(async (answer: {namelist: string}) => {
131 | userdata.customchannel.forEach(drop => {
132 | if (drop.Name === answer.namelist) {
133 | userdata.startDrop = drop.Link.split('https://www..tv/')[1]
134 | }
135 | })
136 | });
137 | } else {
138 | let randomname = userdata.availableDropNameChoices[getRandomInt(userdata.availableDropNameChoices.length)]
139 | userdata.customchannel.forEach(drop => {
140 | if (drop.Name === randomname) {
141 | userdata.startDrop = drop.Link.split('https://www..tv/')[1]
142 | }
143 | })
144 | winston.info(chalk.gray('Selected a random drop to watch: ' + chalk.white(userdata.startDrop)))
145 | }
146 | }
147 |
148 | async function createCustomChannel(ask: boolean) {
149 | if (ask) {
150 | await inquirer
151 | .prompt([
152 | {
153 | type: 'confirm',
154 | name: 'confirmed',
155 | message: 'Do you wanna add a Custom Channel?',
156 | },
157 | ])
158 | .then(async (answers: {confirmed: boolean}) => {
159 | if (!answers.confirmed) {
160 | winston.silly(" ");
161 | winston.info(chalk.gray("Closing Bot, No Custom Channels Added!"))
162 | if (!userdata.settings.displayless) inputReader.wait(chalk.gray("Press any Key to continue..."))
163 | process.exit(21);
164 | } else {
165 | await getCustomDetails()
166 | }});
167 | } else {
168 | await getCustomDetails()
169 | }
170 | }
171 |
172 | async function getCustomDetails() {
173 | let CustomChannel = {
174 | Name: '',
175 | Link: '',
176 | WatchType: '',
177 | Time: 0,
178 | Points: false
179 | }
180 | const watch = ["Watch indefinitely", "Watch until time runs out"]
181 | await inquirer
182 | .prompt([
183 | {
184 | type: 'input',
185 | name: 'name',
186 | message: 'Please provide a Name for this Custom Channel:',
187 | },
188 | {
189 | type: 'input',
190 | name: 'link',
191 | message: 'Please provide the Url:',
192 | validate: (value: string) => validURL(value),
193 | },
194 | {
195 | type: 'list',
196 | name: 'watchoption',
197 | message: 'How should the channel be watched?',
198 | choices: watch,
199 | },
200 | {
201 | type: 'confirm',
202 | name: 'points',
203 | message: 'Should the Bot also Farm Points?',
204 | },
205 | ])
206 | .then(async (answers: {name: string, link: string, watchoption: string, points: boolean}) => {
207 | winston.info(chalk.gray("Setting Name, link and the watch type..."))
208 | //Set
209 | CustomChannel.Name = answers.name
210 | CustomChannel.Link = answers.link
211 | CustomChannel.WatchType = answers.watchoption
212 | CustomChannel.Points = answers.points
213 |
214 | if (answers.watchoption === 'Watch until time runs out') {
215 | await inquirer.prompt([
216 | {
217 | type: 'input',
218 | name: 'time',
219 | message: 'How many minutes should the channel be watched:',
220 | },
221 | ]).then(async (answers: {time: number}) => {
222 | winston.info(chalk.gray("Setting Time..."))
223 | CustomChannel.Time = answers.time;
224 | })
225 | }
226 | userdata.customchannel.push(CustomChannel)
227 | //Save Created CH
228 | await fs.promises.writeFile('./CustomChannels.json', JSON.stringify(userdata.customchannel, null, 2)).then(function () {
229 | winston.silly(" ");
230 | winston.info(chalk.green("Successfully Saved Custom Channels..."))
231 | winston.silly(" ");
232 | }).catch(err => {throw err})
233 | });
234 | }
235 |
236 | export async function customCheckLive(feedback: boolean) {
237 | for (const customchannel of userdata.customchannel) {
238 | let channelLogin = customchannel.Link.split('https://www..tv/')[1]
239 | let status = await GQL.GetLiveStatus(channelLogin)
240 | customchannel["live"] = !!status;
241 | if (feedback) {
242 | winston.silly(" ")
243 | winston.info(chalk.cyan(customchannel.Link) + " | " + chalk.magenta(customchannel.Name)+ " | " + statustoString(customchannel.live), {event: "getResult"});
244 | }
245 | }
246 | if (feedback) winston.silly(" ")
247 | }
--------------------------------------------------------------------------------
/src/functions/get/getSettings.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 | import {userdata} from "../../index"
3 | import {validPath} from "../../utils/util";
4 | import init_logger from "../logger/logger";
5 | const fs = require("fs");
6 | const winston = require("winston");
7 | const chromePaths = require('chrome-paths');
8 | const inquirer = require("inquirer");
9 | const inputReader = require("wait-console-input");
10 |
11 | const path = './settings.json'
12 | const opsys = process.platform;
13 |
14 | export default async function () {
15 |
16 | if (fs.existsSync(path)) { //If settings file exists
17 | await fs.promises.readFile('./settings.json', 'utf8').then(async (settingsfile: any) => {
18 | userdata.settings = await JSON.parse(settingsfile)
19 | await init_logger() //Create Logger after settings read
20 | })
21 | winston.silly(" ");
22 | winston.info(chalk.green("Successfully Loaded Settings..."))
23 | winston.silly(" ");
24 | if(userdata.settings.displayless && userdata.settings.Prioritylist.length === 0) {
25 | winston.warn(chalk.yellow('Warning: Please add Games to your Priorty List, otherwise the bot will select a random game...'))
26 | }
27 | return userdata.settings
28 | } else {
29 | await init_logger()
30 | await fs.promises.writeFile('settings.json', JSON.stringify(userdata.settings, null, 2)).then(function () {
31 | winston.silly(" ");
32 | winston.info(chalk.green("Successfully Created Settings..."))
33 | winston.silly(" ");
34 | }).catch((err: any) => {throw err})
35 | return userdata.settings
36 | }
37 | }
38 |
39 | export async function logimportantvalues() {
40 | if (userdata.settings.debug) {winston.info(chalk.cyan("Debug enabled"))}
41 | if (userdata.settings.displayless) {winston.info(chalk.cyan("Displayless mode enabled"))}
42 | if (userdata.settings.WebHookURL !== "") {winston.info(chalk.cyan("Discord Webhook enabled"))}
43 | }
44 |
45 | export async function Chromepaths() {
46 |
47 | await inquirer
48 | .prompt([
49 | {
50 | type: 'confirm',
51 | name: 'confirmed',
52 | message: 'Found it! Is this your Google Chrome Path? | ' + chalk.cyan(chromePaths.chrome),
53 |
54 | },
55 | ])
56 | .then(async (Answer: {confirmed: boolean}) => {
57 |
58 | //If users selects yes
59 | if (Answer.confirmed) {
60 |
61 | //Check the Path
62 | if (opsys !== 'linux') {
63 | if (fs.existsSync(chromePaths.chrome)) {
64 | winston.silly(" ")
65 | userdata.settings.Chromeexe = await chromePaths.chrome //Set the Path
66 | } else { //If auto detected path is invaild
67 | winston.silly(" ")
68 | winston.error(chalk.red("Invalid Path... Please restart the Bot and provide a new one manually..."))
69 | winston.silly(" ")
70 | if (!userdata.settings.displayless) inputReader.wait(chalk.gray("Press any Key to continue..."))
71 | process.exit(21);
72 | }
73 | } else {
74 | winston.silly(" ")
75 | userdata.settings.Chromeexe = await chromePaths.chrome //Set the Path
76 | }
77 |
78 | } else { // If users selects no on auto detect providing it maunally
79 |
80 | winston.silly(" ")
81 | await inquirer
82 | .prompt([
83 | {
84 | type: 'input',
85 | name: 'pathexe',
86 | message: 'Please provide your Google Chrome Executable path?',
87 | validate: (value: string) => validPath(value),
88 | },
89 | ])
90 | .then(async (Answer: {pathexe: string}) => {
91 |
92 | winston.silly(" ")
93 | winston.info(chalk.gray("Setting Executable Path..."))
94 |
95 | userdata.settings.Chromeexe = Answer.pathexe
96 |
97 | });
98 | }
99 | });
100 | await fs.promises.writeFile('settings.json', JSON.stringify(userdata.settings, null, 2)).then(function () {
101 | winston.silly(" ");
102 | winston.info(chalk.green("Successfully Saved Settings..."))
103 | winston.silly(" ");
104 | }).catch((err: any) => {throw err})
105 | }
--------------------------------------------------------------------------------
/src/functions/get/getTwitchDrops.ts:
--------------------------------------------------------------------------------
1 | import {timebased} from "../../Data/userdata";
2 | import {userdata} from "../../index";
3 | import winston from "winston";
4 | import chalk from "chalk";
5 | import {claimedstatustoString, getRandomInt, livechresponse, statustoString} from "../../utils/util";
6 | import {findLiveChannel} from "../findLiveChannel";
7 | import {claimableCheck, matchClaimedDrops} from "../../Checks/claimCheck";
8 | import {dateCheck} from "../../Checks/dateCheck";
9 |
10 | const GQL = require("@zaarrg/gql-dropbot").Init();
11 | const inquirer = require("inquirer");
12 |
13 | export async function getDrops(game: string, feedback: boolean) {
14 | userdata.drops = []
15 |
16 | let dropidstoget:Array = [];
17 |
18 | const DropCampaignDetails = await GQL._SendQuery("ViewerDropsDashboard", {}, '', 'OAuth ' + userdata.auth_token, true, {}, true)
19 | userdata.userid = DropCampaignDetails[0].data.currentUser.id
20 | let allDropCampaings = DropCampaignDetails[0].data.currentUser.dropCampaigns
21 | if (userdata.settings.debug) winston.info('DropCampain %o', JSON.stringify(DropCampaignDetails,null, 2))
22 |
23 | await allDropCampaings.forEach((campaign: Campaign) => {
24 | if (campaign.status === 'ACTIVE') {
25 | if (campaign.game.displayName === game) {
26 | dropidstoget.push(campaign.id)
27 | }
28 | }
29 | })
30 | if (feedback) {
31 | winston.silly(" ")
32 | winston.info(chalk.gray('Getting all available Drops...'), {event: "get"})
33 | }
34 | for (const e of dropidstoget) {
35 | let opts = {
36 | channelLogin: userdata.userid,
37 | dropID: e
38 | }
39 | const DropDetails = await GQL._SendQuery("DropCampaignDetails", opts, 'f6396f5ffdde867a8f6f6da18286e4baf02e5b98d14689a69b5af320a4c7b7b8', 'OAuth ' + userdata.auth_token, true, {}, true)
40 | let CampaignDetails = DropDetails[0].data.user.dropCampaign
41 |
42 | userdata.drops.push({
43 | dropid: CampaignDetails.id,
44 | dropname: CampaignDetails.name,
45 | Connected: CampaignDetails.self.isAccountConnected,
46 | allowedchannels: CampaignDetails.allow.channels,
47 | timebasedrop: CampaignDetails.timeBasedDrops,
48 | live: false,
49 | foundlivech: [],
50 | isClaimed: false
51 | })
52 | }
53 | if (feedback) {
54 | winston.silly(" ")
55 | winston.info(chalk.gray('Looking for a Live Channel...'), {event: "get"})
56 | }
57 | //Check if drop has a Live channel
58 | for (const e of userdata.drops) {
59 | let livechs = await findLiveChannel(e.allowedchannels)
60 | if (livechs.length !== 0) {
61 | e.live = true;
62 | e.foundlivech.push(livechs[0])
63 | } else {
64 | e.live = false;
65 | }
66 | }
67 |
68 | if (feedback) {
69 | winston.silly(" ")
70 | winston.info(chalk.gray('Checking your Inventory for started Drops...'), {event: "get"})
71 | }
72 | //Check if drop is started if so get data and set it
73 | const rawInventory = await GQL._SendQuery("Inventory", {}, '27f074f54ff74e0b05c8244ef2667180c2f911255e589ccd693a1a52ccca7367', 'OAuth ' + userdata.auth_token, true, {}, true)
74 | let Inventory = rawInventory[0].data.currentUser.inventory
75 | if (userdata.settings.debug) winston.info('rawinventory %o', JSON.stringify(rawInventory,null, 2))
76 | Inventory.gameEventDrops.forEach((claimeddrop: GameEventDrops) => {
77 | userdata.claimedDrops.push({
78 | id: claimeddrop.id,
79 | imageurl: claimeddrop.imageURL,
80 | name: claimeddrop.name,
81 | game: claimeddrop.game
82 | })
83 | })
84 |
85 |
86 | //Match inventory drops in progress to the right Drops
87 | userdata.drops.forEach(DropElement => {
88 | if (Inventory.dropCampaignsInProgress !== null) {
89 | Inventory.dropCampaignsInProgress.forEach((e: DropCampaignsInProgress) => {
90 | if (DropElement.dropid === e.id) {
91 | DropElement.timebasedrop = e.timeBasedDrops;
92 | }
93 | })
94 | } else {
95 | if (userdata.settings.debug) winston.info('No Drops in Progress...')
96 | }
97 | })
98 |
99 | //Make sure self object exits
100 | userdata.drops.forEach(drop => {
101 | drop.timebasedrop.forEach(time => {
102 | if (!("self" in time)) {
103 | time['self'] = {
104 | __typename: "TimeBasedDropSelfEdge",
105 | currentMinutesWatched: 0,
106 | dropInstanceID: null,
107 | isClaimed: null
108 | }
109 | }
110 | })
111 | })
112 |
113 |
114 | if (feedback) {
115 | winston.silly(" ")
116 | winston.info(chalk.gray('Checking your Inventory for claimed Drops...'), {event: "get"})
117 | }
118 | await matchClaimedDrops()
119 | //Update Date Status
120 | for (const drop of userdata.drops) {
121 | await dateCheck(drop, true)
122 | await claimableCheck(drop, userdata.settings.AutoClaim, true)
123 | }
124 |
125 | //Log Result
126 | if (feedback) {
127 | userdata.drops.forEach(drop => {
128 | winston.silly(" ")
129 | winston.info(livechresponse(drop.foundlivech) + " | " + chalk.magenta(drop.dropname) + " | " + statustoString(drop.live) + ' | ' + claimedstatustoString(drop.isClaimed), {event: "getResult"})
130 | })
131 | }
132 |
133 | }
134 |
135 | export async function askWhatDropToStart(random: boolean, filterlive: boolean, filterNonActive: boolean, filterlast: boolean) {
136 | userdata.availableDropNameChoices = []
137 | userdata.drops.forEach(drop => {
138 | if (filterlive) {
139 | if (drop.live) {
140 | userdata.availableDropNameChoices.push(drop.dropname)
141 | }
142 | } else {
143 | userdata.availableDropNameChoices.push(drop.dropname)
144 | }
145 | })
146 |
147 | if (filterNonActive) {
148 | for (const [i, DropName] of userdata.availableDropNameChoices.entries()) {
149 | if (userdata.nonActiveDrops.includes(DropName)){
150 | userdata.availableDropNameChoices.splice(i, 1)
151 | winston.silly(" ")
152 | winston.info(chalk.yellow(DropName + ' | ' + 'was removed because the drop ended or not started yet...'))
153 | }
154 | }
155 | }
156 |
157 | if (filterlast) {
158 | for (const [i, choice] of userdata.availableDropNameChoices.entries()) {
159 | if (choice === userdata.startDrop) {
160 | userdata.availableDropNameChoices.splice(i, 1)
161 | }
162 | }
163 | }
164 |
165 | if (userdata.availableDropNameChoices.length === 0) {
166 | winston.silly(" ")
167 | winston.info(chalk.gray('All available Channels Offline... Select any Drop to start watching...'))
168 | userdata.drops.forEach(drop => {
169 | userdata.availableDropNameChoices.push(drop.dropname)
170 | })
171 | }
172 |
173 | winston.silly(" ")
174 | if (!random) {
175 | await inquirer
176 | .prompt([
177 | {
178 | type: 'list',
179 | name: 'namelist',
180 | message: 'What Drop do you wanna start Watching?',
181 | choices: userdata.availableDropNameChoices,
182 | },
183 | ])
184 | .then(async (answer: {namelist: string}) => {
185 | userdata.startDrop = answer.namelist
186 | });
187 | } else {
188 | userdata.startDrop = userdata.availableDropNameChoices[getRandomInt(userdata.availableDropNameChoices.length)]
189 | winston.info(chalk.gray('Selected a random drop to watch: ' + chalk.white(userdata.startDrop)))
190 | }
191 |
192 | }
193 |
194 |
195 | export async function askWhatGameToWatch(random: boolean) {
196 | let activecampainnames = await getActiveCampaigns();
197 |
198 | winston.silly(" ")
199 | if (!userdata.settings.displayless) {
200 | if (!random) {
201 | await inquirer
202 | .prompt([
203 | {
204 | type: 'list',
205 | name: 'namelist',
206 | message: 'What Game do you wanna watch?',
207 | choices: activecampainnames,
208 | },
209 | ])
210 | .then(async (answer: {namelist: string}) => {
211 | userdata.game = answer.namelist
212 | });
213 | } else {
214 | userdata.game = activecampainnames[getRandomInt(userdata.availableDropNameChoices.length)]
215 | winston.info(chalk.gray('Selected a random game to watch: ' + chalk.white(userdata.game)))
216 | }
217 | } else {
218 | if (userdata.settings.Prioritylist.length === 0) {
219 | winston.warn(chalk.yellow('Warning: Please add Games to your Priority List, otherwise the bot will select a random game... or disable this feature in the settings...'))
220 | userdata.game = activecampainnames[getRandomInt(userdata.availableDropNameChoices.length)]
221 | winston.info(chalk.gray('Selected a random Game to watch: ' + chalk.white(userdata.game)))
222 | } else {
223 | userdata.game = userdata.settings.Prioritylist[0]
224 | winston.info(chalk.gray('Selected a Game from your Priority List watch: ' + userdata.game))
225 | }
226 | }
227 | }
228 |
229 | export async function getActiveCampaigns() {
230 | let activecampainnames:Array = [];
231 | winston.silly(" ")
232 | winston.info(chalk.gray('Getting all active Campaigns...'), {event: "get"})
233 | const DropCampaignDetails = await GQL._SendQuery("ViewerDropsDashboard", {}, '', 'OAuth ' + userdata.auth_token, true, {}, true)
234 | let allDropCampaings = DropCampaignDetails[0].data.currentUser.dropCampaigns
235 | await allDropCampaings.forEach((campaign: Campaign) => {
236 | if (campaign.status === 'ACTIVE') {
237 | if (activecampainnames.includes(campaign.game.displayName) === false) {
238 | activecampainnames.push(campaign.game.displayName)
239 | }
240 | }
241 | })
242 | if (userdata.settings.Prioritylist.length > 0) {
243 | for (let i = userdata.settings.Prioritylist.length; i--;) {
244 | if (!activecampainnames.includes(userdata.settings.Prioritylist[i])) {
245 | winston.info(chalk.yellow("Removed " + userdata.settings.Prioritylist[i] + " from the Priority List, because there is no ACTIVE campaign with such name."))
246 | userdata.settings.Prioritylist.splice(i, 1);
247 | }
248 | }
249 | }
250 | return activecampainnames;
251 | }
252 |
253 |
254 | type Campaign = {
255 | id: string,
256 | name: string,
257 | owner: {
258 | id: string,
259 | name: string,
260 | __typename: string
261 | },
262 | game: {
263 | id: string,
264 | displayName: string,
265 | boxArtURL: string,
266 | __typename: string
267 | },
268 | status: string,
269 | startAt: string,
270 | endAt: string,
271 | detailsURL: string,
272 | accountLinkURL: string,
273 | self: Object,
274 | __typename: string
275 | }
276 |
277 | type DropCampaignsInProgress = {
278 | id: string,
279 | name: string,
280 | status: string,
281 | timeBasedDrops: Array
282 | }
283 |
284 | type GameEventDrops = {
285 | game: Object,
286 | id: string,
287 | imageURL: string,
288 | isConnected: boolean,
289 | lastAwardedAt: string,
290 | name: string,
291 | requiredAccountLink: string,
292 | totalCount: string,
293 | __typename: string
294 | }
--------------------------------------------------------------------------------
/src/functions/get/getWatchOption.ts:
--------------------------------------------------------------------------------
1 | import {userdata} from "../../index" ;
2 |
3 | const inquirer = require("inquirer");
4 |
5 |
6 | export default async function () {
7 | type Watch_Answer = {
8 | watchoptions: string
9 | }
10 | if (!userdata.settings.displayless) {
11 | let options = ["Drops", "Custom Channels"]
12 | await inquirer
13 | .prompt([
14 | {
15 | type: 'list',
16 | name: 'watchoptions',
17 | message: 'What do u wanna watch?',
18 | choices: options,
19 | },
20 | ])
21 | .then(async (answer: Watch_Answer) => {
22 | userdata.watch_option = answer.watchoptions
23 | });
24 | return userdata.watch_option
25 | } else {
26 | userdata.watch_option = "Drops"
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/functions/handler/custompageHandler.ts:
--------------------------------------------------------------------------------
1 | import {CustomChannel} from "../../Data/userdata";
2 | import {userdata} from "../../index";
3 | import {customallOfflineCheck, liveCheck} from "../../Checks/liveCheck";
4 | import {sendMinuteWatched} from "./watchpageHandler"
5 | import {pointsCheck} from "../../Checks/pointsCheck";
6 | import {delay} from "../../utils/util";
7 | import winston from "winston";
8 | import chalk from "chalk";
9 | import {askCustomChannelStart, customCheckLive} from "../get/getCustomChannel";
10 |
11 | let status:string = 'stopped';
12 |
13 | export async function CustomEventHandlerStart(DropcurrentlyWatching: string) {
14 | if (status === 'stopped') {
15 | await customallOfflineCheck();
16 | await liveCheck(DropcurrentlyWatching, true);
17 | await pointsCheck(DropcurrentlyWatching);
18 | await sendMinuteWatched(DropcurrentlyWatching.toString().toLowerCase())
19 | status = 'running'
20 | await customloop(DropcurrentlyWatching);
21 | } else if (status === 'running') {
22 | await customloop(DropcurrentlyWatching);
23 | }
24 | }
25 |
26 | let watchedtime = 0;
27 | async function customloop(channelLogin: string) {
28 | await delay(userdata.settings.ProgressCheckInterval);
29 | watchedtime = (watchedtime + userdata.settings.ProgressCheckInterval)
30 |
31 | //find right custom drop
32 | await getCustomDrop(channelLogin).then(async (currentdrop) => {
33 | await customCheckLive(false);
34 | await customallOfflineCheck();
35 | await liveCheck(channelLogin, true);
36 | let neededtimeinms = (currentdrop.Time * 60000)
37 | if (status === 'running') {
38 | if (currentdrop.WatchType === "Watch until time runs out") {
39 | if (watchedtime < neededtimeinms) {
40 | await pointsCheck(channelLogin).then(async points => {
41 | await sendMinuteWatched(channelLogin.toString().toLowerCase())
42 | winston.info(chalk.gray("Watching since: ") + chalk.white((Number(watchedtime / 60000).toFixed(2))) + chalk.gray(" | Minutes Left: " + chalk.white((neededtimeinms - watchedtime) / 60000)) + chalk.gray(" | Points: ") + chalk.white(points.toString()), {event: "progress"});
43 | winston.silly('', {event: "progressEnd"})
44 | await customloop(channelLogin)
45 | });
46 |
47 | } else if (watchedtime >= neededtimeinms) {
48 | status = 'stopped'
49 | winston.info(chalk.green('Finished watching the channel: ' + channelLogin), {event: "newDrop"})
50 | winston.info(chalk.gray('Looking for a new Channel...'), {event: "newDrop"})
51 | await customrestartHandler(true)
52 | }
53 | } else {
54 | await pointsCheck(channelLogin).then(async points => {
55 | await sendMinuteWatched(channelLogin.toString().toLowerCase())
56 | winston.info(chalk.gray("Watching since: ") + chalk.white((Number(watchedtime / 60000).toFixed(2))) + chalk.gray(" | Points: ") + chalk.white(points.toString()), {event: "progress"});
57 | winston.silly('', {event: "progressEnd"})
58 | await customloop(channelLogin)
59 | });
60 | }
61 | }
62 | })
63 | }
64 |
65 | export async function customrestartHandler(random: boolean) {
66 | watchedtime = 0;
67 | await customCheckLive(false);
68 | await askCustomChannelStart(random, true);
69 | await CustomEventHandlerStart(userdata.startDrop);
70 | }
71 |
72 | async function getCustomDrop(ChannelLogin: string) {
73 | let currentdrop:CustomChannel = {
74 | Name: '',
75 | Link: '',
76 | WatchType: '',
77 | Time: 0,
78 | Points: false,
79 | live: false
80 | };
81 | userdata.customchannel.forEach(drop => {
82 | if (drop.Link === 'https://www..tv/' + ChannelLogin) {
83 | currentdrop = drop;
84 | }
85 | })
86 | return currentdrop;
87 | }
--------------------------------------------------------------------------------
/src/functions/handler/restartHandler.ts:
--------------------------------------------------------------------------------
1 | import {WatchingEventHandlerStop} from "./watchpageHandler";
2 | import {askWhatDropToStart, askWhatGameToWatch, getActiveCampaigns, getDrops} from "../get/getDrops";
3 | import {startWatching} from "../startWatching";
4 | import {userdata} from "../../index" ;
5 | import winston from "winston";
6 | import chalk from "chalk";
7 | import {delay, getRandomInt} from "../../utils/util";
8 |
9 |
10 | export async function restartHandler(random: boolean, filterlive: boolean, filterNonActive: boolean, filterlast: boolean, newgame: boolean) {
11 | if (!newgame) {
12 | await WatchingEventHandlerStop()
13 | await askWhatDropToStart(random, filterlive, filterNonActive, filterlast)
14 | await startWatching()
15 | } else if (newgame && userdata.settings.Prioritylist.length > 0) {
16 | await WatchingEventHandlerStop()
17 | await selectGamefromList()
18 | await getDrops(userdata.game, true).then(async () => {
19 | await askWhatDropToStart(random, filterlive, filterNonActive, filterlast)
20 | await startWatching()
21 | })
22 | } else {
23 | await WatchingEventHandlerStop()
24 | await askWhatGameToWatch(true)
25 | await getDrops(userdata.game, true).then(async () => {
26 | await askWhatDropToStart(random, filterlive, filterNonActive, filterlast)
27 | await startWatching()
28 | })
29 | }
30 | }
31 |
32 | async function selectGamefromList() {
33 | let activecampainnames = await getActiveCampaigns();
34 |
35 | if (userdata.settings.Prioritylist.length === 0) {
36 | winston.warn(chalk.yellow('Warning: Please add Games to your Priority List, otherwise the bot will select a random game... or disable this feature in the settings...'))
37 | userdata.game = activecampainnames[getRandomInt(userdata.availableDropNameChoices.length)]
38 | winston.info(chalk.gray('Selected a random Game to watch: ' + chalk.white(userdata.game)))
39 | } else {
40 | let gameselected = ''
41 | for (const [i, game] of userdata.settings.Prioritylist.entries()) {
42 | if (userdata.game === game) {
43 | if ((userdata.settings.Prioritylist.length-1) === i) {
44 | gameselected = userdata.settings.Prioritylist[0]
45 | } else {
46 | gameselected = userdata.settings.Prioritylist[i+1]
47 | }
48 | }
49 | }
50 | if (gameselected === '') gameselected = userdata.settings.Prioritylist[0]
51 | userdata.game = gameselected
52 | winston.silly(" ")
53 | winston.info(chalk.gray('Selected ') + chalk.white(gameselected) + chalk.gray(' as next game from your Priority list... Watching in 45 seconds...'), {event: "newGame"})
54 | winston.silly(' ', {event: "progressEnd"})
55 | await delay(45000)
56 | }
57 | }
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/functions/handler/watchpageHandler.ts:
--------------------------------------------------------------------------------
1 | import {getDrops} from "../get/getDrops";
2 | import {userdata} from "../../index" ;
3 | import {allOfflineCheck, liveCheck} from "../../Checks/liveCheck";
4 | import winston from "winston";
5 | import {getCurrentDrop} from "../get/getCurrentDrop";
6 | import {delay, minutestoPercent, retryConfig} from "../../utils/util";
7 | import {dateCheck} from "../../Checks/dateCheck";
8 | import axios from "axios";
9 | import {claimableCheck} from "../../Checks/claimCheck";
10 | import chalk from "chalk";
11 | import {SamePercentCheck} from "../../Checks/samepercentCheck";
12 | import {pointsCheck} from "../../Checks/pointsCheck";
13 | const GQL = require("@zaarrg/gql-dropbot").Init(userdata.clientid);
14 | const {Base64} = require('js-base64');
15 |
16 | let status:string = 'stopped';
17 |
18 | export async function WatchingEventHandlerStart(DropcurrentlyWatching: string) {
19 | if (status === 'stopped') {
20 | await getDrops(userdata.game, false)
21 | await allOfflineCheck()
22 | await liveCheck(DropcurrentlyWatching, false);
23 | await sendMinuteWatched(DropcurrentlyWatching.toString().toLowerCase())
24 | status = 'running'
25 | await loop(DropcurrentlyWatching);
26 | } else if (status === 'running') {
27 | await loop(DropcurrentlyWatching);
28 | }
29 | }
30 |
31 | async function loop(DropcurrentlyWatching: string) {
32 | await delay(userdata.settings.ProgressCheckInterval);
33 | if (userdata.settings.debug) winston.info('UserDATA %o', JSON.stringify(userdata,null, 2))
34 | //Update Drop Data
35 | await getDrops(userdata.game, false)
36 | await allOfflineCheck()
37 | //Get the right Drop
38 | if (status === 'running') {
39 | await getCurrentDrop().then(async (CurrentDrop) => {
40 | if (userdata.settings.debug) winston.info('CurrentDrop %o', JSON.stringify(CurrentDrop,null, 2))
41 | //Switch DropcurrentlyWatching to a live one if current offline and new live ch available
42 | if (!CurrentDrop.foundlivech.includes(DropcurrentlyWatching) && CurrentDrop.foundlivech.length > 0) {
43 | DropcurrentlyWatching = CurrentDrop.foundlivech[0]
44 | winston.info(chalk.gray("Switched current Channel to " + chalk.white(DropcurrentlyWatching) + "..."))
45 | winston.silly(" ")
46 | }
47 | await liveCheck(DropcurrentlyWatching, false);
48 | await claimableCheck(CurrentDrop, userdata.settings.AutoClaim, false)
49 | await dateCheck(CurrentDrop, false)
50 | await SamePercentCheck(CurrentDrop)
51 | await pointsCheck(DropcurrentlyWatching).then(points => {
52 | winston.info(chalk.gray('Watching ' + chalk.white(DropcurrentlyWatching) + ' | Points: ' + chalk.white(points.toString())), {event: "progress"})
53 | })
54 | for (const [i, drop] of CurrentDrop.timebasedrop.entries()) {
55 | let dropslenght = CurrentDrop.timebasedrop.length;
56 | winston.info(chalk.gray("Current Progress: ") + chalk.white( minutestoPercent(drop.self.currentMinutesWatched, drop.requiredMinutesWatched)+" %") + chalk.gray(" | Watched " + chalk.white(drop.self.currentMinutesWatched + "/" + drop.requiredMinutesWatched) + " Minutes" + chalk.gray(" | Drop ") + chalk.white((i+1) + "/" + dropslenght) + chalk.gray(" | Status ") + chalk.white(drop.self.status) + chalk.gray(" | isClaimed ") + chalk.white(drop.self.isClaimed)), {event: "progress"});
57 |
58 | }
59 | winston.silly(' ', {event: "progressEnd"})
60 | await sendMinuteWatched(DropcurrentlyWatching)
61 | })
62 | if (userdata.settings.debug) winston.info('Interval Executed')
63 | if (status === 'running') await loop(DropcurrentlyWatching)
64 | }
65 | }
66 |
67 | export async function WatchingEventHandlerStop() {
68 | status = 'stopped'
69 | }
70 |
71 | export async function sendMinuteWatched(ChannelLogin: string) {
72 | let opts = {
73 | "channelLogin":ChannelLogin
74 | }
75 | let Stream = await GQL._SendQuery("UseLive", opts, '639d5f11bfb8bf3053b424d9ef650d04c4ebb7d94711d644afb08fe9a0fad5d9', 'OAuth ' + userdata.auth_token, true);
76 | let channleid = Stream[0].data.user.id
77 | let broadcastid = Stream[0].data.user.stream.id
78 |
79 | const gethtml = await axios.get('https://www..tv/' + ChannelLogin, {
80 | headers: {
81 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
82 | 'encoding': 'utf8',
83 | 'Client-Id': userdata.clientid,
84 | 'Authorization': 'OAuth ' + userdata.auth_token,
85 | },
86 | raxConfig: retryConfig
87 | }).catch(err => {
88 | winston.error("ERROR: Could not load website... Check your connection...")
89 | throw err
90 | })
91 |
92 | let SettingsJSReg = new RegExp('https://static\.cdn\.net/config/settings\.[0-9a-f]{32}\.js')
93 | let parsehtml = SettingsJSReg.exec(gethtml.data.toString())
94 | if (parsehtml![0] === null) winston.error("Error while parsing Settings Url...")
95 |
96 | const getSettingsJS = await axios.get(parsehtml![0].toString(), {raxConfig: retryConfig}).catch(err => {
97 | winston.error("ERROR: Could not load your settings... Check your connection...")
98 | throw err
99 | })
100 |
101 | let SpadeReg = new RegExp('(https://video-edge-[.\\w\\-/]+\\.ts)')
102 | let parseJS = SpadeReg.exec(getSettingsJS.data.toString())
103 | if (parseJS![0] === null) winston.error("Error while parsing Spade URL...")
104 |
105 | let payload = [
106 | {
107 | "event": "minute-watched",
108 | "properties": {
109 | "channel_id": channleid.toString(),
110 | "broadcast_id": broadcastid.toString(),
111 | "player": "site",
112 | "user_id": userdata.userid.toString(),
113 | }
114 | }
115 | ]
116 | let json_event = JSON.stringify(payload);
117 | let b64 = Base64.encode(json_event)
118 |
119 | let config = {
120 | headers: {
121 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
122 | "Content-type": "text/plain",
123 | },
124 | raxConfig: retryConfig
125 | }
126 |
127 | const post = await axios.post(parseJS![0].toString(), b64, config).catch(err => {
128 | winston.error("ERROR: Could not send minute watching event...")
129 | throw err
130 | })
131 | if (userdata.settings.debug) {
132 | winston.info('minute sent!!' + post?.status)
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/functions/handler/webHookHandler.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {Log} from "../logger/logger";
3 | import {userdata} from "../../index";
4 |
5 | //events: requestRetry, claim, newDrop, offline, newGame, get, getResult, progress, start, progressEnd, error, warn, info
6 |
7 | let logqueue: Log[] = []
8 |
9 | export async function webhookHandler(log: Log) {
10 | //Remove Color Codes
11 | log.message = log.message.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '').replace(/,/g, ' ');
12 | if (!log.event) {
13 | log["event"] = log.level;
14 | }
15 |
16 | if (log.event === "progressEnd" || log.message !== " ") {
17 | await webhooklogic(log)
18 | }
19 |
20 | }
21 |
22 | async function webhooklogic(log: Log) {
23 | logqueue.push(log)
24 | if ((logqueue.length > 1 && logqueue[logqueue.length - 1].event !== logqueue[logqueue.length - 2].event)) {
25 | if (userdata.settings.WebHookEvents.length > 0 && userdata.settings.WebHookEvents.includes(logqueue[logqueue.length - 2].event!.toLowerCase())) {
26 | await clearqueueandsend(log)
27 | } else if (userdata.settings.WebHookEvents.length === 0) {
28 | await clearqueueandsend(log)
29 | } else {
30 | logqueue.splice(0, logqueue.length - 1)
31 | }
32 | }
33 | }
34 |
35 | async function clearqueueandsend(log: Log) {
36 | let arraytosend = logqueue.splice(0, logqueue.length - 1)
37 | let stringarray: string[] = []
38 | arraytosend.forEach(log => stringarray.push(log.message))
39 |
40 | await sendWebhook(stringarray, arraytosend[0].event!.toString(), userdata.settings.WebHookURL, 8933352).then(status => {
41 | if (!status) {
42 | throw "Error while trying to send the discord webhook"
43 | }
44 | })
45 |
46 | if (log.event === "progressEnd") logqueue = [];
47 | }
48 |
49 |
50 | //
51 | export async function sendWebhook(msg: string[], event: string, webhookurl: string, color: number) {
52 | let content = "";
53 | let currentDrop = "";
54 | if (userdata.startDrop !== undefined) {
55 | currentDrop = userdata.startDrop === "" ? "none" : userdata.startDrop.toString();
56 | } else {
57 | currentDrop = "none"
58 | }
59 | if (event === "progress") {
60 | let convertedProgress = await convertProgressString(msg)
61 | content = convertedProgress.toString().split(",").join("\n")
62 | } else {
63 | content = msg.toString().split(",").join("\n\n")
64 | }
65 |
66 | let config = {
67 | headers: {
68 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
69 | 'Content-type': 'application/json'
70 | }
71 | }
72 |
73 | let embed = {
74 | username: "DropBot",
75 | avatar_url: "https://i.imgur.com/2WtgNe4.png",
76 | embeds: [
77 | {
78 | "author": {
79 | "name": "DropBot📜",
80 | "url": "https://github.com/Zaarrg/DropBot",
81 | "icon_url": "https://i.imgur.com/2WtgNe4.png"
82 | },
83 | "fields": [
84 | {
85 | "name": "Event",
86 | "value": event,
87 | "inline": true
88 | },
89 | {
90 | "name": "Current Drop",
91 | "value": currentDrop,
92 | "inline": true
93 | },
94 | ],
95 | "color": color,
96 | "description": "```" + content + "```",
97 | "footer": {
98 | "text": "Send directly from Dropbot made by Zarg!"
99 | },
100 | "timestamp": new Date()
101 | }
102 | ]
103 | }
104 |
105 | return await axios.post(webhookurl, JSON.stringify(embed), config).then(() => {return true}).catch(e => {
106 | return false
107 | })
108 | }
109 |
110 |
111 | async function convertProgressString(stringarray: string[]) {
112 | let finallog: string[] = [];
113 | stringarray.forEach(log => {
114 | let splitted = log.split(" | ")
115 |
116 | let firsttwo = splitted.slice(0, 2)
117 | let rest = splitted.slice(2, splitted.length)
118 |
119 | if (rest.length === 0) {
120 | finallog.push(firsttwo.toString().replace(/,/g, ' | ') + "\n")
121 | } else {
122 | if (firsttwo.length > 0) finallog.push(firsttwo.toString().replace(/,/g, ' | '))
123 | if (rest.length > 0) finallog.push(rest.toString().replace(/,/g, ' | ') + "\n")
124 | }
125 | })
126 | return finallog
127 | }
--------------------------------------------------------------------------------
/src/functions/logger/logger.ts:
--------------------------------------------------------------------------------
1 | import {sendWebhook, webhookHandler} from "../handler/webHookHandler";
2 | import {userdata} from "../../index" ;
3 |
4 | const fs = require("fs");
5 | const winston = require('winston');
6 | const {format} = require("winston");
7 | const { printf } = format;
8 |
9 | export default async function () {
10 |
11 | const fileFormat = printf((log: Log) => {return `${log.timestamp}: ${log.message}`});
12 | const consoleFormat = printf((log: Log) => {return log.message})
13 | // Logger configuration
14 | process.on('unhandledRejection', async (reason : string, promise) => {
15 | winston.error("Unhandled Rejection at: %o", promise)
16 | winston.error("Unhandled Rejection Reason: " + reason)
17 | if (userdata.settings.WebHookURL !== "") {
18 | await sendWebhook([reason, "More Details can be found in the error Log...", "Closing Bot..."], "ERROR", userdata.settings.WebHookURL, 16711680).then((request) => {
19 | if (!request) {
20 | winston.info('Could not send Webhook with ERROR: Closing Bot...')
21 | process.exit(21);
22 | } else {
23 | process.exit(21);
24 | }
25 | })
26 | } else {
27 | process.exit(21);
28 | }
29 | })
30 | try {
31 | await createConsoleLogger(consoleFormat)
32 | if (fs.existsSync('./settings.json')) {
33 | let settingsfile = fs.readFileSync('./settings.json', 'utf8');
34 | let options = await JSON.parse(settingsfile)
35 | if (options.LogToFile) {
36 | await createFilelogger(fileFormat)
37 | }
38 | }
39 | } catch (e) {
40 | await createConsoleLogger(consoleFormat)
41 | await createFilelogger(fileFormat)
42 | winston.error('ERROR')
43 | throw 'Invalid/Corrupted JSON file...'
44 | }
45 | return true
46 | }
47 |
48 | async function createConsoleLogger(consoleFormat: any) {
49 | const consoleLogger = new winston.transports.Console({
50 | level: 'silly',
51 | handleExceptions: true,
52 | RejectionHandler: true,
53 | format: format.combine(
54 | format.prettyPrint(),
55 | format.splat(),
56 | consoleFormat
57 | )
58 | })
59 | winston.add(consoleLogger);
60 | consoleLogger.on('logged', async function (log:any) {if (userdata.settings.WebHookURL !== "") await webhookHandler(log)})
61 | }
62 |
63 | async function createFilelogger(fileFormat:any) {
64 | winston.add(new winston.transports.File({
65 | filename: './logs/DropBot-out.log',
66 | level: 'info',
67 | handleExceptions: true,
68 | RejectionHandler: true,
69 | maxsize: "20m",
70 | maxFiles: 5,
71 | timestamp: true,
72 | format: format.combine(
73 | format.uncolorize(),
74 | format.splat(),
75 | format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
76 | fileFormat,
77 | )
78 | }));
79 | winston.add(new winston.transports.File({
80 | filename: './logs/DropBot-error.log',
81 | level: 'error',
82 | handleExceptions: true,
83 | RejectionHandler: true,
84 | maxsize: "20m",
85 | maxFiles: 5,
86 | timestamp: true,
87 | format: format.combine(
88 | format.uncolorize(),
89 | format.splat(),
90 | format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
91 | fileFormat,
92 | )
93 | }));
94 | }
95 |
96 | export type Log = {
97 | message: string,
98 | event?: string,
99 | level: string,
100 | timestamp: string
101 | }
--------------------------------------------------------------------------------
/src/functions/login/defaultlogin.ts:
--------------------------------------------------------------------------------
1 | import winston from "winston";
2 | import chalk from "chalk";
3 | import {userdata} from "../../index" ;
4 | import {Chromepaths} from "../get/getSettings";
5 | import {Login} from "../../Pages/loginPage";
6 | import fs from "fs";
7 | import axios from "axios";
8 | import {retryConfig} from "../../utils/util";
9 | const inquirer = require("inquirer");
10 |
11 | let pw: string = '';
12 | let nm: string = '';
13 | export async function login() {
14 | if (!userdata.auth_token && !fs.existsSync('./drop-session.json')) {
15 | if (!userdata.settings.displayless) {
16 | winston.silly(" ");
17 | winston.info(chalk.gray('Please Login into your Account...'))
18 | winston.silly(" ");
19 |
20 | let options = ["Directly via Command Line", "Via Browser"]
21 | await inquirer
22 | .prompt([
23 | {
24 | type: 'list',
25 | name: 'loginoption',
26 | message: 'How would you like to Login into your account?',
27 | choices: options,
28 | },
29 | ])
30 | .then(async (answer: {loginoption: string}) => {
31 | if (answer.loginoption === 'Via Browser') {
32 | await browserlogin();
33 | } else {
34 | await directlogin('', '');
35 | pw = '';
36 | nm = '';
37 | }
38 | });
39 | } else {
40 | winston.error('ERROR')
41 | throw 'No drop-session.json found to use in displayless mode...'
42 | }
43 | } else {
44 | await getUserDetails()
45 | winston.silly(" ");
46 | winston.info(chalk.gray('Found a drop-session... No need to login...'))
47 | winston.silly(" ");
48 | }
49 |
50 |
51 | }
52 |
53 | async function askforacccountdetails() {
54 | if (pw === '' || nm === '') {
55 | await inquirer
56 | .prompt([
57 | {
58 | type: 'input',
59 | name: 'username',
60 | message: 'What is your Username?'
61 | },
62 | {
63 | type: 'password',
64 | name: 'password',
65 | message: 'What is your Password?'
66 | }
67 | ])
68 | .then(async (Answer: {username: string, password: string}) => {
69 | pw = Answer.password
70 | nm = Answer.username
71 | });
72 | return {pw: pw, nm: nm}
73 | }
74 | return {pw: pw, nm: nm}
75 | }
76 |
77 | async function askforauthcode(errorcode: number) {
78 | let message: string = '';
79 | let input: string = '';
80 | if (errorcode === 3011) message = 'What is your 2FA token?'
81 | if (errorcode === 3022) message = 'What is your Email code?'
82 |
83 | await inquirer
84 | .prompt([
85 | {
86 | type: 'input',
87 | name: 'code',
88 | message: message
89 | }
90 | ])
91 | .then(async (Answer: {code: string}) => {
92 | input = Answer.code
93 | });
94 | return input
95 | }
96 |
97 | async function directlogin(emailcode: string, facode: string, captcha_proof = {}) {
98 | let attempt = 0;
99 | const details = await askforacccountdetails()
100 |
101 | let config = {
102 | headers: {
103 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
104 | "Content-type": "text/plain",
105 | },
106 | raxConfig: retryConfig
107 | }
108 | let body = {
109 | "client_id": "kimne78kx3ncx6brgo4mv6wki5h1ko",
110 | "undelete_user": false,
111 | "remember_me": true,
112 | "username": details.nm,
113 | "password": details.pw,
114 | ...captcha_proof
115 | }
116 | if (emailcode !== '') {
117 | Object.assign(body, {"guard_code": emailcode})
118 | } else if (facode !== '') {
119 | Object.assign(body, {"authy_token": facode})
120 | }
121 |
122 | await axios.post('https://passport..tv/login', body, config)
123 | .then(async function (response) {
124 | let response_data = response.data
125 | if (userdata.settings.debug) winston.info('loginresponse %o', JSON.stringify(response_data,null, 2))
126 | winston.info(chalk.green("Successfully Logged in..."))
127 |
128 | let authcookie = [{
129 | "name": "auth-token",
130 | "value": response_data.access_token,
131 | }]
132 | await fs.promises.writeFile('drop-session.json', JSON.stringify(authcookie, null, 2)).then(function () {
133 | winston.silly(" ");
134 | winston.info(chalk.green("Successfully Saved Cookies..."))
135 | winston.silly(" ");
136 | }).catch(err => {throw err})
137 | await getUserDetails();
138 |
139 | })
140 | .catch(async function (error) {
141 | winston.silly(" ")
142 | winston.error(chalk.yellow('Something went wrong...'))
143 | let errorcode = 0;
144 | let capta = {}
145 | try {
146 | if (error.response.data.captcha_proof) capta = {captcha_proof: error.response.data.captcha_proof}
147 | } catch (e) {}
148 | try {
149 | errorcode = error.response.data.error_code
150 | } catch (e) {}
151 |
152 | if (attempt === 3) {
153 | winston.info(chalk.gray('Failed 3 times to login closing...'))
154 | throw 'Failed to Login...'
155 | }
156 | if (errorcode === 1000) {
157 | nm = '';
158 | pw = '';
159 | winston.info(chalk.gray('Login failed due to CAPTCHA...'))
160 | winston.silly(" ")
161 | winston.info(chalk.gray('Your login attempt was denied by CAPTCHA. Please wait 12h or login via the browser...'))
162 | winston.silly(" ")
163 | winston.info(chalk.gray('Redirecting to browser login...'))
164 | await browserlogin()
165 | } else if (errorcode === 3001 || errorcode === 2005) {
166 | attempt++
167 | nm = '';
168 | pw = '';
169 | winston.info(chalk.gray("Login failed due to incorrect username or password..."))
170 | await directlogin('', '', capta);
171 | } else if (errorcode === 3012) {
172 | attempt++
173 | winston.info(chalk.gray("Invaild 2FA..."))
174 | winston.silly(" ")
175 | let code = await askforauthcode(3011);
176 | await directlogin('', code, capta);
177 | } else if (errorcode === 3023) {
178 | attempt++
179 | winston.info(chalk.gray("Invaild Email Code..."))
180 | winston.silly(" ")
181 | let code = await askforauthcode(3022);
182 | await directlogin('', code, capta);
183 | }
184 | if (errorcode === 3011) {
185 | winston.info(chalk.gray('2FA token required..."'))
186 | winston.silly(" ")
187 | let code = await askforauthcode(3011);
188 | await directlogin('', code, capta);
189 | } else if (errorcode === 3022) {
190 | winston.info(chalk.gray('Email code required...'))
191 | winston.silly(" ")
192 | let code = await askforauthcode(3022);
193 | await directlogin(code, '', capta);
194 | } else if (!fs.existsSync('./drop-session.json')) {
195 | attempt++
196 | nm = '';
197 | pw = '';
198 | winston.info(chalk.gray('Login failed for an unknown reason...'))
199 | winston.info(chalk.gray('The Reason is probably:'))
200 | winston.info(chalk.yellow('Error Code: ' + error.data.error_code + ' | Reason: ' + error.data.error + ' | Error Description: ' + error.error_description))
201 | winston.silly(" ")
202 | await directlogin('', '', capta);
203 | }
204 | })
205 | }
206 |
207 | async function browserlogin() {
208 |
209 | winston.info(chalk.gray('Proceeding to Browser...'))
210 | if (userdata.settings.Chromeexe === '' ) {
211 | winston.info(chalk.gray('No Browser Found...'))
212 | await Chromepaths()
213 | await Login()
214 | await getUserDetails()
215 | } else {
216 | winston.info(chalk.gray('Browser Found...'))
217 | await Login()
218 | await getUserDetails()
219 | }
220 |
221 | }
222 |
223 | async function getUserDetails() {
224 | if (userdata.auth_token || fs.existsSync('./drop-session.json')) {
225 | if (fs.existsSync('./drop-session.json')) {
226 | const data = await fs.promises.readFile('./drop-session.json', 'utf8')
227 | let cookiedata = JSON.parse(data);
228 | for (let i = 0; i < cookiedata.length; i++) {
229 | if (cookiedata[i].name === 'auth-token') {
230 | userdata.auth_token = cookiedata[i].value;
231 | break;
232 | }
233 | }
234 | }
235 | if (userdata.auth_token === "") {
236 | winston.error('ERROR')
237 | throw 'Could somehow not find a auth token in your session...'
238 | }
239 | } else {
240 | winston.error('ERROR')
241 | throw 'Could somehow not find a session...'
242 | }
243 |
244 |
245 | let auth = 'OAuth ' + userdata.auth_token
246 | let head = {
247 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
248 | Authorization: auth
249 | }
250 | await axios.get('https://id..tv/oauth2/validate', {headers: head, raxConfig: retryConfig})
251 | .then(function (response){
252 | let response_data = response.data
253 | userdata.userid = response_data.user_id
254 | userdata.clientid = response_data.client_id
255 | })
256 | .catch(function (error) {
257 | winston.error(chalk.red('ERROR: Could not validate your auth token...'))
258 | throw error.response.status + ' ' + error.response.statusText + ' ' + error.response.data.message
259 | })
260 |
261 |
262 | }
--------------------------------------------------------------------------------
/src/functions/startWatching.ts:
--------------------------------------------------------------------------------
1 | import winston from "winston";
2 | import chalk from "chalk";
3 | import {userdata} from "../index" ;
4 | import {WatchingEventHandlerStart} from "./handler/watchpageHandler";
5 |
6 | export async function startWatching() {
7 | let channelLogin:string = ''
8 | for await (const Drops of userdata.drops) {
9 | if (Drops.dropname === userdata.startDrop) {
10 | channelLogin = Drops.foundlivech[0]
11 | }
12 | }
13 |
14 | winston.silly(" ")
15 | winston.info(chalk.gray('Starting to watch..'), {event: "progress"})
16 |
17 | //Start WatchingEventHandler
18 | await WatchingEventHandlerStart(channelLogin)
19 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export const version = "2.0.0.4";
2 | import {userdataclass} from "./Data/userdata";
3 | export let userdata = new userdataclass();
4 | import chalk from "chalk";
5 | import CheckVersion from "./Checks/versionCheck"
6 | import GetSettings, {logimportantvalues} from "./functions/get/getSettings"
7 | import GetWatchOption from "./functions/get/getWatchOption"
8 | import {askWhatDropToStart, askWhatGameToWatch, getDrops} from "./functions/get/getDrops"
9 | import {startWatching} from "./functions/startWatching";
10 | import {login} from "./functions/login/defaultlogin";
11 | import fs from "fs";
12 | import {getCustomChannel} from "./functions/get/getCustomChannel";
13 | import {CustomEventHandlerStart} from "./functions/handler/custompageHandler";
14 | import {validateAuthToken} from "./Checks/validateAuthToken";
15 | import {matchArgs, setArgs} from "./functions/get/getArgs";
16 | import * as rax from 'retry-axios';
17 | import {retryConfig} from "./utils/util";
18 | const winston = require('winston');
19 | const GQL = require("@zaarrg/gql-dropbot").Init();
20 |
21 | (async () => {
22 | //Get Settings
23 | await setArgs();
24 | await GetSettings();
25 | await matchArgs();
26 | await setRetries();
27 | await logimportantvalues()
28 | await CheckVersion(version)
29 | //Http Keep Alive
30 | if (userdata.settings.UseKeepAlive) keepAlive();
31 | //Login
32 | await login()
33 | //Validate
34 | await validateAuthToken()
35 | //Get Watch Option
36 | if (!userdata.settings.displayless) {
37 | await GetWatchOption()
38 | await watchoptionSwitch()
39 | } else {
40 | if (userdata.settings.ForceCustomChannel) {
41 | if (fs.existsSync('./CustomChannels.json')) {
42 | userdata.watch_option = 'Custom Channels'
43 | } else {
44 | winston.warn(chalk.yellow('Cant force custom channels without a CustomChannels.json'))
45 | userdata.watch_option = 'Drops'
46 | }
47 | } else {
48 | userdata.watch_option = 'Drops'
49 | }
50 | await watchoptionSwitch()
51 | }
52 | winston.info(chalk.gray('Idle!'))
53 | })();
54 |
55 |
56 | async function watchoptionSwitch() {
57 | switch (userdata.watch_option) {
58 | case "Drops":
59 | //What Drops
60 | await askWhatGameToWatch(false)
61 | //Get The Drops of the Game
62 | await getDrops(userdata.game, true)
63 | if (userdata.settings.displayless) {
64 | await askWhatDropToStart(true, true, true, false)
65 | } else {
66 | await askWhatDropToStart(false, true, true, false)}
67 | await startWatching()
68 | break;
69 | case "Custom Channels":
70 | await getCustomChannel()
71 | await CustomEventHandlerStart(userdata.startDrop)
72 | break;
73 | }
74 | }
75 |
76 | async function setRetries() {
77 | await GQL.SetRetryTimeout(userdata.settings.RetryDelay).then(() => {
78 | retryConfig.retryDelay = userdata.settings.RetryDelay;
79 | rax.attach();
80 | })
81 | }
82 |
83 | function keepAlive(port = process.env.PORT) {
84 | const express = require('express');
85 | const app = express()
86 | app.get("/", (req: any, res: any) => res.send("DropBot is alive"))
87 | app.listen(port, () => winston.info(`App listening on port ${port || 0}`))
88 | }
--------------------------------------------------------------------------------
/src/start.bat:
--------------------------------------------------------------------------------
1 | npm run start:dev
2 | pause
--------------------------------------------------------------------------------
/src/utils/util.ts:
--------------------------------------------------------------------------------
1 | import * as rax from "retry-axios";
2 | import winston from "winston";
3 | import {userdata} from "../index";
4 |
5 | const chalk = require("chalk");
6 | const fs = require("fs");
7 |
8 | export function validPath(str: string) {
9 | if (fs.existsSync(str) && str.endsWith('.exe')) {
10 | return true
11 | } else {
12 | return "Please provide a Valid Path..."
13 | }
14 | }
15 |
16 | export function validURL(str: string) {
17 | if (str.startsWith("https://www..tv/")) {
18 | return true
19 | } else {
20 | return "Please provide a Valid URL..."
21 | }
22 | }
23 |
24 | export function getRandomInt(max: number) {
25 | return Math.floor(Math.random() * Math.floor(max));
26 | }
27 |
28 | export function statustoString(status: boolean) {
29 | if(!status) {
30 | return chalk.red("Offline")
31 | } else {
32 | return chalk.greenBright("Live")
33 | }
34 | }
35 |
36 | export function claimedstatustoString (streamer: boolean) {
37 | return (streamer) ? chalk.greenBright.italic('Claimed') : chalk.red.italic("Unclaimed")
38 | }
39 |
40 | export function livechresponse (foundlivechs: Array) {
41 | if (foundlivechs.length >= 1) {
42 | return chalk.cyanBright(foundlivechs[0])
43 | } else if (foundlivechs.length === 0) {
44 | return chalk.cyan('No Channel Live')
45 | }
46 | }
47 |
48 | export function minutestoPercent(timewatched: number, maxtime: number) {
49 | let result = (100/maxtime)*timewatched
50 | let resultr = Math.round((result + Number.EPSILON) * 100) / 100;
51 | return resultr
52 | }
53 |
54 | export async function delay(ms: number) {
55 | return await new Promise(resolve => setTimeout(resolve, ms));
56 | }
57 |
58 | export let retryConfig = {
59 | retry: 3,
60 | noResponseRetries: 3,
61 | retryDelay: userdata.settings.RetryDelay,
62 | statusCodesToRetry: [[100, 199], [429, 429, 400], [500, 599]],
63 | httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST'],
64 | onRetryAttempt: (err:any) => {
65 | const cfg = rax.getConfig(err);
66 | winston.info(chalk.yellow('Failed axios Request... Retrying in '+ Math.round(((cfg?.retryDelay)!/1000) * 100)/100 + ' seconds... Try: ' + cfg?.currentRetryAttempt + "/3 " + err), {event: "requestRetry"});
67 | },
68 | backoffType: 'static' as const
69 | }
70 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2016",
4 | "lib": [
5 | "es6",
6 | "dom"
7 | ],
8 | "module": "commonjs",
9 | "rootDir": "src",
10 | "resolveJsonModule": true,
11 | "allowJs": true,
12 | "outDir": "build",
13 | "esModuleInterop": true,
14 | "forceConsistentCasingInFileNames": false,
15 | "strict": true,
16 | "noImplicitAny": true,
17 | "skipLibCheck": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/twitch.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zaarrg/DropBot/67e09db0848a4ea196ab5a0c0d041da1bc382637/twitch.ico
--------------------------------------------------------------------------------