├── .gitattributes ├── .prettierrc.json ├── README.md ├── utils ├── init.js └── cli.js ├── package.json ├── LICENSE ├── .gitignore └── index.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "arrowParens": "avoid", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "useTabs": true, 7 | "tabWidth": 4, 8 | "semi": true 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddgmail 2 | An unofficial CLI for DuckDuckGo Email Protection. 3 | 4 | # Usage 5 | - `npx ddgmail` 6 | - `npm i -g ddgmail`, `ddgmail` 7 | 8 | # Features 9 | - Categorize your private duck addresses 10 | - Join the waitlist without using a mobile device 11 | - Change your forwarding address 12 | - and more... 13 | 14 | # Demos 15 | - [Authentication and categories](https://youtu.be/YMgZ1fdiSEw) 16 | - [Waitlist](https://youtu.be/-raXXgjGuWM) 17 | - [Forwarding Address](https://youtu.be/-MwSPf7geMk) 18 | -------------------------------------------------------------------------------- /utils/init.js: -------------------------------------------------------------------------------- 1 | import { readFile } from "fs/promises"; 2 | const pkg = JSON.parse( 3 | await readFile(new URL("./../package.json", import.meta.url)) 4 | ); 5 | import welcome from "cli-welcome"; 6 | import unhandled from "cli-handle-unhandled"; 7 | 8 | export default function () { 9 | unhandled(); 10 | welcome({ 11 | title: `DuckDuckGo Mail`, 12 | tagLine: `by lemons`, 13 | description: pkg.description, 14 | version: pkg.version, 15 | bgColor: "#36BB09", 16 | color: "#000000", 17 | bold: true, 18 | clear: false 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "ddgmail", 4 | "description": "An unofficial CLI for DuckDuckGo Email Protection.", 5 | "version": "1.3.1", 6 | "license": "MIT", 7 | "bin": { 8 | "ddgmail": "index.js" 9 | }, 10 | "author": { 11 | "name": "lem6ns", 12 | "email": "sensei@duck.com", 13 | "url": "https://lem6ns.github.io" 14 | }, 15 | "keywords": [ 16 | "ddgmail", 17 | "duckduckgo", 18 | "ddg", 19 | "mail", 20 | "email", 21 | "lem6ns" 22 | ], 23 | "files": [ 24 | "index.js", 25 | "utils" 26 | ], 27 | "scripts": { 28 | "format": "prettier --write \"./**/*.{js,json}\"" 29 | }, 30 | "dependencies": { 31 | "chalk": "^5.0.1", 32 | "cli-alerts": "^1.2.2", 33 | "cli-handle-error": "^4.4.0", 34 | "cli-handle-unhandled": "^1.1.1", 35 | "cli-meow-help": "^2.0.2", 36 | "cli-welcome": "^2.2.2", 37 | "clipboardy": "^3.0.0", 38 | "fuse.js": "^6.5.3", 39 | "inquirer": "^8.2.1", 40 | "launch-editor": "^2.3.0", 41 | "meow": "^9.0.0", 42 | "node-fetch": "^3.2.3", 43 | "ora": "^6.1.0" 44 | }, 45 | "devDependencies": { 46 | "prettier": "^2.6.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 lem6ns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM # 2 | ########## 3 | # Ignore all directories called node_modules in current folder and any subfolders. 4 | node_modules/ 5 | /node_modules/ 6 | 7 | # Packages # 8 | ############ 9 | *.7z 10 | *.dmg 11 | *.gz 12 | *.bz2 13 | *.iso 14 | *.jar 15 | *.rar 16 | *.tar 17 | *.zip 18 | *.tgz 19 | *.map 20 | 21 | # Logs and databases # 22 | ###################### 23 | *.log 24 | *.sql 25 | *.env 26 | 27 | # OS generated files # 28 | ###################### 29 | **.DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | ._* 34 | **settings.dat* 35 | 36 | # Vim generated files # 37 | ###################### 38 | *.un~ 39 | 40 | # SASS # 41 | ########## 42 | **/.sass-cache 43 | **/.sass-cache/* 44 | **/.map 45 | 46 | # Composer # 47 | ########## 48 | !assets/js/vendor/ 49 | wpcs/ 50 | /vendor/ 51 | 52 | # Bower # 53 | ########## 54 | assets/bower_components/* 55 | 56 | # Codekit # 57 | ########## 58 | /codekit-config.json 59 | *.codekit 60 | **.codekit-cache/* 61 | 62 | # Compiled Files and Build Dirs # 63 | ########## 64 | /README.html 65 | 66 | # PhpStrom Project Files # 67 | .idea/ 68 | library/vendors/composer 69 | assets/img/.DS_Store 70 | 71 | # VSCode related files # 72 | # .vscode 73 | -------------------------------------------------------------------------------- /utils/cli.js: -------------------------------------------------------------------------------- 1 | import meow from "meow"; 2 | import meowHelp from "cli-meow-help"; 3 | 4 | const flags = { 5 | version: { 6 | type: `boolean`, 7 | alias: `v`, 8 | desc: `Print CLI version` 9 | }, 10 | username: { 11 | type: `string`, 12 | alias: `u`, 13 | desc: `Username of duck.com account`, 14 | default: `` 15 | }, 16 | accessToken: { 17 | type: `string`, 18 | alias: `t`, 19 | desc: `Access token of duck.com account`, 20 | default: `` 21 | }, 22 | noClipboard: { 23 | type: `boolean`, 24 | alias: `n`, 25 | desc: `Don't automatically copy to clipboard`, 26 | default: false 27 | }, 28 | category: { 29 | type: `string`, 30 | alias: `c`, 31 | desc: `Category of the email`, 32 | default: `E-Mail` 33 | } 34 | }; 35 | 36 | const commands = { 37 | help: { desc: `Print help info` }, 38 | new: { desc: `Create a new private email address` }, 39 | amount: { 40 | desc: `Get the amount of private email addresses you have generated (Cannot use access token)` 41 | }, 42 | access: { desc: `Get your access token` }, 43 | config: { desc: `Open config.json in your default editor` }, 44 | "config-table": { desc: `Print config as a table` }, 45 | "config-set": { desc: `Set a config value` }, 46 | "config-get": { desc: `Get a config value` }, 47 | "config-reset": { desc: `Reset config to default` }, 48 | emails: { desc: `Get a list of emails you have generated using ddgmail` }, 49 | "email-delete": { 50 | desc: `Delete an email you have generated using ddgmail` 51 | }, 52 | "change-forwarding-address": { 53 | desc: `Change the forwarding address of your duck.com account` 54 | }, 55 | "category-search": { desc: `Search for a category` }, 56 | "category-change": { desc: `Change category of an email` }, 57 | join: { desc: `Join the waitlist` }, 58 | check: { desc: `Check if you have a chance to get email protection` }, 59 | "get-code": { desc: `Get code` }, 60 | auth: { desc: `Manually trigger authentication` } 61 | }; 62 | 63 | const helpText = meowHelp({ 64 | name: `ddgmail`, 65 | flags, 66 | commands 67 | }); 68 | 69 | const options = { 70 | inferType: true, 71 | description: false, 72 | hardRejection: false, 73 | flags 74 | }; 75 | 76 | export default meow(helpText, options); 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * ddgmail 5 | * An unofficial CLI for DuckDuckGo Email Protection. 6 | * 7 | * @author lem6ns 8 | */ 9 | 10 | import init from "./utils/init.js"; 11 | import cli from "./utils/cli.js"; 12 | import fetch, { FormData } from "node-fetch"; 13 | import inquirer from "inquirer"; 14 | import alert from "cli-alerts"; 15 | import clipboard from "clipboardy"; 16 | import ora from "ora"; 17 | import fs from "fs"; 18 | import launch from "launch-editor"; 19 | import Fuse from "fuse.js"; 20 | 21 | const input = cli.input; 22 | const flags = cli.flags; 23 | const { username, noClipboard } = flags; 24 | const API_URL = "https://quack.duckduckgo.com/api"; 25 | const userDataFolder = 26 | process.env.APPDATA || 27 | (process.platform == "darwin" 28 | ? process.env.HOME + "/Library/Preferences" 29 | : process.env.HOME + "/.local/share"); 30 | const settingsFolder = `${userDataFolder}/lemons/ddgmail`; 31 | const settingsFile = `${settingsFolder}/settings.json`; 32 | 33 | // #region APIs 34 | function checkSettingsAndCreateSettingsFileIfUnexistent() { 35 | if (!fs.existsSync(settingsFile)) createSettingsFile(); 36 | } 37 | 38 | function createSettingsFile() { 39 | fs.mkdirSync(settingsFolder, { recursive: true }); 40 | fs.writeFileSync( 41 | settingsFile, 42 | JSON.stringify({ 43 | username: "", 44 | accessToken: "", 45 | generatedEmails: [], 46 | amountGenerated: 0, 47 | waitlist: { 48 | timestamp: 0, 49 | token: "", 50 | code: "" 51 | } 52 | }), 53 | null, 54 | 4 55 | ); 56 | } 57 | 58 | const settings = { 59 | getSetting: key => { 60 | checkSettingsAndCreateSettingsFileIfUnexistent(); 61 | const settings = JSON.parse(fs.readFileSync(settingsFile)); 62 | return settings[key]; 63 | }, 64 | changeSetting: (key, value) => { 65 | checkSettingsAndCreateSettingsFileIfUnexistent(); 66 | const settings = JSON.parse(fs.readFileSync(settingsFile)); 67 | const oldSettings = settings; 68 | settings[key] = value; 69 | fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 4)); 70 | return { oldSettings, newSettings: settings }; 71 | }, 72 | getAllSettings: () => { 73 | checkSettingsAndCreateSettingsFileIfUnexistent(); 74 | return JSON.parse(fs.readFileSync(settingsFile)); 75 | } 76 | }; 77 | 78 | const email = { 79 | private: { 80 | create: async (accessToken, category) => { 81 | let { address } = await fetch(`${API_URL}/email/addresses`, { 82 | method: "POST", 83 | headers: { 84 | Authorization: "Bearer " + accessToken 85 | } 86 | }).then(r => r.json()); 87 | if (!address) 88 | return alert({ type: `error`, msg: `Something went wrong.` }); 89 | 90 | address = `${address}@duck.com`; 91 | settings.changeSetting( 92 | "amountGenerated", 93 | settings.getSetting("amountGenerated") + 1 94 | ); 95 | settings.changeSetting("generatedEmails", [ 96 | ...settings.getSetting("generatedEmails"), 97 | { category, address } 98 | ]); 99 | if (settings.getSetting("accessToken") !== accessToken) 100 | settings.changeSetting("accessToken", accessToken); 101 | return `${address}`; 102 | }, 103 | getEmailAmount: async username => { 104 | return (await auth(username)).stats["addresses_generated"]; 105 | } 106 | }, 107 | signUp: async (code, forwardingEmail, emailChoice) => {} 108 | }; 109 | 110 | const waitlist = { 111 | join: async () => { 112 | const waitlistSettings = settings.getSetting("waitlist"); 113 | let overwrite = false; 114 | if (waitlistSettings.token && waitlistSettings.timestamp) { 115 | overwrite = await inquirer 116 | .prompt([ 117 | { 118 | type: "confirm", 119 | name: "overwrite", 120 | message: 121 | "You already have joined the waitlist. Overwrite?" 122 | } 123 | ]) 124 | .then(answer => answer.overwrite); 125 | } 126 | if (!overwrite) return; 127 | const spinner = ora("Joining waitlist...").start(); 128 | const waitlist = await fetch(`${API_URL}/auth/waitlist/join`, { 129 | method: "POST" 130 | }).then(r => r.json()); 131 | const { timestamp, token } = waitlist; 132 | 133 | let currentWaitlistSettings = settings.getSetting("waitlist"); 134 | currentWaitlistSettings.timestamp = timestamp; 135 | currentWaitlistSettings.token = token; 136 | settings.changeSetting("waitlist", currentWaitlistSettings); 137 | 138 | spinner.succeed(`Successfully joined waitlist.`); 139 | return waitlist; 140 | }, 141 | check: async () => { 142 | const waitlist = settings.getSetting("waitlist"); 143 | if (!waitlist.timestamp) 144 | return alert({ 145 | type: `error`, 146 | msg: `You have not joined the waitlist. Use the join command.` 147 | }); 148 | const { timestamp } = await fetch( 149 | `https://quack.duckduckgo.com/api/auth/waitlist/status` 150 | ).then(r => r.json()); 151 | if (timestamp >= waitlist.timestamp) { 152 | return alert({ 153 | type: `info`, 154 | msg: `You might be able to get a invite code. Use the get-code command.` 155 | }); 156 | } 157 | return alert({ 158 | type: `info`, 159 | msg: `You are not able to get an invite code yet. Try again later.` 160 | }); 161 | }, 162 | getCode: async () => { 163 | const waitlist = settings.getSetting("waitlist"); 164 | if (!waitlist.timestamp) 165 | return alert({ 166 | type: `error`, 167 | msg: `You have not joined the waitlist. Use the join command.` 168 | }); 169 | const code = await fetch( 170 | `https://quack.duckduckgo.com/api/auth/waitlist/code`, 171 | { 172 | method: "POST", 173 | body: { 174 | token: waitlist.token 175 | } 176 | } 177 | ).then(r => r.json()); 178 | if (JSON.stringify(code) === "{}") 179 | return alert({ 180 | type: `error`, 181 | msg: `Failed to get code. Use the check command if you are able to get one in the future.` 182 | }); 183 | 184 | console.log(code); 185 | } 186 | }; 187 | 188 | async function changeForwardingAddress(newAddress, user) { 189 | if (username) user = username; 190 | 191 | await fetch( 192 | `https://quack.duckduckgo.com/api/auth/confirmlink?&action=change_email_address&user=${user}&action=change_email_address&username=${user}` 193 | ); 194 | alert({ 195 | type: `info`, 196 | msg: `Please check your email for an OTP code from DuckDuckGo. This code is only sent to DuckDuckGo, nowhere else.` 197 | }); 198 | let { otp } = await inquirer.prompt([ 199 | { 200 | type: "input", 201 | name: "otp", 202 | message: "Enter OTP code:", 203 | validate: value => { 204 | if (value.trim().split(" ").length === 4) { 205 | return true; 206 | } 207 | } 208 | } 209 | ]); 210 | otp = otp.trim().replace(/ /g, "+"); 211 | 212 | const spinner = ora("Authenticating").start(); 213 | 214 | const authResp = await fetch( 215 | `https://quack.duckduckgo.com/api/auth/confirm?action=change_email_address&otp=${otp}&user=${user}` 216 | ).then(r => r.json()); 217 | if (authResp.status != "authenticated") 218 | return spinner.fail("An error occured (Invalid OTP Error)"); 219 | 220 | const dashboardResp = await fetch(`${API_URL}/email/dashboard`, { 221 | headers: { 222 | Authorization: `Bearer ${authResp.token}` 223 | } 224 | }).then(r => r.json()); 225 | 226 | if (dashboardResp.error) { 227 | return alert({ type: "error", msg: JSON.stringify(dashboardResp) }); 228 | } 229 | spinner.succeed("Authentication successful!"); 230 | 231 | const validateEmail = await fetch( 232 | `${API_URL}/api/auth/validate-email-address?email=${newAddress}` 233 | ).then(r => r.json()); 234 | if (validateEmail.error) 235 | return alert({ 236 | type: "error", 237 | msg: `Something went wrong. (${validateEmail.error})` 238 | }); 239 | 240 | const formData = new FormData(); 241 | formData.set("email", newAddress); 242 | formData.set("disable_secure_reply", 0); 243 | 244 | const changeEmailResp = await fetch( 245 | `${API_URL}/email/change-email-address`, 246 | { 247 | method: "POST", 248 | headers: { 249 | Authorization: `Bearer ${authResp.token}` 250 | }, 251 | body: formData 252 | } 253 | ).then(r => r.json()); 254 | if (changeEmailResp.status != "changed") 255 | return alert({ 256 | type: "error", 257 | msg: `Something went wrong. (${JSON.stringify(changeEmailResp)})` 258 | }); 259 | 260 | return alert({ 261 | type: "success", 262 | msg: `Your forwarding address has been changed to ${newAddress} successfully.` 263 | }); 264 | } 265 | 266 | async function auth(user) { 267 | if (username) user = username; 268 | 269 | // send request to duckduckgo to send OTP to user 270 | await fetch(`${API_URL}/auth/loginlink?user=${user}`); // {} 271 | alert({ 272 | type: `info`, 273 | msg: `Please check your email for an OTP code from DuckDuckGo. This code is only sent to DuckDuckGo, nowhere else.` 274 | }); 275 | let { otp } = await inquirer.prompt([ 276 | { 277 | type: "input", 278 | name: "otp", 279 | message: "Enter OTP code:", 280 | validate: value => { 281 | if (value.trim().split(" ").length === 4) { 282 | return true; 283 | } 284 | } 285 | } 286 | ]); 287 | otp = otp.replace(/ /g, "+").trim(); 288 | 289 | const spinner = ora("Authenticating").start(); 290 | 291 | const authResp = await fetch( 292 | `${API_URL}/auth/login?otp=${otp}&user=${user}` 293 | ).then(r => r.json()); 294 | if (authResp.status == "authenticated") { 295 | const dashboardResp = await fetch(`${API_URL}/email/dashboard`, { 296 | headers: { 297 | Authorization: `Bearer ${authResp.token}` 298 | } 299 | }).then(r => r.json()); 300 | 301 | if (dashboardResp.error) { 302 | return alert({ type: "error", msg: JSON.stringify(dashboardResp) }); 303 | } 304 | 305 | spinner.succeed("Authentication successful!"); 306 | settings.changeSetting("username", user); 307 | settings.changeSetting( 308 | "accessToken", 309 | dashboardResp.user["access_token"] 310 | ); 311 | alert({ 312 | type: `success`, 313 | msg: `Set username & accessToken successfully.` 314 | }); 315 | return dashboardResp; 316 | } 317 | 318 | spinner.fail("An error occured (Invalid OTP Error)"); 319 | return authResp; 320 | } 321 | 322 | async function getUsername() { 323 | if (username) { 324 | settings.changeSetting("username", username); 325 | return username; 326 | } 327 | if (settings.getSetting("username")) return settings.getSetting("username"); 328 | const user = await inquirer 329 | .prompt([ 330 | { 331 | type: "input", 332 | name: "username", 333 | message: "Enter duck.com username:" 334 | } 335 | ]) 336 | .then(answer => answer.username); 337 | 338 | settings.changeSetting("username", user); 339 | return user; 340 | } 341 | // #endregion 342 | 343 | (async () => { 344 | init(); 345 | checkSettingsAndCreateSettingsFileIfUnexistent(); 346 | if (input.length == 0) cli.showHelp(0); 347 | 348 | for (const argument of input) { 349 | switch (argument) { 350 | case `help`: 351 | cli.showHelp(0); 352 | break; 353 | 354 | // #region Email 355 | case "new": 356 | const { category } = flags; 357 | let accessToken; 358 | if (flags.accessToken) { 359 | accessToken = flags.accessToken; 360 | } else if (settings.getSetting("accessToken")) { 361 | accessToken = settings.getSetting("accessToken"); 362 | } else { 363 | const user = await getUsername(); 364 | accessToken = (await auth(user)).user["access_token"]; 365 | } 366 | 367 | const address = await email.private.create( 368 | accessToken, 369 | category 370 | ); 371 | alert({ 372 | type: `success`, 373 | msg: `Successfully created a private email address (${address})${ 374 | category !== "E-Mail" 375 | ? ` with the category ${category}` 376 | : "" 377 | }. ${ 378 | !noClipboard 379 | ? "It has been copied to your clipboard." 380 | : "" 381 | }` 382 | }); 383 | if (!noClipboard) clipboard.writeSync(address); 384 | break; 385 | 386 | case "amount": 387 | const name = await getUsername(); 388 | const amount = await email.private.getEmailAmount(name); 389 | alert({ 390 | type: `success`, 391 | msg: `You have generated ${amount} private email addresses, and have generated ${settings.getSetting( 392 | "amountGenerated" 393 | )} using this tool.` 394 | }); 395 | break; 396 | 397 | case "emails": 398 | const emails = settings.getSetting("generatedEmails"); 399 | if (emails.length == 0) { 400 | return alert({ 401 | type: `error`, 402 | msg: `You have not generated any private email addresses yet.` 403 | }); 404 | } 405 | console.table(emails); 406 | break; 407 | 408 | case "category-change": 409 | let { cat, index } = await inquirer.prompt([ 410 | { 411 | type: "input", 412 | name: "cat", 413 | message: "Enter category:" 414 | }, 415 | { 416 | type: "input", 417 | name: "index", 418 | message: "Enter index number:" 419 | } 420 | ]); 421 | index = parseInt(index); 422 | 423 | settings.changeSetting( 424 | "generatedEmails", 425 | settings.getSetting("generatedEmails").map((email, i) => { 426 | if (i == index) email.category = cat; 427 | return email; 428 | }) 429 | ); 430 | 431 | alert({ 432 | type: `success`, 433 | msg: `Successfully changed category of email address ${ 434 | settings.getSetting("generatedEmails")[index].address 435 | } to ${cat}.` 436 | }); 437 | break; 438 | 439 | case "category-search": 440 | const { cate } = await inquirer.prompt([ 441 | { 442 | type: "input", 443 | name: "cate", 444 | message: "Enter category:" 445 | } 446 | ]); 447 | const filteredEmails = new Fuse( 448 | settings.getSetting("generatedEmails"), 449 | { 450 | keys: ["category"] 451 | } 452 | ) 453 | .search(cate) 454 | .map(i => i.item); 455 | console.table(filteredEmails); 456 | break; 457 | 458 | case "email-delete": 459 | let { i } = await inquirer.prompt([ 460 | { 461 | type: "input", 462 | name: "i", 463 | message: "Enter index number:" 464 | } 465 | ]); 466 | i = parseInt(i); 467 | 468 | settings.changeSetting( 469 | "generatedEmails", 470 | settings 471 | .getSetting("generatedEmails") 472 | .filter((email, index) => index != i) 473 | ); 474 | break; 475 | 476 | case "change-forwarding-address": 477 | const usrname = await getUsername(); 478 | const { newAddress } = await inquirer.prompt([ 479 | { 480 | type: "input", 481 | name: "newAddress", 482 | message: "Enter your new forwarding address:" 483 | } 484 | ]); 485 | return await changeForwardingAddress(newAddress, usrname); 486 | 487 | case "signup": 488 | break; 489 | // #endregion 490 | 491 | // #region Utils 492 | case "access": 493 | const user = await getUsername(); 494 | const authResp = await auth(user); 495 | if (authResp.error) 496 | return alert({ 497 | type: "error", 498 | msg: "An error occured (Invalid OTP Error)" 499 | }); 500 | const token = authResp.user["access_token"]; 501 | 502 | alert({ 503 | type: `success`, 504 | msg: `Your access token is: "${token}". ${ 505 | !noClipboard 506 | ? "This has been copied to your clipboard." 507 | : "" 508 | }` 509 | }); 510 | if (!noClipboard) clipboard.writeSync(token); 511 | break; 512 | 513 | case "auth": 514 | auth(); 515 | break; 516 | // #endregion 517 | 518 | // #region config commands 519 | case "config": 520 | launch(settingsFile, "code"); 521 | alert({ 522 | type: `success`, 523 | msg: `Opened ${settingsFile} in your default editor.` 524 | }); 525 | break; 526 | 527 | case "config-set": 528 | const { key, value } = await inquirer.prompt([ 529 | { 530 | type: "input", 531 | name: "key", 532 | message: "Enter the key of the setting to change:" 533 | }, 534 | { 535 | type: "input", 536 | name: "value", 537 | message: "Enter the new value of the setting:" 538 | } 539 | ]); 540 | if (settings.getSetting(key) !== undefined) { 541 | settings.changeSetting(key, value); 542 | alert({ 543 | type: `success`, 544 | msg: `Successfully changed setting "${key}" to "${value}".` 545 | }); 546 | } else { 547 | alert({ 548 | type: `error`, 549 | msg: `Setting "${key}" does not exist.` 550 | }); 551 | } 552 | break; 553 | 554 | case "config-get": 555 | const keyName = await inquirer 556 | .prompt([ 557 | { 558 | type: "input", 559 | name: "key", 560 | message: "Enter the key of the setting to get:" 561 | } 562 | ]) 563 | .then(answer => answer.key); 564 | const valueOut = settings.getSetting(keyName); 565 | if (valueOut !== undefined) { 566 | return alert({ 567 | type: `success`, 568 | msg: `The value of the setting "${keyName}" is: "${JSON.stringify( 569 | valueOut 570 | )}".` 571 | }); 572 | } 573 | return alert({ 574 | type: `error`, 575 | msg: `The setting "${keyName}" does not exist.` 576 | }); 577 | 578 | case "config-reset": 579 | const { reset } = await inquirer.prompt([ 580 | { 581 | type: "confirm", 582 | name: "reset", 583 | message: "Are you sure you want to reset all settings?" 584 | } 585 | ]); 586 | if (reset) createSettingsFile(); 587 | break; 588 | 589 | case "config-table": 590 | const allSettings = settings.getAllSettings(); 591 | const kv = Object.keys(allSettings).map(key => [ 592 | key, 593 | allSettings[key] 594 | ]); 595 | return console.table(kv); 596 | 597 | case "config-delete": 598 | const { del } = await inquirer.prompt([ 599 | { 600 | type: "confirm", 601 | name: "del", 602 | message: 603 | "Are you sure you want to delete the config file? Note: Running ddgemail again will regenerate the config file." 604 | } 605 | ]); 606 | if (del) { 607 | fs.unlinkSync(settingsFile); 608 | fs.rmSync(settingsFolder, { recursive: true }); 609 | alert({ 610 | type: `success`, 611 | msg: `Successfully deleted the config file.` 612 | }); 613 | } 614 | break; 615 | // #endregion 616 | 617 | // #region Waitlist 618 | case `join`: 619 | waitlist.join(); 620 | break; 621 | 622 | case `check`: 623 | waitlist.check(); 624 | break; 625 | 626 | case `get-code`: 627 | waitlist.getCode(); 628 | break; 629 | // #endregion 630 | 631 | default: 632 | cli.showHelp(0); 633 | break; 634 | } 635 | } 636 | })(); 637 | --------------------------------------------------------------------------------