levix-bot 2.0.0
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.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/SETUP.md +242 -0
- package/app.cjs +337 -0
- package/bin/levix.js +7 -0
- package/deploy/install.sh +182 -0
- package/deploy/levix.service +56 -0
- package/deploy/nginx/levix.leviro.net.conf +79 -0
- package/package.json +78 -0
- package/public/brand/apple-touch-icon.png +0 -0
- package/public/brand/banner.jpg +0 -0
- package/public/brand/banner.webp +0 -0
- package/public/brand/icon-16.png +0 -0
- package/public/brand/icon-192.png +0 -0
- package/public/brand/icon-32.png +0 -0
- package/public/brand/icon-512.png +0 -0
- package/public/brand/icon-512.webp +0 -0
- package/public/brand/mark.png +0 -0
- package/public/brand/mark.svg +16 -0
- package/public/brand/mark.webp +0 -0
- package/public/brand/wordmark.png +0 -0
- package/public/brand/wordmark.webp +0 -0
- package/public/dashboard.css +881 -0
- package/public/dashboard.js +1057 -0
- package/public/logo.svg +16 -0
- package/public/qrcode.min.js +1 -0
- package/public/socket.io.min.js +7 -0
- package/scheduler.cjs +197 -0
- package/src/auth/auth-storage.cjs +74 -0
- package/src/auth/use-database-auth-state.js +130 -0
- package/src/bootstrap/core.js +74 -0
- package/src/bootstrap/events.cjs +43 -0
- package/src/bootstrap/panel.js +97 -0
- package/src/cli.js +153 -0
- package/src/commands/autoschedule.cjs +84 -0
- package/src/commands/blacklist.cjs +116 -0
- package/src/commands/block.cjs +70 -0
- package/src/commands/calc.cjs +387 -0
- package/src/commands/debt.cjs +175 -0
- package/src/commands/deleteschedule.cjs +36 -0
- package/src/commands/gemini.cjs +727 -0
- package/src/commands/group/add.cjs +136 -0
- package/src/commands/group/all.cjs +43 -0
- package/src/commands/group/antiSpam.cjs +96 -0
- package/src/commands/group/antilink.cjs +167 -0
- package/src/commands/group/approveall.cjs +54 -0
- package/src/commands/group/clearwarns.cjs +41 -0
- package/src/commands/group/deletenote.cjs +33 -0
- package/src/commands/group/demote.cjs +62 -0
- package/src/commands/group/kick.cjs +85 -0
- package/src/commands/group/media.cjs +120 -0
- package/src/commands/group/members.cjs +59 -0
- package/src/commands/group/note.cjs +32 -0
- package/src/commands/group/promote.cjs +60 -0
- package/src/commands/group/removeall.cjs +42 -0
- package/src/commands/group/rules.cjs +31 -0
- package/src/commands/group/save.cjs +36 -0
- package/src/commands/group/setname.cjs +41 -0
- package/src/commands/group/setpp.cjs +51 -0
- package/src/commands/group/setrules.cjs +46 -0
- package/src/commands/group/setwarn.cjs +58 -0
- package/src/commands/group/tagadmins.cjs +50 -0
- package/src/commands/group/warn.cjs +105 -0
- package/src/commands/group/warns.cjs +66 -0
- package/src/commands/group/welcome.cjs +109 -0
- package/src/commands/group.cjs +160 -0
- package/src/commands/help.cjs +230 -0
- package/src/commands/listschedules.cjs +47 -0
- package/src/commands/loop.cjs +163 -0
- package/src/commands/memory.cjs +201 -0
- package/src/commands/mod.cjs +155 -0
- package/src/commands/notes.cjs +28 -0
- package/src/commands/perm.cjs +236 -0
- package/src/commands/ping.cjs +17 -0
- package/src/commands/poll.cjs +80 -0
- package/src/commands/prayer.cjs +64 -0
- package/src/commands/qr.cjs +39 -0
- package/src/commands/rand.cjs +130 -0
- package/src/commands/restart.cjs +25 -0
- package/src/commands/schedule.cjs +86 -0
- package/src/commands/score.cjs +130 -0
- package/src/commands/setprefix.cjs +40 -0
- package/src/commands/shortlink.cjs +74 -0
- package/src/commands/shutdown.cjs +45 -0
- package/src/commands/status.cjs +75 -0
- package/src/commands/stt.cjs +150 -0
- package/src/commands/todo.cjs +88 -0
- package/src/commands/tts.cjs +170 -0
- package/src/commands/unblock.cjs +57 -0
- package/src/commands/weather.cjs +79 -0
- package/src/config/ai-persona.md +17 -0
- package/src/config/baileys.config.js +51 -0
- package/src/config/brand.cjs +36 -0
- package/src/config/brand.esm.js +17 -0
- package/src/config/constants.js +16 -0
- package/src/config/defaults.cjs +91 -0
- package/src/config/lock.cjs +110 -0
- package/src/config/paths.cjs +90 -0
- package/src/config/runtime-config.cjs +219 -0
- package/src/config/secrets.cjs +130 -0
- package/src/config/settings.cjs +386 -0
- package/src/core/connection.js +199 -0
- package/src/core/events.js +61 -0
- package/src/core/socket.js +64 -0
- package/src/db/db.cjs +282 -0
- package/src/db/store.cjs +837 -0
- package/src/db/store.esm.js +17 -0
- package/src/domain/caddy.js +120 -0
- package/src/domain/command.js +380 -0
- package/src/domain/detect.js +173 -0
- package/src/domain/nginx.js +138 -0
- package/src/domain/system.js +148 -0
- package/src/handlers/command.handler.js +414 -0
- package/src/handlers/group.handler.js +73 -0
- package/src/handlers/message.handler.js +149 -0
- package/src/index.js +217 -0
- package/src/middleware/antispam.middleware.js +68 -0
- package/src/middleware/blacklist.middleware.js +29 -0
- package/src/middleware/forward-tracking.middleware.js +49 -0
- package/src/middleware/permissions.middleware.js +114 -0
- package/src/routes/dashboard.api.esm.js +779 -0
- package/src/services/aiAgent.cjs +317 -0
- package/src/services/aiTools.cjs +635 -0
- package/src/utils/datetime.cjs +61 -0
- package/src/utils/lid-helper.esm.js +143 -0
- package/src/utils/logger.cjs +72 -0
- package/src/utils/memory.cjs +385 -0
- package/src/utils/normalizeJid.cjs +65 -0
- package/src/utils/normalizeJid.esm.js +37 -0
- package/src/utils/openBrowser.cjs +129 -0
- package/src/utils/permissions.cjs +154 -0
- package/src/utils/permissions.esm.js +375 -0
- package/src/utils/recentMessageCache.esm.js +70 -0
- package/src/utils/requestOrigin.cjs +102 -0
- package/src/utils/sendBotMessage.cjs +43 -0
- package/src/utils/sendBotMessage.esm.js +139 -0
- package/src/utils/statusMessage.cjs +179 -0
- package/src/utils/storage-hub.cjs +49 -0
- package/src/utils/storage-hub.esm.js +24 -0
- package/src/utils/storage.cjs +80 -0
- package/src/utils/storage.esm.js +88 -0
- package/src/utils/textDecode.cjs +197 -0
- package/src/utils/thumbnail.cjs +591 -0
- package/src/utils/typing.esm.js +102 -0
- package/views/dashboard.ejs +329 -0
- package/views/login.ejs +105 -0
- package/views/qr.ejs +108 -0
- package/views/setup.ejs +122 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title><%= brand.name %> · Control panel</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/brand/icon-16.png" />
|
|
9
|
+
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
10
|
+
<link rel="stylesheet" href="/dashboard.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="shell">
|
|
14
|
+
<aside class="sidebar" id="sidebar">
|
|
15
|
+
<div class="brand">
|
|
16
|
+
<img src="/brand/mark.svg" alt="" />
|
|
17
|
+
<div>
|
|
18
|
+
<div class="brand-name"><%= brand.name %></div>
|
|
19
|
+
<div class="brand-tag"><%= brand.tagline %></div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<nav class="nav">
|
|
24
|
+
<a href="#overview" data-view="overview">
|
|
25
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
26
|
+
Overview
|
|
27
|
+
</a>
|
|
28
|
+
<a href="#connection" data-view="connection">
|
|
29
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20a8 8 0 1 0-7-4l-1 4 4-1a8 8 0 0 0 4 1z"/></svg>
|
|
30
|
+
Connection
|
|
31
|
+
</a>
|
|
32
|
+
|
|
33
|
+
<div class="nav-label">Control</div>
|
|
34
|
+
<a href="#commands" data-view="commands">
|
|
35
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
|
|
36
|
+
Commands
|
|
37
|
+
</a>
|
|
38
|
+
<a href="#ai" data-view="ai">
|
|
39
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/><circle cx="12" cy="12" r="4"/></svg>
|
|
40
|
+
AI & memory
|
|
41
|
+
</a>
|
|
42
|
+
<a href="#groups" data-view="groups">
|
|
43
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="8" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19a6 6 0 0 1 12 0M15 19a5 5 0 0 1 6-4"/></svg>
|
|
44
|
+
Groups
|
|
45
|
+
</a>
|
|
46
|
+
<a href="#roles" data-view="roles">
|
|
47
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l7 4v5c0 4-3 7-7 9-4-2-7-5-7-9V7z"/></svg>
|
|
48
|
+
Roles
|
|
49
|
+
</a>
|
|
50
|
+
|
|
51
|
+
<div class="nav-label">Data</div>
|
|
52
|
+
<a href="#data" data-view="data">
|
|
53
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>
|
|
54
|
+
Tables
|
|
55
|
+
</a>
|
|
56
|
+
<a href="#settings" data-view="settings">
|
|
57
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 0 0-.1-1l2-1.5-2-3.4-2.3 1a7 7 0 0 0-1.7-1L14.5 3h-4l-.4 2.6a7 7 0 0 0-1.7 1l-2.3-1-2 3.4L6 11a7 7 0 0 0 0 2l-2 1.5 2 3.4 2.3-1a7 7 0 0 0 1.7 1l.4 2.6h4l.4-2.6a7 7 0 0 0 1.7-1l2.3 1 2-3.4-2-1.5c.1-.3.1-.7.1-1z"/></svg>
|
|
58
|
+
Settings
|
|
59
|
+
</a>
|
|
60
|
+
</nav>
|
|
61
|
+
|
|
62
|
+
<div class="sidebar-foot">
|
|
63
|
+
<%= brand.name %> · built by <%= brand.developer %><br />
|
|
64
|
+
<a href="<%= brand.repo %>" target="_blank" rel="noopener noreferrer">Source & docs</a>
|
|
65
|
+
</div>
|
|
66
|
+
</aside>
|
|
67
|
+
|
|
68
|
+
<button class="sidebar-backdrop" id="sidebar-backdrop" type="button" aria-label="Close navigation"></button>
|
|
69
|
+
|
|
70
|
+
<div class="main">
|
|
71
|
+
<header class="topbar">
|
|
72
|
+
<button class="btn btn-icon menu-btn" id="menu-btn" type="button" aria-label="Open navigation" aria-controls="sidebar" aria-expanded="false">☰</button>
|
|
73
|
+
<h1 id="page-title">Overview</h1>
|
|
74
|
+
<div class="spacer"></div>
|
|
75
|
+
<span class="pill" id="status-pill"><span class="dot"></span>Checking…</span>
|
|
76
|
+
<button class="btn btn-icon" id="refresh-btn" type="button" aria-label="Refresh current view">⟳</button>
|
|
77
|
+
<button class="btn btn-icon" id="theme-btn" type="button" aria-label="Switch color theme">☀</button>
|
|
78
|
+
<form action="/logout" method="post">
|
|
79
|
+
<button type="submit" class="btn btn-sm">Log out</button>
|
|
80
|
+
</form>
|
|
81
|
+
</header>
|
|
82
|
+
|
|
83
|
+
<main class="content">
|
|
84
|
+
<!-- Overview ---------------------------------------------------->
|
|
85
|
+
<section class="view" id="view-overview">
|
|
86
|
+
<div class="section-head">
|
|
87
|
+
<h2>Overview</h2>
|
|
88
|
+
<p>What the bot is doing right now.</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="stats" id="stat-cards"></div>
|
|
92
|
+
|
|
93
|
+
<div class="card">
|
|
94
|
+
<div class="card-head"><h2>Runtime</h2></div>
|
|
95
|
+
<div class="grid-2" id="overview-info"></div>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<!-- Connection -------------------------------------------------->
|
|
100
|
+
<section class="view" id="view-connection">
|
|
101
|
+
<div class="section-head">
|
|
102
|
+
<h2>Connection</h2>
|
|
103
|
+
<p>Link a WhatsApp account, or unlink the one in use.</p>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="card">
|
|
107
|
+
<div class="card-head">
|
|
108
|
+
<h2>Account</h2>
|
|
109
|
+
<div class="spacer"></div>
|
|
110
|
+
<span id="conn-state"></span>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div id="qr-panel">
|
|
114
|
+
<div class="qr-box">
|
|
115
|
+
<div class="qr-frame" id="qr-frame"></div>
|
|
116
|
+
<div class="hint" id="qr-note">Waiting for a QR code…</div>
|
|
117
|
+
<ol class="steps">
|
|
118
|
+
<li>Open WhatsApp on the phone the bot should use.</li>
|
|
119
|
+
<li>Settings → Linked devices → Link a device.</li>
|
|
120
|
+
<li>Point the camera at the code above.</li>
|
|
121
|
+
</ol>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div class="inline" style="margin-top:12px">
|
|
126
|
+
<button class="btn btn-danger" id="unlink-btn">Unlink account</button>
|
|
127
|
+
<button class="btn" id="restart-btn">Restart bot</button>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="hint" style="margin-top:8px">
|
|
130
|
+
Restart only comes back on its own under a supervisor (pm2, systemd, Docker).
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</section>
|
|
134
|
+
|
|
135
|
+
<!-- Commands ---------------------------------------------------->
|
|
136
|
+
<section class="view" id="view-commands">
|
|
137
|
+
<div class="section-head">
|
|
138
|
+
<h2>Commands</h2>
|
|
139
|
+
<p>Rename triggers, change who may run what, switch commands off.</p>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="card">
|
|
143
|
+
<div class="card-head"><h2>Prefix</h2></div>
|
|
144
|
+
<div class="inline">
|
|
145
|
+
<input type="text" id="prefix-input" maxlength="3" style="max-width:90px" />
|
|
146
|
+
<button class="btn btn-primary" id="prefix-save">Save</button>
|
|
147
|
+
<span class="hint">The character every command starts with, e.g. <code>!ping</code>.</span>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="card">
|
|
152
|
+
<div class="toolbar">
|
|
153
|
+
<input type="text" id="cmd-search" placeholder="Search commands…" />
|
|
154
|
+
<select id="cmd-filter">
|
|
155
|
+
<option value="all">All commands</option>
|
|
156
|
+
<option value="general">General</option>
|
|
157
|
+
<option value="group">Group</option>
|
|
158
|
+
</select>
|
|
159
|
+
<div class="spacer" style="flex:1"></div>
|
|
160
|
+
<span class="hint">Changes apply to the next message.</span>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div class="table-wrap">
|
|
164
|
+
<table>
|
|
165
|
+
<thead>
|
|
166
|
+
<tr>
|
|
167
|
+
<th>Command</th>
|
|
168
|
+
<th>Type</th>
|
|
169
|
+
<th>Aliases (comma separated)</th>
|
|
170
|
+
<th>Who can run it</th>
|
|
171
|
+
<th>On</th>
|
|
172
|
+
</tr>
|
|
173
|
+
</thead>
|
|
174
|
+
<tbody id="cmd-body">
|
|
175
|
+
<tr><td colspan="5"><div class="loading">Loading…</div></td></tr>
|
|
176
|
+
</tbody>
|
|
177
|
+
</table>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</section>
|
|
181
|
+
|
|
182
|
+
<!-- AI ---------------------------------------------------------->
|
|
183
|
+
<section class="view" id="view-ai">
|
|
184
|
+
<div class="section-head">
|
|
185
|
+
<h2>AI & memory</h2>
|
|
186
|
+
<p>The prompt the model gets, and everything it has been told to remember.</p>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="card">
|
|
190
|
+
<div class="card-head">
|
|
191
|
+
<h2>System prompt</h2>
|
|
192
|
+
<div class="spacer"></div>
|
|
193
|
+
<span class="hint"><code id="persona-file">ai-persona.md</code></span>
|
|
194
|
+
</div>
|
|
195
|
+
<textarea id="persona-body" spellcheck="false"></textarea>
|
|
196
|
+
<div class="inline" style="margin-top:12px">
|
|
197
|
+
<button class="btn btn-primary" id="persona-save">Save prompt</button>
|
|
198
|
+
<span class="hint">Applies to the next message — no restart.</span>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div class="card">
|
|
203
|
+
<div class="card-head"><h2>Long-term memory</h2></div>
|
|
204
|
+
<div class="inline" id="memory-list"></div>
|
|
205
|
+
|
|
206
|
+
<div id="memory-editor" style="display:none;margin-top:16px">
|
|
207
|
+
<div class="card-head">
|
|
208
|
+
<h2>Editing <code id="memory-scope"></code></h2>
|
|
209
|
+
</div>
|
|
210
|
+
<textarea id="memory-content" spellcheck="false"></textarea>
|
|
211
|
+
<div class="inline" style="margin-top:12px">
|
|
212
|
+
<button class="btn btn-primary" id="memory-save">Save</button>
|
|
213
|
+
<button class="btn btn-danger" id="memory-delete">Delete file</button>
|
|
214
|
+
<span class="hint">Plain Markdown — a line starting with <code>-</code> is one memory.</span>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</section>
|
|
219
|
+
|
|
220
|
+
<!-- Groups ------------------------------------------------------>
|
|
221
|
+
<section class="view" id="view-groups">
|
|
222
|
+
<div class="section-head">
|
|
223
|
+
<h2>Groups</h2>
|
|
224
|
+
<p>Moderation settings per group. Same values the <code>!group</code> commands change.</p>
|
|
225
|
+
</div>
|
|
226
|
+
<div id="groups-list"><div class="loading">Loading…</div></div>
|
|
227
|
+
</section>
|
|
228
|
+
|
|
229
|
+
<!-- Roles ------------------------------------------------------->
|
|
230
|
+
<section class="view" id="view-roles">
|
|
231
|
+
<div class="section-head">
|
|
232
|
+
<h2>Roles</h2>
|
|
233
|
+
<p>Bot-level roles. An owner can do everything; an admin counts as an admin in every chat.</p>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="card">
|
|
237
|
+
<div class="card-head"><h2>Add someone</h2></div>
|
|
238
|
+
<div class="inline">
|
|
239
|
+
<input type="text" id="role-number" placeholder="201234567890" style="max-width:220px" />
|
|
240
|
+
<select id="role-kind" style="max-width:140px">
|
|
241
|
+
<option value="admin">Admin</option>
|
|
242
|
+
<option value="owner">Owner</option>
|
|
243
|
+
</select>
|
|
244
|
+
<button class="btn btn-primary" id="role-add">Add</button>
|
|
245
|
+
<span class="hint">International format, digits only.</span>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="card">
|
|
250
|
+
<div class="card-head"><h2>Owners</h2></div>
|
|
251
|
+
<div class="table-wrap">
|
|
252
|
+
<table>
|
|
253
|
+
<thead><tr><th>Number</th><th>Name</th><th></th></tr></thead>
|
|
254
|
+
<tbody id="owners-body"></tbody>
|
|
255
|
+
</table>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div class="card">
|
|
260
|
+
<div class="card-head"><h2>Admins</h2></div>
|
|
261
|
+
<div class="table-wrap">
|
|
262
|
+
<table>
|
|
263
|
+
<thead><tr><th>Number</th><th>Name</th><th></th></tr></thead>
|
|
264
|
+
<tbody id="admins-body"></tbody>
|
|
265
|
+
</table>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</section>
|
|
269
|
+
|
|
270
|
+
<!-- Data -------------------------------------------------------->
|
|
271
|
+
<section class="view" id="view-data">
|
|
272
|
+
<div class="section-head">
|
|
273
|
+
<h2>Tables</h2>
|
|
274
|
+
<p>What the bot has stored: debts, warnings, notes, todo lists, known users.</p>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div class="card">
|
|
278
|
+
<div class="toolbar" id="data-tabs">
|
|
279
|
+
<button class="btn btn-sm btn-primary" data-table="debts">Debts</button>
|
|
280
|
+
<button class="btn btn-sm" data-table="warnings">Warnings</button>
|
|
281
|
+
<button class="btn btn-sm" data-table="notes">Notes</button>
|
|
282
|
+
<button class="btn btn-sm" data-table="todos">Todos</button>
|
|
283
|
+
<button class="btn btn-sm" data-table="users">Users</button>
|
|
284
|
+
<button class="btn btn-sm" data-table="schedules">Schedules</button>
|
|
285
|
+
</div>
|
|
286
|
+
<div id="data-table"><div class="loading">Loading…</div></div>
|
|
287
|
+
</div>
|
|
288
|
+
</section>
|
|
289
|
+
|
|
290
|
+
<!-- Settings ---------------------------------------------------->
|
|
291
|
+
<section class="view" id="view-settings">
|
|
292
|
+
<div class="section-head">
|
|
293
|
+
<h2>Settings</h2>
|
|
294
|
+
<p>
|
|
295
|
+
This page is the only place the bot is configured — there is no config file.
|
|
296
|
+
Clear a field to go back to its default.
|
|
297
|
+
</p>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<div class="card">
|
|
301
|
+
<div class="card-head"><h2>Control panel password</h2></div>
|
|
302
|
+
<div class="inline" style="margin-bottom:8px">
|
|
303
|
+
<input type="password" id="pw-current" placeholder="Current password"
|
|
304
|
+
autocomplete="current-password" />
|
|
305
|
+
</div>
|
|
306
|
+
<div class="inline" style="margin-bottom:8px">
|
|
307
|
+
<input type="password" id="pw-next" placeholder="New password"
|
|
308
|
+
autocomplete="new-password" />
|
|
309
|
+
<button class="btn btn-sm btn-primary" id="pw-save">Change</button>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="hint">
|
|
312
|
+
This password is the only thing between the internet and a linked WhatsApp
|
|
313
|
+
account. It is stored as a hash — nobody, including this page, can read it back.
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<div id="settings-list"><div class="loading">Loading…</div></div>
|
|
318
|
+
</section>
|
|
319
|
+
</main>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<div class="toasts" id="toasts"></div>
|
|
324
|
+
|
|
325
|
+
<script src="/socket.io.min.js"></script>
|
|
326
|
+
<script src="/qrcode.min.js"></script>
|
|
327
|
+
<script src="/dashboard.js"></script>
|
|
328
|
+
</body>
|
|
329
|
+
</html>
|
package/views/login.ejs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title><%= brand.name %> · Sign in</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
9
|
+
<link rel="stylesheet" href="/dashboard.css" />
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
display: grid;
|
|
13
|
+
place-items: center;
|
|
14
|
+
padding: 24px;
|
|
15
|
+
background:
|
|
16
|
+
radial-gradient(900px 500px at 15% -10%, rgba(22, 82, 240, 0.22), transparent 60%),
|
|
17
|
+
radial-gradient(700px 420px at 90% 110%, rgba(33, 212, 253, 0.16), transparent 60%),
|
|
18
|
+
var(--bg);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.login {
|
|
22
|
+
width: 100%;
|
|
23
|
+
max-width: 380px;
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.login .brand-lockup {
|
|
28
|
+
margin-bottom: 22px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.login .card {
|
|
32
|
+
text-align: left;
|
|
33
|
+
padding: 24px;
|
|
34
|
+
box-shadow: var(--shadow);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.login h1 {
|
|
38
|
+
font-size: 1.05rem;
|
|
39
|
+
font-weight: 650;
|
|
40
|
+
margin-bottom: 4px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.login p.sub {
|
|
44
|
+
color: var(--muted);
|
|
45
|
+
font-size: 0.86rem;
|
|
46
|
+
margin-bottom: 18px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.login button {
|
|
50
|
+
width: 100%;
|
|
51
|
+
margin-top: 6px;
|
|
52
|
+
padding: 11px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.error {
|
|
56
|
+
margin-top: 12px;
|
|
57
|
+
padding: 9px 12px;
|
|
58
|
+
border-radius: var(--radius-sm);
|
|
59
|
+
background: color-mix(in srgb, var(--danger) 14%, transparent);
|
|
60
|
+
border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
|
|
61
|
+
color: var(--danger);
|
|
62
|
+
font-size: 0.85rem;
|
|
63
|
+
text-align: center;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.foot {
|
|
67
|
+
margin-top: 20px;
|
|
68
|
+
color: var(--faint);
|
|
69
|
+
font-size: 0.78rem;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<div class="login">
|
|
75
|
+
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
76
|
+
<img src="/brand/mark.svg" alt="" />
|
|
77
|
+
<strong><%= brand.name %></strong>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<form class="card" action="/login" method="post">
|
|
81
|
+
<h1>Control panel</h1>
|
|
82
|
+
<p class="sub">Sign in with your control panel password.</p>
|
|
83
|
+
|
|
84
|
+
<label class="field">
|
|
85
|
+
<span>Password</span>
|
|
86
|
+
<input
|
|
87
|
+
type="password"
|
|
88
|
+
name="password"
|
|
89
|
+
autocomplete="current-password"
|
|
90
|
+
autofocus
|
|
91
|
+
required
|
|
92
|
+
/>
|
|
93
|
+
</label>
|
|
94
|
+
|
|
95
|
+
<button type="submit" class="btn btn-primary">Sign in</button>
|
|
96
|
+
|
|
97
|
+
<% if (error) { %>
|
|
98
|
+
<div class="error"><%= error %></div>
|
|
99
|
+
<% } %>
|
|
100
|
+
</form>
|
|
101
|
+
|
|
102
|
+
<div class="foot"><%= brand.name %> - <%= brand.tagline %> · by <%= brand.developer %></div>
|
|
103
|
+
</div>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
package/views/qr.ejs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title><%= brand.name %> · Link WhatsApp</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
9
|
+
<link rel="stylesheet" href="/dashboard.css" />
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
display: grid;
|
|
13
|
+
place-items: center;
|
|
14
|
+
padding: 24px;
|
|
15
|
+
background:
|
|
16
|
+
radial-gradient(900px 500px at 15% -10%, rgba(22, 82, 240, 0.2), transparent 60%),
|
|
17
|
+
var(--bg);
|
|
18
|
+
}
|
|
19
|
+
.wrap {
|
|
20
|
+
width: 100%;
|
|
21
|
+
max-width: 430px;
|
|
22
|
+
text-align: center;
|
|
23
|
+
}
|
|
24
|
+
.wrap .brand-lockup {
|
|
25
|
+
margin-bottom: 20px;
|
|
26
|
+
}
|
|
27
|
+
.wrap .card {
|
|
28
|
+
box-shadow: var(--shadow);
|
|
29
|
+
}
|
|
30
|
+
.foot {
|
|
31
|
+
margin-top: 18px;
|
|
32
|
+
color: var(--faint);
|
|
33
|
+
font-size: 0.78rem;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div class="wrap">
|
|
39
|
+
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
40
|
+
<img src="/brand/mark.svg" alt="" />
|
|
41
|
+
<strong><%= brand.name %></strong>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="card">
|
|
45
|
+
<div class="card-head">
|
|
46
|
+
<h2>Link a WhatsApp account</h2>
|
|
47
|
+
<div class="spacer"></div>
|
|
48
|
+
<span class="pill" id="state"><span class="dot"></span>Waiting…</span>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="qr-box">
|
|
52
|
+
<div class="qr-frame" id="frame"></div>
|
|
53
|
+
<ol class="steps">
|
|
54
|
+
<li>Open WhatsApp on the phone the bot should use.</li>
|
|
55
|
+
<li>Settings → Linked devices → Link a device.</li>
|
|
56
|
+
<li>Point the camera at the code above.</li>
|
|
57
|
+
</ol>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<a class="btn" href="/">Back to the dashboard</a>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="foot"><%= brand.name %> · by <%= brand.developer %></div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<script src="/socket.io/socket.io.js"></script>
|
|
67
|
+
<script src="/qrcode.min.js"></script>
|
|
68
|
+
<script>
|
|
69
|
+
(() => {
|
|
70
|
+
const frame = document.getElementById("frame");
|
|
71
|
+
const state = document.getElementById("state");
|
|
72
|
+
let painted = null;
|
|
73
|
+
|
|
74
|
+
function setState(text, kind) {
|
|
75
|
+
state.className = `pill ${kind || ""}`;
|
|
76
|
+
state.innerHTML = `<span class="dot"></span>${text}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function render(qr) {
|
|
80
|
+
if (!qr || painted === qr) return;
|
|
81
|
+
painted = qr;
|
|
82
|
+
frame.innerHTML = "";
|
|
83
|
+
new QRCode(frame, {
|
|
84
|
+
text: qr,
|
|
85
|
+
width: 250,
|
|
86
|
+
height: 250,
|
|
87
|
+
correctLevel: QRCode.CorrectLevel.H,
|
|
88
|
+
});
|
|
89
|
+
setState("Scan me", "");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The QR the server already had when this page was rendered.
|
|
93
|
+
const initial = <%- JSON.stringify(qr || "") %>;
|
|
94
|
+
if (initial) render(initial);
|
|
95
|
+
else setState("Waiting for a code…", "");
|
|
96
|
+
|
|
97
|
+
const socket = io();
|
|
98
|
+
socket.on("qr", render);
|
|
99
|
+
socket.on("status_update", (data) => {
|
|
100
|
+
if (data.status === "Connected") {
|
|
101
|
+
frame.innerHTML = '<div style="padding:60px 30px;color:#0d1730">Linked ✓</div>';
|
|
102
|
+
setState("Connected", "on");
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
})();
|
|
106
|
+
</script>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
package/views/setup.ejs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title><%= brand.name %> · First run</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
9
|
+
<link rel="stylesheet" href="/dashboard.css" />
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
display: grid;
|
|
13
|
+
place-items: center;
|
|
14
|
+
padding: 24px;
|
|
15
|
+
background:
|
|
16
|
+
radial-gradient(900px 500px at 15% -10%, rgba(22, 82, 240, 0.22), transparent 60%),
|
|
17
|
+
radial-gradient(700px 420px at 90% 110%, rgba(33, 212, 253, 0.16), transparent 60%),
|
|
18
|
+
var(--bg);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.login {
|
|
22
|
+
width: 100%;
|
|
23
|
+
max-width: 400px;
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.login .brand-lockup {
|
|
28
|
+
margin-bottom: 22px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.login .card {
|
|
32
|
+
text-align: left;
|
|
33
|
+
padding: 24px;
|
|
34
|
+
box-shadow: var(--shadow);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.login h1 {
|
|
38
|
+
font-size: 1.05rem;
|
|
39
|
+
font-weight: 650;
|
|
40
|
+
margin-bottom: 4px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.login p.sub {
|
|
44
|
+
color: var(--muted);
|
|
45
|
+
font-size: 0.86rem;
|
|
46
|
+
margin-bottom: 18px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.login button {
|
|
50
|
+
width: 100%;
|
|
51
|
+
margin-top: 6px;
|
|
52
|
+
padding: 11px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.login .field {
|
|
56
|
+
margin-bottom: 12px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.error {
|
|
60
|
+
margin-top: 12px;
|
|
61
|
+
padding: 9px 12px;
|
|
62
|
+
border-radius: var(--radius-sm);
|
|
63
|
+
background: color-mix(in srgb, var(--danger) 14%, transparent);
|
|
64
|
+
border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
|
|
65
|
+
color: var(--danger);
|
|
66
|
+
font-size: 0.85rem;
|
|
67
|
+
text-align: center;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.foot {
|
|
71
|
+
margin-top: 20px;
|
|
72
|
+
color: var(--faint);
|
|
73
|
+
font-size: 0.78rem;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<div class="login">
|
|
79
|
+
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
80
|
+
<img src="/brand/mark.svg" alt="" />
|
|
81
|
+
<strong><%= brand.name %></strong>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<form class="card" action="/setup" method="post">
|
|
85
|
+
<h1>Welcome</h1>
|
|
86
|
+
<p class="sub">
|
|
87
|
+
Pick a password for the control panel. It is the only thing protecting a
|
|
88
|
+
linked WhatsApp account, so make it a real one — at least
|
|
89
|
+
<%= minLength %> characters.
|
|
90
|
+
</p>
|
|
91
|
+
|
|
92
|
+
<% if (needsCode) { %>
|
|
93
|
+
<label class="field">
|
|
94
|
+
<span>Setup code</span>
|
|
95
|
+
<input type="text" name="code" autocomplete="off" autofocus required
|
|
96
|
+
placeholder="printed in the bot's terminal" />
|
|
97
|
+
</label>
|
|
98
|
+
<% } %>
|
|
99
|
+
|
|
100
|
+
<label class="field">
|
|
101
|
+
<span>Password</span>
|
|
102
|
+
<input type="password" name="password" autocomplete="new-password"
|
|
103
|
+
minlength="<%= minLength %>" <%= needsCode ? "" : "autofocus" %> required />
|
|
104
|
+
</label>
|
|
105
|
+
|
|
106
|
+
<label class="field">
|
|
107
|
+
<span>Repeat password</span>
|
|
108
|
+
<input type="password" name="confirm" autocomplete="new-password"
|
|
109
|
+
minlength="<%= minLength %>" required />
|
|
110
|
+
</label>
|
|
111
|
+
|
|
112
|
+
<button type="submit" class="btn btn-primary">Create and sign in</button>
|
|
113
|
+
|
|
114
|
+
<% if (error) { %>
|
|
115
|
+
<div class="error"><%= error %></div>
|
|
116
|
+
<% } %>
|
|
117
|
+
</form>
|
|
118
|
+
|
|
119
|
+
<div class="foot"><%= brand.name %> - <%= brand.tagline %> · by <%= brand.developer %></div>
|
|
120
|
+
</div>
|
|
121
|
+
</body>
|
|
122
|
+
</html>
|