├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── .idea ├── .gitignore ├── TTVDropBot-v2.iml ├── discord.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── Dockerfile ├── Procfile ├── app.json ├── package-lock.json ├── package.json ├── readme.md ├── src ├── Checks │ ├── claimCheck.ts │ ├── dateCheck.ts │ ├── liveCheck.ts │ ├── pointsCheck.ts │ ├── samepercentCheck.ts │ ├── validateAuthToken.ts │ └── versionCheck.ts ├── Data │ └── userdata.ts ├── Pages │ └── loginPage.ts ├── functions │ ├── findLiveChannel.ts │ ├── get │ │ ├── getArgs.ts │ │ ├── getCurrentDrop.ts │ │ ├── getCustomChannel.ts │ │ ├── getSettings.ts │ │ ├── getTwitchDrops.ts │ │ └── getWatchOption.ts │ ├── handler │ │ ├── custompageHandler.ts │ │ ├── restartHandler.ts │ │ ├── watchpageHandler.ts │ │ └── webHookHandler.ts │ ├── logger │ │ └── logger.ts │ ├── login │ │ └── defaultlogin.ts │ └── startWatching.ts ├── index.ts ├── start.bat └── utils │ └── util.ts ├── tsconfig.json └── twitch.ico /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | # Publish `main` as Docker `latest` image. 6 | branches: 7 | - main 8 | 9 | # Publish `v1.2.3` tags as releases. 10 | tags: 11 | 12 | # Run tests for any PRs. 13 | pull_request: 14 | 15 | env: 16 | IMAGE_NAME: dropbot 17 | 18 | jobs: 19 | # Run tests. 20 | # See also https://docs.docker.com/docker-hub/builds/automated-testing/ 21 | test: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Run tests 28 | run: | 29 | if [ -f docker-compose.test.yml ]; then 30 | docker-compose --file docker-compose.test.yml build 31 | docker-compose --file docker-compose.test.yml run sut 32 | else 33 | docker build . --file Dockerfile 34 | fi 35 | 36 | # Push image to GitHub Packages. 37 | # See also https://docs.docker.com/docker-hub/builds/ 38 | push: 39 | # Ensure test job passes before pushing image. 40 | needs: test 41 | 42 | runs-on: ubuntu-latest 43 | if: github.event_name == 'push' 44 | 45 | steps: 46 | - uses: actions/checkout@v2 47 | 48 | - name: Build image 49 | run: docker build . --file Dockerfile --tag $IMAGE_NAME 50 | 51 | - name: Log into registry 52 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 53 | 54 | - name: Push image 55 | run: | 56 | IMAGE_ID=ghcr.io/${{ github.repository }}/$IMAGE_NAME 57 | 58 | # Change all uppercase to lowercase 59 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 60 | 61 | # Strip git ref prefix from version 62 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 63 | 64 | # Strip "v" prefix from tag name 65 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 66 | 67 | # Use Docker `latest` tag convention 68 | [ "$VERSION" == "main" ] && VERSION=latest 69 | 70 | echo IMAGE_ID=$IMAGE_ID 71 | echo VERSION=$VERSION 72 | 73 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 74 | docker push $IMAGE_ID:$VERSION 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.npmrc 3 | /build/ 4 | /-session.json 5 | /CustomChannels.json 6 | /src/**/*.bat 7 | *.log 8 | /package/* 9 | .npmrc 10 | /settings.json 11 | src/-session.json 12 | .idea/codestream.xml 13 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/TTVDropBot-v2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-buster-slim as compiler 2 | WORKDIR /usr/src/app 3 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 4 | && apt-get -y install --no-install-recommends gnutls-bin git ca-certificates 5 | COPY . . 6 | RUN npm install 7 | RUN npm run build 8 | 9 | FROM node:16-buster-slim as runner 10 | WORKDIR /usr/src/app/ 11 | COPY --from=compiler /usr/src/app/package*.json ./ 12 | COPY --from=compiler /usr/src/app/build ./build 13 | RUN npm ci --production 14 | RUN npm cache clean --force 15 | RUN npm install 16 | ENV NODE_ENV="production" 17 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: node build/index.js --displayless -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DropBot", 3 | "description": "Drop Bot. Automaticlly Farms every Drop / Campaign available like Rust for ya and now also your Custom Channel's ", 4 | "keywords": [ 5 | "Bot", 6 | "Webhook" 7 | ], 8 | "repository": "https://github.com/Zaarrg/DropBot", 9 | "logo": "https://i.imgur.com/2WtgNe4.png" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropbot-v2", 3 | "version": "2.0.0", 4 | "description": "Drop Bot", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node build/index.js --displayless", 8 | "start:dev": "ts-node ./src/index.ts", 9 | "build": "rimraf ./build && tsc", 10 | "package": "rimraf ./package/DropBot && npm run build && pkg -o ./package/DropBot/DropBot --config package.json -t node14-win-x64 ./build/index.js", 11 | "packageforlinux": "rimraf ./package/DropBot-linux && npm run build && pkg -o ./package/DropBot-linux/DropBot-linux-x64 --config package.json -t node14-linux-x64 ./build/index.js", 12 | "start:production": "npm run build && node build/index.js", 13 | "postinstall": "rimraf ./node_modules/puppeteer/.local-chromium" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Zaarrg/DropBot.git" 18 | }, 19 | "engines": { 20 | "node": "16.x" 21 | }, 22 | "bin": { 23 | "dropbot": "./src/index.ts" 24 | }, 25 | "author": "Zarg", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/Zaarrg/DropBot/issues" 29 | }, 30 | "homepage": "https://github.com/Zaarrg/DropBot#readme", 31 | "devDependencies": { 32 | "@types/node": "^17.0.23", 33 | "@types/yargs": "^17.0.10", 34 | "pkg": "^5.5.2", 35 | "rimraf": "^3.0.2", 36 | "ts-node": "^10.7.0", 37 | "typescript": "^4.6.3" 38 | }, 39 | "dependencies": { 40 | "@zaarrg/gql-dropbot": "github:zaarrg/gql-dropbot", 41 | "axios": "^0.26.1", 42 | "chalk": "^4.1.2", 43 | "chrome-paths": "^1.0.1", 44 | "express": "^4.18.1", 45 | "inquirer": "^8.2.2", 46 | "js-base64": "^3.7.2", 47 | "puppeteer-core": "^17.1.3", 48 | "retry-axios": "^2.6.0", 49 | "wait-console-input": "^0.1.7", 50 | "winston": "^3.7.2", 51 | "yargs": "^17.4.1" 52 | }, 53 | "pkg": { 54 | "assets": [ 55 | "node_modules/puppeteer-core/**/*.*", 56 | "node_modules/@zaarrg/**/*.*" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Bear Stone Smart Home 5 |
6 | DropBot 7 |

