yacscript 0.3.0__py3-none-any.whl

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.
app/db.py ADDED
@@ -0,0 +1,25 @@
1
+ import os
2
+ import sqlite3
3
+ from logging import Logger
4
+ from app.definitions import SCHEMA
5
+
6
+
7
+ def get_db(path: str, logger: Logger) -> sqlite3.Connection:
8
+ if not os.path.exists(path):
9
+ logger.warning("Database not found!")
10
+ conn = sqlite3.connect(path, check_same_thread=False, autocommit=True)
11
+ conn.executescript(SCHEMA)
12
+ logger.info("Database created and initialized.")
13
+ return conn
14
+
15
+ conn = sqlite3.connect(path, check_same_thread=False, autocommit=True)
16
+ cursor = conn.cursor()
17
+ cursor.execute(
18
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='CHAT';")
19
+ if cursor.fetchone() is None:
20
+ logger.warning("Database not initialized or is CORRUPTED!")
21
+ conn.executescript(SCHEMA)
22
+ logger.info("Database initialized.")
23
+ cursor.close()
24
+ logger.info("Database good!")
25
+ return conn
app/definitions.py ADDED
@@ -0,0 +1,71 @@
1
+ SCHEMA = '''PRAGMA foreign_keys = ON;
2
+
3
+ CREATE TABLE IF NOT EXISTS CHAT (
4
+ ID INTEGER PRIMARY KEY AUTOINCREMENT,
5
+ BODY TEXT NOT NULL,
6
+ CREATED TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
7
+ CHANNEL_ID INTEGER NOT NULL,
8
+ AUTHOR TEXT NOT NULL,
9
+ IS_DELETED INTEGER NOT NULL DEFAULT 0,
10
+ FOREIGN KEY (CHANNEL_ID) REFERENCES CHANNEL(ID)
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS CHANNEL (
14
+ ID INTEGER PRIMARY KEY AUTOINCREMENT,
15
+ NAME TEXT NOT NULL,
16
+ IS_DELETED INTEGER NOT NULL DEFAULT 0,
17
+ ADMIN_ONLY INTEGER NOT NULL
18
+ );
19
+
20
+ -- not really gonna use blob here
21
+ CREATE TABLE IF NOT EXISTS RESOURCE (
22
+ UUID TEXT PRIMARY KEY,
23
+ FILE_NAME TEXT NOT NULL,
24
+ MIME_TYPE TEXT NOT NULL,
25
+ IS_EXPIRED INTEGER NOT NULL DEFAULT 0
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS ATTACHMENT (
29
+ CHAT_ID INTEGER,
30
+ RESOURCE_ID INTEGER,
31
+ FOREIGN KEY (CHAT_ID) REFERENCES CHAT(ID),
32
+ FOREIGN KEY (RESOURCE_ID) REFERENCES RESOURCE(ID)
33
+ );
34
+
35
+ INSERT INTO CHANNEL (NAME,ADMIN_ONLY)
36
+ VALUES ('Default Channel', 0);'''
37
+
38
+ CONFIG_DEFAULT = {
39
+ 'DEBUG': True,
40
+ 'app': {
41
+ 'ip': '0.0.0.0',
42
+ 'port': 8080,
43
+ 'admin_phrase': 'CHANGE_ME_ADMIN',
44
+ 'user_phrase': 'CHANGE_ME',
45
+ 'log_level': 'INFO',
46
+ 'timeout': 5000,
47
+ 'proxy_fix': False,
48
+ },
49
+ 'custom': {
50
+ 'title': 'The YACS',
51
+ 'motd': 'Heya!',
52
+ 'emoticons': ['(*゚∇゚)'],
53
+ },
54
+ 'res': {
55
+ 'path': './resource',
56
+ 'size_max': 1024
57
+ },
58
+ 'captcha': {
59
+ 'length': 4,
60
+ 'max_cache': 60,
61
+ 'expire': 120,
62
+ 'options': {
63
+ 'numbers': False,
64
+ 'lowercase': True,
65
+ 'uppercase': False,
66
+ }
67
+ },
68
+ 'db': {
69
+ 'path': './yacs.db'
70
+ }
71
+ }
app/static/admin.js ADDED
@@ -0,0 +1,110 @@
1
+ const auth = { Authorization: `Basic ${NICK} ${TOKEN}` };
2
+
3
+ async function new_chan() {
4
+ var name = window.prompt("New channel's name");
5
+ var head = auth;
6
+ head["Content-Type"] = "application/json";
7
+ var resp = await fetch("/channel", {
8
+ method: "POST",
9
+ headers: head,
10
+ body: JSON.stringify({ name: name }),
11
+ });
12
+ if (resp.ok) {
13
+ alert("Channel Created!");
14
+ socket.emit("updating_channel");
15
+ }
16
+ }
17
+
18
+ async function del_chan() {
19
+ var id = Number(window.prompt("Channel ID", ""));
20
+ if (id === undefined) {
21
+ return;
22
+ }
23
+ if (!window.confirm(`Is channel ${id} going to be deleted?`)) {
24
+ return;
25
+ }
26
+ var resp = await fetch(`/channel?id=${id}`, {
27
+ method: "DELETE",
28
+ headers: auth,
29
+ });
30
+ if (resp.ok) {
31
+ alert("Channel Deleted!");
32
+ socket.emit("updating_channel");
33
+ }
34
+ }
35
+
36
+ async function sw_chan() {
37
+ var id = Number(window.prompt("Channel's id"));
38
+ var resp = await fetch(`/channel?sw_priv=${id}`, {
39
+ method: "PUT",
40
+ headers: auth,
41
+ });
42
+ if (resp.ok) {
43
+ alert("Channel Updated!");
44
+ socket.emit("updating_channel");
45
+ }
46
+ }
47
+
48
+ async function re_chan() {
49
+ var id = Number(window.prompt("Channel's id"));
50
+ var new_name = window.prompt("Channel's name");
51
+ var resp = await fetch(`/channel?id=${id}&name=${new_name}`, {
52
+ method: "UPDATE",
53
+ headers: auth,
54
+ });
55
+ if (resp.ok) {
56
+ alert("Channel Updated!");
57
+ socket.emit("updating_channel");
58
+ }
59
+ }
60
+
61
+ async function del_user() {
62
+ var name = window.prompt("User's name", "");
63
+ if (name === "") {
64
+ return;
65
+ }
66
+ if (!window.confirm(`Is user ${name} going to be deleted?`)) {
67
+ return;
68
+ }
69
+ var resp = await fetch(`/online/${name}`, {
70
+ method: "DELETE",
71
+ headers: auth,
72
+ });
73
+ if (resp.ok) {
74
+ alert("User kicked.")
75
+ }
76
+ }
77
+
78
+ async function del_res() {
79
+ var id = window.prompt("Resources's id", "");
80
+ if (id == "") {
81
+ return;
82
+ }
83
+ if (!window.confirm(`Is resource ${id} going to be deleted?`)) {
84
+ return;
85
+ }
86
+ var resp = await fetch(`/resource/${id}`, {
87
+ method: "DELETE",
88
+ headers: auth,
89
+ });
90
+ if (resp.ok) {
91
+ alert("Resource deleted.")
92
+ }
93
+ }
94
+
95
+ async function del_msg() {
96
+ var id = Number(window.prompt("Message's id", ""));
97
+ if (id == 0) {
98
+ return;
99
+ }
100
+ if (!window.confirm(`Is message ${id} going to be deleted?`)) {
101
+ return;
102
+ }
103
+ var resp = await fetch(`/message/${id}`, {
104
+ method: "DELETE",
105
+ headers: auth,
106
+ });
107
+ if (resp.ok) {
108
+ alert("Message Deleted.")
109
+ }
110
+ }
app/static/file.mp3 ADDED
Binary file
app/static/icon.png ADDED
Binary file
app/static/join.mp3 ADDED
Binary file
app/static/leave.mp3 ADDED
Binary file
app/static/msg.mp3 ADDED
Binary file
app/static/room.js ADDED
@@ -0,0 +1,364 @@
1
+ var tag_list = ["b", "i", "u", "s", "quote", "mention"];
2
+
3
+ var current_channel = 0;
4
+
5
+ var is_uploading = false;
6
+
7
+ var socket = io({
8
+ auth: {
9
+ nick: NICK,
10
+ token: TOKEN,
11
+ },
12
+ });
13
+
14
+ async function heartbeat() {
15
+ await socket.emit("heartbeat", {
16
+ nick: NICK,
17
+ token: TOKEN,
18
+ });
19
+ }
20
+
21
+ setInterval(() => {
22
+ heartbeat();
23
+ }, TIMEOUT - 1000);
24
+
25
+ socket.on("connect", async () => {
26
+ await refresh_channels();
27
+ switch_channel(1);
28
+ if (!is_muted) {
29
+ document.getElementById("welcome_sfx").play();
30
+ }
31
+ });
32
+
33
+ socket.on("channel_updated", async () => {
34
+ refresh_channels();
35
+ });
36
+
37
+ socket.on("leaving", async (e) => {
38
+ if (e["target"] == NICK){ return }
39
+ if (!is_muted) {
40
+ document.getElementById("leave_sfx").play();
41
+ }
42
+ document.getElementById(`fellow-${e["target"]}`).remove()
43
+ });
44
+
45
+ socket.on("joining", async (e) => {
46
+ if (e["target"] == NICK){ return }
47
+ if (!is_muted) {
48
+ document.getElementById("join_sfx").play();
49
+ }
50
+ document.getElementById("fellows_list").innerHTML += `<li id="fellow-${e["target"]}">${e["target"]}</li>`
51
+ });
52
+
53
+ async function refresh_channels() {
54
+ var clist = document.getElementById("channel_list");
55
+ clist.innerHTML = "";
56
+ var resp = await fetch("/channels", {
57
+ headers: {
58
+ Authorization: `Basic ${NICK} ${TOKEN}`,
59
+ },
60
+ });
61
+ var channels = await resp.json();
62
+ for (var c of channels) {
63
+ var element = document.createElement("li");
64
+ var lock = c["is_admin"]
65
+ ? `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path fill="darkred" d="M0 12h11V7H4V4H3v3H2V3H1v4H0Zm1-1V8h9v3Zm1-8h1V2H2Zm2 1h3V3H4ZM3 2h5V1H3Zm4 4h3V3H9v2H8V4H7Zm1-3h1V2H8Zm0 0"/></svg>`
66
+ : "";
67
+ element.innerHTML = `[${c["id"]}] ${lock} ${c["name"]}`;
68
+ element.setAttribute("style", "cursor: pointer");
69
+ element.setAttribute("href", "#");
70
+ element.setAttribute("id", `clist-${c["id"]}`);
71
+ element.setAttribute("onclick", `switch_channel(${c["id"]})`);
72
+ clist.appendChild(element);
73
+ }
74
+ if (current_channel != 0) {
75
+ switch_channel(current_channel);
76
+ }
77
+ }
78
+
79
+ socket.on("msg_deliver", async (msg) => {
80
+ var msgbox = document.getElementById("msgbox");
81
+ var rendered = await render_msg(msg);
82
+ msgbox.appendChild(rendered);
83
+ msgbox.scrollTop = msgbox.scrollHeight;
84
+ if (!is_muted) {
85
+ if (msg["author"] == NICK){
86
+ document.getElementById("send_sfx").play();
87
+ } else {
88
+ document.getElementById("msg_sfx").play();
89
+ }
90
+
91
+ }
92
+ });
93
+
94
+ async function msg_send() {
95
+ var editor = document.getElementById("editor");
96
+ var body = editor.value;
97
+ if (body == "") {
98
+ window.alert("Message shouldn't be empty!");
99
+ return;
100
+ }
101
+ var attachments = [];
102
+ // Submit all attachments
103
+ for (var key in file_buffer) {
104
+ resp = await fetch(`/submit_upload?submit=${file_buffer[key]}`, {
105
+ headers: {
106
+ Authorization: `Basic ${NICK} ${TOKEN}`,
107
+ },
108
+ });
109
+ if (resp.ok) {
110
+ attachments.push(file_buffer[key]);
111
+ }
112
+ document.getElementById(file_buffer[key]).remove();
113
+ delete file_buffer[key];
114
+ }
115
+ socket.emit("msg_send", {
116
+ author: NICK,
117
+ token: TOKEN,
118
+ body: body,
119
+ attachments: attachments,
120
+ });
121
+ editor.value = "";
122
+ }
123
+
124
+ var is_muted = false;
125
+
126
+ function switch_mute() {
127
+ is_muted = !is_muted;
128
+ if (is_muted) {
129
+ document.getElementById(
130
+ "mute_switch"
131
+ ).innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"><path fill="#000" d="M13 2h-2v2H9v2H7v2H3v8h4v2h2v2h2v2h2zM9 18v-2H7v-2H5v-4h2V8h2V6h2v12zm10-6.777h-2v-2h-2v2h2v2h-2v2h2v-2h2v2h2v-2h-2zm0 0h2v-2h-2z"/></svg>`;
132
+ } else {
133
+ document.getElementById(
134
+ "mute_switch"
135
+ ).innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"><path fill="#000" d="M11 2H9v2H7v2H5v2H1v8h4v2h2v2h2v2h2zM7 18v-2H5v-2H3v-4h2V8h2V6h2v12zm6-8h2v4h-2zm8-6h-2V2h-6v2h6v2h2v12h-2v2h-6v2h6v-2h2v-2h2V6h-2zm-2 4h-2V6h-4v2h4v8h-4v2h4v-2h2z"/>`;
136
+ }
137
+ }
138
+
139
+ function insert_emote(node) {
140
+ var editor = document.getElementById("editor");
141
+ var emote = node.value;
142
+ var cursor = editor.selectionStart;
143
+ editor.value =
144
+ editor.value.slice(0, cursor) + emote + editor.value.slice(cursor);
145
+ node.selectedIndex = 0;
146
+ }
147
+
148
+ function insert_tag(node) {
149
+ var editor = document.getElementById("editor");
150
+ var tag = tag_list[node.selectedIndex - 1];
151
+ var start = editor.selectionStart;
152
+ var end = editor.selectionEnd;
153
+ editor.value =
154
+ editor.value.slice(0, start) +
155
+ `[${tag}]` +
156
+ editor.value.slice(start, end) +
157
+ `[/${tag}]` +
158
+ editor.value.slice(end);
159
+ node.selectedIndex = 0;
160
+ }
161
+
162
+ async function switch_channel(channel_id) {
163
+ // Update Visual
164
+ if (current_channel != 0) {
165
+ var last_entry = document.getElementById(`clist-${current_channel}`);
166
+ last_entry.className = "";
167
+ }
168
+ var current_entry = document.getElementById(`clist-${channel_id}`);
169
+ current_entry.className = "cselected";
170
+ current_channel = channel_id;
171
+ // Call server update channel info
172
+ socket.emit("sw_channel", { to: channel_id, nick: NICK, token: TOKEN });
173
+ // Refresh messages
174
+ var msgbox = document.getElementById("msgbox");
175
+ msgbox.innerHTML = "";
176
+ await load_more();
177
+ await refresh_fellows();
178
+ msgbox.scrollTop = msgbox.scrollHeight;
179
+ }
180
+
181
+ async function load_more(count = 30) {
182
+ var msgbox = document.getElementById("msgbox");
183
+ var new_msg = await fetch(
184
+ `/messages/${current_channel}?count=${count}&offset=${msgbox.childElementCount}`,
185
+ {
186
+ headers: {
187
+ Authorization: `Basic ${NICK} ${TOKEN}`,
188
+ },
189
+ }
190
+ );
191
+ var resp = await new_msg.json();
192
+ for (var msg of resp) {
193
+ let rendered = await render_msg(msg, msgbox);
194
+ msgbox.insertBefore(rendered, msgbox.firstChild);
195
+ }
196
+ }
197
+
198
+ async function refresh_fellows() {
199
+ var list = document.getElementById("fellows_list")
200
+ list.innerHTML = ""
201
+ var resp = await fetch("/fellows",
202
+ {
203
+ headers: {
204
+ Authorization: `Basic ${NICK} ${TOKEN}`,
205
+ },
206
+ }
207
+ )
208
+ if (resp.ok) {
209
+ var resp_json = await resp.json()
210
+ var fellows = resp_json["fellows"]
211
+ for (var fellow of fellows) {
212
+ var e = document.createElement("li")
213
+ e.innerText = fellow
214
+ e.id = `fellow-${fellow}`
215
+ list.appendChild(e)
216
+ }
217
+ }
218
+ }
219
+
220
+ async function render_msg(msg) {
221
+ var attachment_ids = msg["attachments"];
222
+ var attachments = [];
223
+ if (attachment_ids != []) {
224
+ for (let a of attachment_ids) {
225
+ var resp = await fetch(`/resource_meta/${a}`);
226
+ if (!resp.ok) {
227
+ continue;
228
+ }
229
+ var meta = await resp.json();
230
+ var attach;
231
+ if (meta["mime"].startsWith("image")) {
232
+ attach = document.createElement("img");
233
+ attach.style = "width: 200px";
234
+ attach.src = `/resource/${a}`;
235
+ }else if(meta["mime"].startsWith("video")) {
236
+ attach = document.createElement("video");
237
+ attach.setAttribute("controls", "")
238
+ attach.setAttribute("width", "200")
239
+ attach.volume = 0;
240
+ let source = document.createElement("source");
241
+ source.setAttribute("type", meta["mime"])
242
+ source.setAttribute("src", `/resource/${a}`)
243
+ attach.appendChild(source)
244
+ } else {
245
+ attach = document.createElement("a");
246
+ attach.href = `/resource/${a}`;
247
+ attach.target = "_blank";
248
+ attach.innerText = `Download ${meta["filename"]}`;
249
+ }
250
+ attachments.push(attach);
251
+ }
252
+ }
253
+ var msg_render = document.createElement("p");
254
+ msg_render.id = `rendered-msg-${msg["id"]}`;
255
+ msg_render.innerHTML = `<span style="color: red">${msg["author"]}</span> at ${msg["datetime"]} > ${msg["id"]}<br>${msg["body"]}`;
256
+ if (attachments != []) {
257
+ msg_render.appendChild(document.createElement("br"));
258
+ attachments.forEach((e) => {
259
+ msg_render.appendChild(e);
260
+ });
261
+ }
262
+ return msg_render;
263
+ }
264
+
265
+ var file_buffer = {};
266
+
267
+ async function upload() {
268
+ if (is_uploading) {
269
+ window.alert("Please wait till the current file's uploaded!");
270
+ return
271
+ }
272
+ var file_component = document.getElementById("attachment");
273
+ var files = file_component.files;
274
+
275
+ if (files.length == 0) {
276
+ window.alert("No file selected!");
277
+ return;
278
+ }
279
+ var data = new FormData();
280
+ data.append("file", files[0]);
281
+ var fname = files[0].name;
282
+
283
+ var indicator = document.getElementById("upload_indicator")
284
+ indicator.innerHTML = `<p style="margin: 0;">Uploading ${fname}...</p><div class="progress-indicator segmented" style="width: 100%; flex: 1; height:50%"><span id="upload_progress" class="progress-indicator-bar" style="width: 0%;"></span></div>`
285
+ const xhr = new XMLHttpRequest();
286
+ xhr.open("POST", "/index_upload", true);
287
+
288
+ xhr.setRequestHeader(
289
+ "Authorization",
290
+ `Basic ${NICK} ${TOKEN}`
291
+ )
292
+ xhr.upload.addEventListener("progress", (event) => {
293
+ if (event.lengthComputable) {
294
+ var progress = document.getElementById("upload_progress")
295
+ const percentComplete = (event.loaded / event.total) * 100;
296
+ progress.setAttribute("style", `width: ${percentComplete}%`);
297
+ }
298
+ });
299
+
300
+ xhr.onload = () => {
301
+ if (xhr.status === 200) {
302
+ var uuid = (JSON.parse(xhr.response))["uuid"];
303
+ file_buffer[fname] = uuid;
304
+ var attachment_list = document.getElementById("attachment_list");
305
+ var attach = document.createElement("option");
306
+ attach.id = uuid;
307
+ attach.innerText = fname;
308
+ attachment_list.appendChild(attach);
309
+ if (!is_muted) {
310
+ document.getElementById("file_sfx").play();
311
+ }
312
+ window.alert(`${fname} Uploaded!`);
313
+ file_component.value = "";
314
+ } else {
315
+ window.alert(`${fname} Failed to upload!`);
316
+ }
317
+ var indicator = document.getElementById("upload_indicator")
318
+ indicator.innerHTML = "";
319
+ is_uploading = false;
320
+ };
321
+ xhr.send(data)
322
+ is_uploading = true;
323
+ }
324
+
325
+ async function delete_attach(node) {
326
+ var deleting_name = node.value;
327
+ node.selectedIndex = 0;
328
+ if (window.confirm(`Are you deleting ${deleting_name} ?`)) {
329
+ var deleting_id = file_buffer[deleting_name];
330
+ var response = await fetch(`/submit_upload?recall=${deleting_id}`, {
331
+ headers: {
332
+ Authorization: `Basic ${NICK} ${TOKEN}`,
333
+ },
334
+ });
335
+ if (response.ok) {
336
+ document.getElementById(deleting_id).remove();
337
+ delete file_buffer[deleting_name];
338
+ window.alert(`${deleting_name} deleted!`);
339
+ }
340
+ }
341
+ }
342
+
343
+ document.addEventListener("DOMContentLoaded", () => {
344
+ const editor = document.getElementById("editor");
345
+ if (editor) {
346
+ editor.addEventListener("keydown", (event) => {
347
+ if (event.key === "Enter") {
348
+ if (event.shiftKey) {
349
+ return; // Allow new line with Shift + Enter
350
+ } else {
351
+ event.preventDefault(); // Prevent default Enter behavior
352
+ msg_send(); // Send the message
353
+ }
354
+ }
355
+ });
356
+ }
357
+ });
358
+
359
+ document.addEventListener("keydown", (event) => {
360
+ const keyName = event.key;
361
+ if (keyName == "F1") {
362
+ window.open("https://github.com/valine-dev/yacs/blob/main/docs/manual.md", "_blank").focus();
363
+ }
364
+ });
app/static/send.mp3 ADDED
Binary file
app/static/style.css ADDED
@@ -0,0 +1,64 @@
1
+ @import url("https://unpkg.com/98.css");
2
+
3
+ body {
4
+ background-color: black;
5
+ background-repeat: repeat;
6
+ display: flex;
7
+ flex-wrap: nowrap;
8
+ justify-content: center;
9
+ padding: 0px;
10
+ margin: 0px;
11
+ }
12
+
13
+ .main {
14
+ background-color: rgba(0, 0, 0, 0);
15
+ min-width: 60vw;
16
+ min-height: 100vh;
17
+ display: flex;
18
+ flex-flow: column nowrap;
19
+ align-items: center;
20
+ }
21
+
22
+ h1 {
23
+ color: white;
24
+ padding-top: 0.6ch;
25
+ padding-bottom: 0.6ch;
26
+ width: 100%;
27
+ text-align: center;
28
+ font-family: "Pixelated MS Sans Serif", Arial;
29
+ letter-spacing: 0;
30
+ text-shadow: black 5px 5px;
31
+ }
32
+
33
+ pre > p {
34
+ margin-bottom: 1em;
35
+ margin-top: 0;
36
+ }
37
+
38
+ footer {
39
+ color: white;
40
+ }
41
+
42
+ footer > a {
43
+ color: red
44
+ }
45
+
46
+ .cselected {
47
+ color: blue
48
+ }
49
+
50
+ blockquote::before {
51
+ content: "> ";
52
+ margin: 0;
53
+ }
54
+
55
+ blockquote {
56
+ background-color: black;
57
+ color:white;
58
+ padding: 5px;
59
+ }
60
+
61
+ code {
62
+ background-color: black;
63
+ color:white;
64
+ }
app/static/tile.gif ADDED
Binary file
app/static/tile.jpg ADDED
Binary file
app/static/welcome.mp3 ADDED
Binary file