@necrolab/dashboard 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.claude/settings.local.json +45 -0
  2. package/.eslintrc.js +24 -0
  3. package/.prettierignore +1 -0
  4. package/.prettierrc +10 -0
  5. package/.vscode/extensions.json +3 -0
  6. package/ICONS.md +21 -0
  7. package/README.md +65 -0
  8. package/backend/api.js +430 -0
  9. package/backend/auth.js +62 -0
  10. package/backend/batching.js +43 -0
  11. package/backend/endpoints.js +343 -0
  12. package/backend/index.js +23 -0
  13. package/backend/mock-data.js +66 -0
  14. package/backend/mock-src/classes/logger.js +112 -0
  15. package/backend/mock-src/classes/utils.js +42 -0
  16. package/backend/mock-src/ticketmaster.js +92 -0
  17. package/backend/validator.js +62 -0
  18. package/config/configs.json +20 -0
  19. package/config/filter.json +3 -0
  20. package/config/presale.csv +3 -0
  21. package/config/proxies.txt +6 -0
  22. package/config/used-codes.json +4 -0
  23. package/index.html +114 -0
  24. package/index.js +2 -0
  25. package/jsconfig.json +16 -0
  26. package/package.json +48 -0
  27. package/postcss.config.js +6 -0
  28. package/postinstall.js +9 -0
  29. package/public/android-chrome-192x192.png +0 -0
  30. package/public/android-chrome-512x512.png +0 -0
  31. package/public/apple-touch-icon.png +0 -0
  32. package/public/favicon-16x16.png +0 -0
  33. package/public/favicon-32x32.png +0 -0
  34. package/public/favicon.ico +0 -0
  35. package/public/flags/ae.svg +1 -0
  36. package/public/flags/at.svg +1 -0
  37. package/public/flags/au.svg +1 -0
  38. package/public/flags/be.svg +1 -0
  39. package/public/flags/ch.svg +1 -0
  40. package/public/flags/cz.svg +1 -0
  41. package/public/flags/de.svg +1 -0
  42. package/public/flags/dk.svg +1 -0
  43. package/public/flags/es.svg +1 -0
  44. package/public/flags/nl.svg +1 -0
  45. package/public/flags/no.svg +1 -0
  46. package/public/flags/nz.svg +1 -0
  47. package/public/flags/pl.svg +1 -0
  48. package/public/flags/se.svg +1 -0
  49. package/public/flags/uk.svg +1 -0
  50. package/public/flags/us.svg +1 -0
  51. package/public/img/award.svg +3 -0
  52. package/public/img/background.svg +14 -0
  53. package/public/img/bag_w.svg +12 -0
  54. package/public/img/banks/amex.svg +4 -0
  55. package/public/img/banks/mastercard.svg +4 -0
  56. package/public/img/banks/visa.svg +4 -0
  57. package/public/img/camera.svg +3 -0
  58. package/public/img/close.svg +3 -0
  59. package/public/img/controls/disable.svg +5 -0
  60. package/public/img/controls/enable.svg +5 -0
  61. package/public/img/groups.svg +3 -0
  62. package/public/img/hand.svg +3 -0
  63. package/public/img/key.svg +3 -0
  64. package/public/img/logo.png +0 -0
  65. package/public/img/logo_icon.png +0 -0
  66. package/public/img/logo_icon_2.png +0 -0
  67. package/public/img/logo_trans.png +0 -0
  68. package/public/img/loyalty.svg +3 -0
  69. package/public/img/mail.svg +3 -0
  70. package/public/img/pencil.svg +3 -0
  71. package/public/img/profile.svg +4 -0
  72. package/public/img/reload.svg +3 -0
  73. package/public/img/sandclock.svg +25 -0
  74. package/public/img/save.svg +5 -0
  75. package/public/img/savings.svg +3 -0
  76. package/public/img/scanner.svg +3 -0
  77. package/public/img/sell.svg +3 -0
  78. package/public/img/shield.svg +3 -0
  79. package/public/img/ski.svg +3 -0
  80. package/public/img/stadium.svg +8 -0
  81. package/public/img/stadium_w.svg +8 -0
  82. package/public/img/timer.svg +3 -0
  83. package/public/manifest.json +27 -0
  84. package/public/robots.txt +2 -0
  85. package/run +10 -0
  86. package/src/App.vue +307 -0
  87. package/src/assets/css/_input.scss +197 -0
  88. package/src/assets/css/main.scss +269 -0
  89. package/src/assets/css/tailwind.css +3 -0
  90. package/src/assets/img/award.svg +3 -0
  91. package/src/assets/img/background.svg +11 -0
  92. package/src/assets/img/camera.svg +3 -0
  93. package/src/assets/img/close.svg +3 -0
  94. package/src/assets/img/eyes/closed.svg +13 -0
  95. package/src/assets/img/eyes/open.svg +12 -0
  96. package/src/assets/img/groups.svg +3 -0
  97. package/src/assets/img/hand.svg +3 -0
  98. package/src/assets/img/key.svg +3 -0
  99. package/src/assets/img/logo.png +0 -0
  100. package/src/assets/img/logo_icon.png +0 -0
  101. package/src/assets/img/logo_icon_2.png +0 -0
  102. package/src/assets/img/logo_trans.png +0 -0
  103. package/src/assets/img/loyalty.svg +3 -0
  104. package/src/assets/img/mail.svg +3 -0
  105. package/src/assets/img/pencil.svg +3 -0
  106. package/src/assets/img/reload.svg +3 -0
  107. package/src/assets/img/savings.svg +3 -0
  108. package/src/assets/img/scanner.svg +3 -0
  109. package/src/assets/img/sell.svg +3 -0
  110. package/src/assets/img/shield.svg +3 -0
  111. package/src/assets/img/ski.svg +3 -0
  112. package/src/assets/img/square_check.svg +5 -0
  113. package/src/assets/img/square_uncheck.svg +5 -0
  114. package/src/assets/img/stadium.svg +8 -0
  115. package/src/assets/img/timer.svg +3 -0
  116. package/src/assets/img/wildcard.svg +7 -0
  117. package/src/components/Auth/LoginForm.vue +48 -0
  118. package/src/components/Editors/Account/Account.vue +119 -0
  119. package/src/components/Editors/Account/AccountCreator.vue +147 -0
  120. package/src/components/Editors/Account/AccountView.vue +87 -0
  121. package/src/components/Editors/Account/CreateAccount.vue +106 -0
  122. package/src/components/Editors/Profile/CreateProfile.vue +321 -0
  123. package/src/components/Editors/Profile/Profile.vue +142 -0
  124. package/src/components/Editors/Profile/ProfileCountryChooser.vue +75 -0
  125. package/src/components/Editors/Profile/ProfileView.vue +96 -0
  126. package/src/components/Editors/TagLabel.vue +16 -0
  127. package/src/components/Editors/TagToggle.vue +41 -0
  128. package/src/components/Filter/Filter.vue +409 -0
  129. package/src/components/Filter/FilterPreview.vue +236 -0
  130. package/src/components/Filter/PriceSortToggle.vue +105 -0
  131. package/src/components/Table/Header.vue +5 -0
  132. package/src/components/Table/Row.vue +5 -0
  133. package/src/components/Table/Table.vue +14 -0
  134. package/src/components/Table/index.js +4 -0
  135. package/src/components/Tasks/CheckStock.vue +62 -0
  136. package/src/components/Tasks/Controls/DesktopControls.vue +73 -0
  137. package/src/components/Tasks/Controls/MobileControls.vue +32 -0
  138. package/src/components/Tasks/Controls/index.js +3 -0
  139. package/src/components/Tasks/CreateTaskAXS.vue +339 -0
  140. package/src/components/Tasks/CreateTaskTM.vue +459 -0
  141. package/src/components/Tasks/MassEdit.vue +50 -0
  142. package/src/components/Tasks/QuickSettings.vue +167 -0
  143. package/src/components/Tasks/ScrapeVenue.vue +42 -0
  144. package/src/components/Tasks/Stats.vue +66 -0
  145. package/src/components/Tasks/Task.vue +296 -0
  146. package/src/components/Tasks/TaskLabel.vue +20 -0
  147. package/src/components/Tasks/TaskView.vue +126 -0
  148. package/src/components/Tasks/Utilities.vue +33 -0
  149. package/src/components/icons/Award.vue +8 -0
  150. package/src/components/icons/Bag.vue +8 -0
  151. package/src/components/icons/BagWhite.vue +8 -0
  152. package/src/components/icons/Box.vue +8 -0
  153. package/src/components/icons/Camera.vue +8 -0
  154. package/src/components/icons/Cart.vue +8 -0
  155. package/src/components/icons/Check.vue +5 -0
  156. package/src/components/icons/Checkmark.vue +11 -0
  157. package/src/components/icons/Click.vue +8 -0
  158. package/src/components/icons/Close.vue +21 -0
  159. package/src/components/icons/CloseX.vue +5 -0
  160. package/src/components/icons/Console.vue +13 -0
  161. package/src/components/icons/Down.vue +8 -0
  162. package/src/components/icons/Edit.vue +13 -0
  163. package/src/components/icons/Event.vue +8 -0
  164. package/src/components/icons/Expand.vue +8 -0
  165. package/src/components/icons/Filter.vue +13 -0
  166. package/src/components/icons/Gear.vue +8 -0
  167. package/src/components/icons/Group.vue +8 -0
  168. package/src/components/icons/Hand.vue +8 -0
  169. package/src/components/icons/Key.vue +21 -0
  170. package/src/components/icons/Logout.vue +13 -0
  171. package/src/components/icons/Loyalty.vue +8 -0
  172. package/src/components/icons/Mail.vue +8 -0
  173. package/src/components/icons/Menu.vue +8 -0
  174. package/src/components/icons/Pause.vue +5 -0
  175. package/src/components/icons/Pencil.vue +21 -0
  176. package/src/components/icons/Play.vue +8 -0
  177. package/src/components/icons/Plus.vue +8 -0
  178. package/src/components/icons/Profile.vue +18 -0
  179. package/src/components/icons/Reload.vue +7 -0
  180. package/src/components/icons/Sandclock.vue +33 -0
  181. package/src/components/icons/Savings.vue +8 -0
  182. package/src/components/icons/Scanner.vue +8 -0
  183. package/src/components/icons/Scrape.vue +8 -0
  184. package/src/components/icons/Sell.vue +21 -0
  185. package/src/components/icons/Shield.vue +8 -0
  186. package/src/components/icons/Shrink.vue +8 -0
  187. package/src/components/icons/Ski.vue +8 -0
  188. package/src/components/icons/Spinner.vue +42 -0
  189. package/src/components/icons/SquareCheck.vue +18 -0
  190. package/src/components/icons/SquareUncheck.vue +18 -0
  191. package/src/components/icons/Stadium.vue +13 -0
  192. package/src/components/icons/StadiumWhite.vue +13 -0
  193. package/src/components/icons/Status.vue +8 -0
  194. package/src/components/icons/Tag.vue +8 -0
  195. package/src/components/icons/Tasks.vue +13 -0
  196. package/src/components/icons/Ticket.vue +8 -0
  197. package/src/components/icons/Timer.vue +8 -0
  198. package/src/components/icons/Trash.vue +8 -0
  199. package/src/components/icons/Up.vue +10 -0
  200. package/src/components/icons/Wildcard.vue +18 -0
  201. package/src/components/icons/index.js +111 -0
  202. package/src/components/ui/Modal.vue +61 -0
  203. package/src/components/ui/Navbar.vue +207 -0
  204. package/src/components/ui/ReconnectIndicator.vue +90 -0
  205. package/src/components/ui/Splash.vue +24 -0
  206. package/src/components/ui/controls/CountryChooser.vue +87 -0
  207. package/src/components/ui/controls/EyeToggle.vue +11 -0
  208. package/src/components/ui/controls/atomic/Checkbox.vue +28 -0
  209. package/src/components/ui/controls/atomic/Dropdown.vue +138 -0
  210. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  211. package/src/components/ui/controls/atomic/MultiDropdown.vue +262 -0
  212. package/src/components/ui/controls/atomic/Switch.vue +84 -0
  213. package/src/libs/Filter.js +593 -0
  214. package/src/libs/ansii.js +565 -0
  215. package/src/libs/panzoom.js +1413 -0
  216. package/src/main.js +23 -0
  217. package/src/registerServiceWorker.js +32 -0
  218. package/src/router/index.js +65 -0
  219. package/src/stores/cities.json +1 -0
  220. package/src/stores/connection.js +399 -0
  221. package/src/stores/countries.js +88 -0
  222. package/src/stores/logger.js +103 -0
  223. package/src/stores/requests.js +88 -0
  224. package/src/stores/sampleData.js +1034 -0
  225. package/src/stores/ui.js +584 -0
  226. package/src/stores/utils.js +554 -0
  227. package/src/types/index.js +42 -0
  228. package/src/utils/debug.js +1 -0
  229. package/src/views/Accounts.vue +191 -0
  230. package/src/views/Console.vue +224 -0
  231. package/src/views/Editor.vue +785 -0
  232. package/src/views/FilterBuilder.vue +785 -0
  233. package/src/views/Login.vue +27 -0
  234. package/src/views/Profiles.vue +209 -0
  235. package/src/views/Tasks.vue +157 -0
  236. package/static/offline.html +184 -0
  237. package/tailwind.config.js +57 -0
  238. package/vite.config.js +63 -0
  239. package/vue.config.js +32 -0
  240. package/workbox-config.js +66 -0