8 |

Watches Drops for you!

9 |

This Project is currently not maintained and will most likely not work!

10 | 11 |
12 |
13 | 14 | 15 | 16 |

17 |
18 | 19 | @Zaarrg/DropBot issues 20 | 21 | 22 | @Zaarrg/DropBot pull requests 23 | 24 | 25 | Zaarrg/DropBot requests 26 | 27 |
28 | 29 | Discord 30 | 31 | 32 | Discord 33 | 34 |

35 | 36 |
37 | 38 | ![DropBot](https://i.imgur.com/9icOyNB.png "DropBot") 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 |

166 | 167 | ![Drops](https://i.imgur.com/ra3zm1x.png) 168 | 169 | **2. Step** 170 | 171 |

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 |

174 | 175 | ![Drops](https://i.imgur.com/DRqIkpz.png) 176 | 177 | **3. Step** 178 |

179 | Select the campaign you want to start watching. If you want to only watch certain campaign and not all refer to Settings: Priority list
180 |

181 | 182 | ![Drops](https://i.imgur.com/CMuV729.png) 183 | 184 | **4. Step** 185 |

186 | Select the Drop you want to start watching.
187 |

188 | 189 | ![Drops](https://i.imgur.com/DzB5qjX.png) 190 | 191 | **5. Step** 192 |

193 | 🎉 Enjoy! You are successfully watching your drop.
194 |

195 | 196 | ![Drops](https://i.imgur.com/iNmvIZc.png) 197 | 198 | 199 | 200 |

Step by Step Usage: Custom Channels

201 | 202 | **1. Step** 203 |

204 | Select Custom Channels to start watching them.
205 |

206 | 207 | ![Drops](https://i.imgur.com/DRqIkpz.png) 208 | 209 | **2. Step** 210 |

211 | Fill in the needed information to add a Channel. They can always be modified in the customchannel.json
212 |

213 | 214 | ![Drops](https://i.imgur.com/kBabjJL.png) 215 | 216 | **3. Step** 217 |

218 | Select the Channel you want to start. The bot will switch between the Custom Channels, if one goes offline.
219 |

220 | 221 | ![Drops](https://i.imgur.com/AZt3xpU.png) 222 | 223 | **4. Step** 224 |

225 | 🎉 Enjoy! You are successfully watching your Custom Channel.
226 |

227 | 228 | ![Drops](https://i.imgur.com/k95h9Tu.png) 229 | 230 | 231 |

Step by Step Usage: Heroku

232 | 233 |

234 | ⚠️ Only Recommended for advanced users. ⚠️
235 |

236 | 237 | **1. Step** 238 |

239 | Click on the Deploy to Heroku Button at the top of the Readme
240 |

241 | 242 | ![Drops](https://i.imgur.com/1ll6yjV.png) 243 | 244 | **2. Step** 245 |

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 |

249 | 250 | ![Drops](https://i.imgur.com/oIm3m52.png) 251 | 252 | **3. Step** 253 |

254 | Go to the Resources tab and disable the web dyno and enable the worker instead
255 |

256 | 257 | ![Drops](https://i.imgur.com/5XeKXRC.png) 258 | 259 | **4. Step** 260 |

261 | Click on more in the top right corner and then on Run console.
262 | Type in bash and click Run.
263 |

264 | 265 | ![Drops](https://i.imgur.com/Q7mArVd.png) 266 | 267 | **5. Step** 268 |

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 |

272 | 273 | ![Drops](https://i.imgur.com/qfJV0OQ.png) 274 | 275 | **6. Step** 276 | 277 |

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 |

283 | 284 | ![Drops](https://i.imgur.com/EnB36ih.png) 285 | 286 | **7. Step** 287 |

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 | ![Drops](https://i.imgur.com/7Jrsojx.png) 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 | ![Drops](https://i.imgur.com/kBabjJL.png) 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 | [![GitHub's followers](https://img.shields.io/github/followers/Zaarrg.svg?style=social)](https://github.com/Zaarrg) 507 | [![GitHub stars](https://img.shields.io/github/stars/Zaarrg/DropBot.svg?style=social)](https://github.com/Zaarrg/DropBot/stargazers) 508 | [![GitHub watchers](https://img.shields.io/github/watchers/Zaarrg/DropBot.svg?style=social)](https://github.com/Zaarrg/DropBot/watchers) 509 | [![GitHub forks](https://img.shields.io/github/forks/Zaarrg/DropBot.svg?style=social)](https://github.com/Zaarrg/DropBot/network/members) 510 | 511 | If you like my work feel free to buy me a coffee. ☕ 512 | 513 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](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 --------------------------------------------------------------------------------