@@ -0,0 +1,45 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run lint)",
5
+ "Bash(find:*)",
6
+ "Bash(ls:*)",
7
+ "Bash(grep:*)",
8
+ "Bash(npm run build:*)",
9
+ "Bash(npm view:*)",
10
+ "Bash(npm run dev:*)",
11
+ "Bash(sudo rm:*)",
12
+ "Bash(rm:*)",
13
+ "Bash(rg:*)",
14
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"const DEBUG\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/views/)",
15
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"border-2 border-dark-550\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
16
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"bg-dark-500\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
17
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"flex items-center justify-center\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
18
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"col-span.*flex.*items-center.*justify-center\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
19
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"button.*flex.*items-center.*justify-center.*gap\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
20
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"import.*useUIStore\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
21
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"const.*ref\\(\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
22
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"button-default.*bg-dark-400.*w-48.*text-xs.*flex.*items-center.*justify-center.*gap-x-2\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
23
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"const DEBUG.*window\\.location\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
24
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -A5 -B5 \"input-incrementer\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/components/Tasks/CreateTaskTM.vue)",
25
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -A2 -B2 \"label-override\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/assets/css/main.scss)",
26
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"MultiDropdown\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
27
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"ant-select.*multiple\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
28
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"multiple.*true\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
29
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"a-select\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
30
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"Select\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
31
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -A5 -B5 \"mode.*multiple\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/components/Tasks/)",
32
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -A10 -B5 \"a-select\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/components/Tasks/CreateTaskTM.vue)",
33
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"CountryChooser\\|currentCountry\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/views/Tasks.vue)",
34
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"currentModule\\|CountryChooser\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/components/ui/Navbar.vue)",
35
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -l \"CountryChooser\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/)",
36
+ "Bash(/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-darwin/rg -n \"bg-dark-400.*button\\|button.*bg-dark-400\" /Users/luca/Documents/GitHub/Necro/Dashboard/src/views/Tasks.vue)",
37
+ "Bash(npm install:*)",
38
+ "Bash(sudo chown:*)",
39
+ "Bash(sudo npm install:*)",
40
+ "Bash(node:*)",
41
+ "Bash(git checkout:*)"
42
+ ],
43
+ "deny": []
44
+ }
45
+ }
package/.eslintrc.js ADDED
@@ -0,0 +1,24 @@
1
+ module.exports = {
2
+ env: {
3
+ browser: true,
4
+ es2021: true,
5
+ node: true
6
+ },
7
+ extends: ["eslint:recommended", "plugin:vue/vue3-essential"],
8
+ overrides: [],
9
+ parserOptions: {
10
+ ecmaVersion: "latest",
11
+ sourceType: "module"
12
+ },
13
+ plugins: ["vue"],
14
+ rules: {
15
+ "html.validate.scripts": 0,
16
+ "html.validate.styles": 0
17
+ },
18
+ ignorePatterns: ["src/registerServiceWorker.js", "src/libs/panzoom.js", "**/**.vue"],
19
+ globals: {
20
+ Bot: true,
21
+ refreshTaskOnFrontEnd: true,
22
+ pushWSUpdate: true
23
+ }
24
+ };
@@ -0,0 +1 @@
1
+ src/components/editors/Profile/cities.json
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "tabWidth": 4,
3
+ "useTabs": false,
4
+ "semi": true,
5
+ "singleQuote": false,
6
+ "trailingComma": "none",
7
+ "bracketSpacing": true,
8
+ "arrowParens": "always",
9
+ "printWidth": 120
10
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["Vue.volar"]
3
+ }
package/ICONS.md ADDED
@@ -0,0 +1,21 @@
1
+ # Meanings of icons
2
+
3
+ - ![stadium_w](./public/img/stadium_w.svg) `task.eventId`
4
+ - ![bag_w](./public/img/bag_w.svg) `task.quantity`
5
+ - ![mail](./public/img/mail.svg) `task.email`
6
+ - ![key](./public/img/key.svg) `task.password`
7
+ - ![profileName](./public/img/profile.svg) `task.profileName` (this.profile?.profileName)
8
+ - ![camera](./public/img/camera.svg) `task.proxy`
9
+ - ![timer](./public/img/timer.svg) `task.smartTimer`
10
+ - ![groups](./public/img/groups.svg) `task.loginAfterCart`
11
+ - ![hand](./public/img/hand.svg) `task.manual`
12
+ - ![savings](./public/img/savings.svg) `task.doNotPay`
13
+ - ![loyalty](./public/img/loyalty.svg) `task.presaleMode`
14
+ - ![ski](./public/img/ski.svg) `task.quickQueue`
15
+ - ![sell](./public/img/sell.svg) `task.profileTag`
16
+ - ![scanner](./public/img/scanner.svg) `task.accountTag`
17
+ - ![pencil](./public/img/pencil.svg) `task.presaleCode`
18
+ - ![stadium_w](./public/img/stadium_w.svg) `task.eventName`
19
+ - ![stadium_w](./public/img/stadium_w.svg) `task.eventVenue`
20
+ - ![stadium_w](./public/img/stadium_w.svg) `task.eventLocalDate`
21
+ - ![sandclock](./public/img/sandclock.svg) `task.agedAccount`
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # 🎫 Necro Dashboard
2
+
3
+ A sophisticated PWA dashboard for managing ticket purchasing automation across multiple platforms. Built with Vue 3 and real-time WebSocket communication.
4
+
5
+ ## ⚡ Quick Start
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Visit `http://localhost:5173` and you're live.
13
+
14
+ ## 🚀 Commands
15
+
16
+ | Command | Action |
17
+ | ---------------- | ----------------------------- |
18
+ | `npm run dev` | Development server |
19
+ | `npm run expose` | Network-accessible dev server |
20
+ | `npm run build` | Production build with PWA |
21
+ | `npm run lint` | Code formatting |
22
+
23
+ **Frontend Stack**
24
+
25
+ - Vue 3 + Composition API
26
+ - Pinia state management
27
+ - TailwindCSS + SCSS
28
+ - PWA with offline support
29
+
30
+ **Backend Stack**
31
+
32
+ - Express.js with WebSockets
33
+ - MessagePack serialization for big socket messages
34
+ - Real-time message batching
35
+ - Platform abstraction layer
36
+
37
+ ## 📱 PWA Support
38
+
39
+ Built as a Progressive Web App with:
40
+
41
+ - 📲 Install to home screen
42
+ - 🔄 Background sync
43
+ - 📶 Offline functionality
44
+ - 🎨 Native app feel
45
+
46
+ ## 🛠️ Development
47
+
48
+ The app runs in mock mode by default for development. Backend simulates bot operations while frontend provides full UI functionality.
49
+
50
+ **File Structure**
51
+
52
+ ```
53
+ src/
54
+ ├─ components/ # Reusable Vue components
55
+ ├─ views/ # Page-level components
56
+ ├─ stores/ # Pinia state management
57
+ └─ assets/ # Static resources
58
+
59
+ backend/
60
+ ├─ api.js # Express server + WebSocket
61
+ ├─ mock-data.js # Development data
62
+ └─ endpoints.js # API route definitions
63
+ ```
64
+
65
+ ---
package/backend/api.js ADDED
@@ -0,0 +1,430 @@
1
+ const enableWs = require("express-ws");
2
+ const express = require("express");
3
+ const cors = require("cors");
4
+ const cookieParser = require("cookie-parser");
5
+ const { encode } = require("@msgpack/msgpack");
6
+ const fs = require("node:fs");
7
+ const path = require("node:path");
8
+
9
+ const { createLogger } = require("./mock-src/classes/logger");
10
+ const utils = require("./mock-src/classes/utils");
11
+
12
+ const Batcher = require("./batching");
13
+ const endpoints = require("./endpoints");
14
+ const authSystem = require("./auth");
15
+ const { users } = require("./mock-data.js");
16
+
17
+ const logger = createLogger("WEB UI");
18
+
19
+ const maxWaitTime = 350; // ms
20
+ const maxMessageSize = 100;
21
+
22
+ const app = express();
23
+ const auth = new authSystem();
24
+
25
+ // CORS - for dev
26
+ const alowedCors = {
27
+ origin: ["*"]
28
+ };
29
+ app.use(cors(alowedCors));
30
+ // enable express-websocket
31
+ enableWs(app);
32
+
33
+ app.use((error, req, res, next) => {
34
+ logger.Error("Error Handling Middleware called");
35
+ logger.Error("Path: ", req.path);
36
+ next(); // (optional) invoking next middleware
37
+ });
38
+
39
+ // serve static vue files
40
+ app.use(
41
+ express.static(path.join(__dirname, "../dist"), {
42
+ setHeaders: (res) => {
43
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
44
+ }
45
+ })
46
+ );
47
+
48
+ // parse JSON
49
+ app.use(express.json());
50
+ // send and parse cookies
51
+ app.use(cookieParser());
52
+
53
+ // =======
54
+ // Config
55
+ // =======
56
+
57
+ const port = process.env.PORT || 8081;
58
+ const filesThatCanBeEdited = ["configs.json"];
59
+
60
+ global.refreshTaskOnFrontEnd = (task) => {
61
+ pushWSUpdate({
62
+ event: "task-update",
63
+ task: task,
64
+ id: task.taskId
65
+ });
66
+ };
67
+
68
+ // =============
69
+ // Websocket
70
+ // =============
71
+
72
+ let wsClients = [];
73
+
74
+ const sendObject = (ws, batch) => {
75
+ // if object is too large, use msgpack encoding
76
+ const asString = JSON.stringify(batch);
77
+ if (asString.length > 1 && !Bot.Settings.DebugMode) return ws.send(encode(batch));
78
+ else return ws.send(asString);
79
+ };
80
+
81
+ const pushConsoleWSUpdate = (batch) => {
82
+ wsClients.forEach((c) => {
83
+ if (c.type !== "console") return;
84
+ batch = batch.filter((e) => !!e.log);
85
+ sendObject(c.ws, batch);
86
+ });
87
+ };
88
+
89
+ const pushTaskWSUpdate = (batch) => {
90
+ wsClients.forEach((c) => {
91
+ if (c.type !== "tasks") return;
92
+ sendObject(c.ws, batch);
93
+ });
94
+ };
95
+
96
+ const taskBatcher = new Batcher(pushTaskWSUpdate, maxMessageSize, maxWaitTime);
97
+ const logBatcher = new Batcher(pushConsoleWSUpdate, maxMessageSize, maxWaitTime);
98
+
99
+ const pushWSUpdate = (u) => {
100
+ if (u.type === "console") logBatcher.add(u);
101
+ else taskBatcher.add(u);
102
+ };
103
+
104
+ global.pushWSUpdate = pushWSUpdate;
105
+
106
+ app.ws("/api/updates", async function (ws, req) {
107
+ let currentUser = users[0];
108
+ try {
109
+ // const acc = auth.validateAuthToken(req.cookies.auth);
110
+ // logger.Info(acc, req.cookies.auth);
111
+ // if (!acc.name) {
112
+ // ws.send('[{"event": "login"}]');
113
+ // return ws.close();
114
+ // }
115
+ ws.send(JSON.stringify([users[0]]));
116
+ } catch (e) {
117
+ logger.Error(e);
118
+ }
119
+
120
+ try {
121
+ const id = utils.uuid();
122
+
123
+ logger.Info(`New websocket connected: ${id}`);
124
+ // Add client to array
125
+
126
+ wsClients.push({
127
+ ws: ws,
128
+ id: id,
129
+ type: req.query.type
130
+ });
131
+
132
+ if (req.query.type === "tasks") {
133
+ const send = (msg) => sendObject(ws, [msg]);
134
+ await utils.sleep(100);
135
+ pushWSUpdate({ event: "startup", message: "Starting Bot", user: currentUser.name });
136
+ await utils.sleep(750);
137
+
138
+ pushWSUpdate({ event: "startup", done: true, user: currentUser.name, version: "1.0" });
139
+ ws.send(
140
+ JSON.stringify([
141
+ { event: "set-button-disabled", button: "add-tasks", value: false },
142
+ { event: "set-button-disabled", button: "add-profiles", value: false },
143
+ { event: "set-button-disabled", button: "add-accounts", value: false }
144
+ ])
145
+ );
146
+ setTimeout(() => send({ event: "init-tasks", tasks: endpoints.getStrippedTasks(currentUser.name) }));
147
+ setTimeout(() => send({ event: "init-profiles", profiles: Bot.Profiles }));
148
+ setTimeout(() => send({ event: "init-tm-accounts", accounts: Bot.TM.Accounts }));
149
+ setTimeout(() => send({ event: "init-axs-accounts", accounts: Bot.AXS.Accounts }));
150
+
151
+ ws.on("message", async (msg) => {
152
+ if (msg === "ping") return ws.send("pong");
153
+ const parsed = JSON.parse(msg);
154
+ parsed.data.user = currentUser;
155
+ const res = await endpoints.handleWebsocketMessage(parsed);
156
+ if (res?.error) ws.send(JSON.stringify([{ event: "error", msg: res.error }]));
157
+ });
158
+ }
159
+ if (req.query.type === "console") {
160
+ for (let c = 0; c < Bot.ConsoleBuffer.length; c++) {
161
+ pushWSUpdate({
162
+ type: "console",
163
+ ...Bot.ConsoleBuffer[c]
164
+ });
165
+ }
166
+ }
167
+
168
+ ws.on("close", () => {
169
+ logger.Yellow("WebSocket was closed:", id);
170
+ // Remove client from array
171
+ wsClients = wsClients.filter((c) => c.id !== id);
172
+ });
173
+ } catch (e) {
174
+ logger.Error(e);
175
+ }
176
+ });
177
+
178
+ // catches vue reloads
179
+ app.use(
180
+ ["/login", "/console", "/editor", "/filter", "/profiles", "/accounts"],
181
+ express.static(path.join(__dirname, "../dist/index.html"))
182
+ );
183
+
184
+ // ==============
185
+ // API endpoints
186
+ // ==============
187
+
188
+ // ===== Login ======
189
+ app.post("/api/login", async (req, res) => {
190
+ const pwd = req.body.password;
191
+ const user = req.body.name;
192
+ const authResult = auth.loginToAccount(user, pwd);
193
+ if (authResult.token) return res.cookie("auth", authResult.token).send(authResult);
194
+ else return res.send(authResult);
195
+ });
196
+
197
+ app.post("/api/logout", async (req, res) => {
198
+ const token = req.cookies.auth;
199
+ const result = auth.invalidateAuthToken(token);
200
+ return res.cookie("auth", "").send(result);
201
+ });
202
+
203
+ // ===== File editing =====
204
+
205
+ // Get list of all the available files
206
+ app.get("/api/json-files", async (req, res) => {
207
+ res.send(filesThatCanBeEdited);
208
+ });
209
+
210
+ // Get current content of one of the files
211
+ app.get("/api/json-file", async (req, res) => {
212
+ const requestedFile = req.query.file;
213
+ if (!requestedFile)
214
+ return res.status(400).send({
215
+ error: "No file specified: missing ?file= parameter"
216
+ });
217
+
218
+ if (!filesThatCanBeEdited.includes(requestedFile))
219
+ return res.status(400).send({
220
+ error: "File can't be edited: check /api/json-files"
221
+ });
222
+
223
+ const content = fs.readFileSync(path.normalize(`${process.cwd()}/config/${requestedFile}`), "utf8");
224
+ return res.send({
225
+ content: utils.btoa(content)
226
+ });
227
+ });
228
+
229
+ // Set new content of one of the files
230
+ app.post("/api/json-file", async (req, res) => {
231
+ const requestedFile = req.query.file;
232
+ if (!requestedFile)
233
+ return res.status(400).send({
234
+ error: "No file specified: missing ?file= parameter"
235
+ });
236
+
237
+ if (!filesThatCanBeEdited.includes(requestedFile))
238
+ return res.status(400).send({
239
+ error: "File can't be edited: check /api/json-files"
240
+ });
241
+
242
+ const content = utils.atob(req.body.content);
243
+
244
+ if (!content)
245
+ return res.status(400).send({
246
+ error: "Request must contain a content field"
247
+ });
248
+
249
+ try {
250
+ if (requestedFile.endsWith(".json")) JSON.parse(content);
251
+ } catch (e) {
252
+ return res.status(400).send({
253
+ error: "Content must be valid JSON"
254
+ });
255
+ }
256
+
257
+ fs.writeFile(path.normalize(`${process.cwd()}/config/${requestedFile}`), content, (err) => {
258
+ if (err) {
259
+ console.error(err);
260
+ return res.status(400).send({
261
+ error: err
262
+ });
263
+ }
264
+ return res.send({});
265
+ });
266
+ });
267
+
268
+ const closeTab = (res) => {
269
+ res.set("Content-Type", "text/html");
270
+ res.send(Buffer.from("<script>window.close();</script>"));
271
+ };
272
+
273
+ // ===== Task managing =====
274
+
275
+ // ===== Continue task (manual) =====
276
+ app.get("/api/tasks/continue", async (req, res) => {
277
+ const r = await endpoints.continueTask(req.query);
278
+ if (r.error) return res.send(r);
279
+ else closeTab(res);
280
+ });
281
+
282
+ // ===== Open Browser =====
283
+ app.get("/api/tasks/delete", async (req, res) => {
284
+ const r = await endpoints.deleteTask(req.query);
285
+ if (r.error) return res.send(r);
286
+ else closeTab(res);
287
+ });
288
+
289
+ // Get all tasks
290
+ app.get("/api/tasks", async (req, res) => {
291
+ return res.send(await endpoints.getTasks());
292
+ });
293
+
294
+ app.get("/api/tasks/load-presale", async (req, res) => {
295
+ await endpoints.deleteAllTasks();
296
+ closeTab(res);
297
+ });
298
+
299
+ app.get("/api/tasks/start", async (req, res) => {
300
+ return res.send(await endpoints.startTask(req.query));
301
+ });
302
+
303
+ app.get("/api/tasks/stop", async (req, res) => {
304
+ return res.send(await endpoints.stopTask(req.query));
305
+ });
306
+
307
+ app.post("/api/tasks/add", async (req, res) => {
308
+ return res.send(await endpoints.addTask(req.body));
309
+ });
310
+
311
+ app.post("/api/mass-edit-presale-code", async (req, res) => {
312
+ return res.send(await endpoints.massEditPresaleCode(req.body));
313
+ });
314
+
315
+ app.post("/api/scrape-map", async (req, res) => {
316
+ return res.send(await endpoints.scrapeMap(req.body));
317
+ });
318
+
319
+ app.post("/api/check-stock", async (req, res) => {
320
+ return res.send(await endpoints.checkStock(req.body));
321
+ });
322
+
323
+ let filter = {};
324
+ app.get("/api/filter/load", async (req, res) => {
325
+ res.json({
326
+ filters: [
327
+ { eventId: "542695", generalAdmission: true },
328
+ { eventId: "542695", buyAny: true },
329
+ { eventId: "529171", section: "INNENRAUM STEHPLATZ" }
330
+ ],
331
+ globalFilter: { priceSort: "desc", minPrice: 0, maxPrice: 0 }
332
+ });
333
+ });
334
+
335
+ app.post("/api/filter/save", async (req, res) => {
336
+ filter = req.body;
337
+ logger.Info("Saved filter", filter);
338
+ res.json(filter);
339
+ });
340
+ app.get("/api/cors", async (req, res) => {
341
+ const { url } = req.query;
342
+ if (!url) {
343
+ return res.status(400).send({ error: "No URL provided" });
344
+ }
345
+
346
+ try {
347
+ const response = await fetch(url);
348
+
349
+ if (!response.ok) {
350
+ return res.status(response.status).send({
351
+ error: `Upstream server returned ${response.status}`
352
+ });
353
+ }
354
+
355
+ const contentType = response.headers.get("content-type") || "application/octet-stream";
356
+ const data = Buffer.from(await response.arrayBuffer());
357
+
358
+ if (contentType.includes("application/json")) {
359
+ const jsonData = JSON.parse(data.toString());
360
+ return res.type(contentType).send(jsonData);
361
+ }
362
+
363
+ res.type(contentType).send(data);
364
+
365
+ logger.Info("Proxied", url, response.status);
366
+ } catch (error) {
367
+ logger.Error("Proxy error", { url, error: error.message });
368
+ res.status(500).send({ error: error.message });
369
+ }
370
+ });
371
+
372
+ // ===== Reload proxies / profiles =====
373
+
374
+ let config = {};
375
+ app.get("/api/quickconfig", async (req, res) => {
376
+ return res.json({
377
+ keys: {
378
+ Captcha: {
379
+ CapMonster: "example",
380
+ CapSolver: "example",
381
+ Invizible: "example",
382
+ TwoCaptcha: "example"
383
+ },
384
+ SMS: {
385
+ Quackr: "example",
386
+ SMSActivate: "example"
387
+ }
388
+ }
389
+ });
390
+ });
391
+
392
+ app.post("/api/quickconfig", async (req, res) => {
393
+ config = JSON.parse(atob(req.body.content));
394
+ return res.json(config);
395
+ });
396
+
397
+ let proxies;
398
+ app.get("/api/proxies", async (req, res) => {
399
+ return res.send(proxies);
400
+ });
401
+ app.post("/api/proxies", async (req, res) => {
402
+ proxies = atob(req.body.content);
403
+ res.send(proxies);
404
+ });
405
+
406
+ app.post("/api/userconfig/set", async (req, res) => {
407
+ // eslint-disable-next-line no-unused-vars
408
+ const { field, value } = req.body;
409
+
410
+ users[0].proxyList = value;
411
+
412
+ return res.send(users[0]);
413
+ });
414
+
415
+ app.get("/api/userconfig/proxylists", async (req, res) => {
416
+ return res.send(["admin-proxies", "admin-proxies-1", "admin-proxies-2"]);
417
+ });
418
+
419
+ app.get("/api/userconfig/balances", async (req, res) => {
420
+ return res.send({ CapSolver: 100, TwoCaptcha: 40 });
421
+ });
422
+
423
+ module.exports = {
424
+ start: async () => {
425
+ // onChange = (await import('on-change')).default;
426
+ app.listen(port, "0.0.0.0", () => {
427
+ logger.Info("Web API started on port:", port);
428
+ });
429
+ }
430
+ };
@@ -0,0 +1,62 @@
1
+ const crypto = require("node:crypto");
2
+
3
+ const generateAuthToken = () => {
4
+ return crypto.randomUUID();
5
+ };
6
+
7
+ // This is not secure, at all.
8
+ // If you need it to be more secure, at least
9
+ // use some kind of hashing for passwords
10
+
11
+ // ===============
12
+ // Auth middleware
13
+ // ===============
14
+ let currentAuthTokens = {};
15
+
16
+ class Auth {
17
+ registerNewAccount(name, password) {
18
+ Bot.Users.push({
19
+ name: name,
20
+ password: password
21
+ });
22
+ }
23
+ loginToAccount(name, password) {
24
+ let res = null;
25
+ Bot.Users.forEach((acc) => {
26
+ if (acc.name === name && acc.password === password) res = acc;
27
+ });
28
+ if (!res || !res?.name)
29
+ return {
30
+ error: "name or password incorrect",
31
+ token: null
32
+ };
33
+
34
+ const auth = generateAuthToken();
35
+ currentAuthTokens[auth] = res;
36
+ return {
37
+ error: null,
38
+ token: auth,
39
+ profile: {
40
+ name: res.name,
41
+ profilePicture: res.profilePicture
42
+ }
43
+ };
44
+ }
45
+ invalidateAuthToken(token) {
46
+ if (!currentAuthTokens[token])
47
+ return {
48
+ error: "Token does not exist"
49
+ };
50
+ delete currentAuthTokens[token];
51
+ return {};
52
+ }
53
+ validateAuthToken(token) {
54
+ if (!currentAuthTokens[token])
55
+ return {
56
+ error: "Token does not exist"
57
+ };
58
+ return currentAuthTokens[token];
59
+ }
60
+ }
61
+
62
+ module.exports = Auth;