Update README.md
[KisSync.git] / src / database / channels.js
blob90180356134e6ba7c37e202bfde2fb4bb64fd67e
1 var db = require("../database");
2 var valid = require("../utilities").isValidChannelName;
3 var Flags = require("../flags");
4 var util = require("../utilities");
5 import { createMySQLDuplicateKeyUpdate } from '../util/on-duplicate-key-update';
6 import Config from '../config';
8 const LOGGER = require('@calzoneman/jsli')('database/channels');
10 var blackHole = function () { };
12 module.exports = {
13 init: function () {
16 /**
17 * Checks if the given channel name is registered
19 isChannelTaken: function (name, callback) {
20 if (typeof callback !== "function") {
21 return;
24 if (!valid(name)) {
25 callback("Invalid channel name", null);
26 return;
29 db.query("SELECT name FROM `channels` WHERE name=?",
30 [name],
31 function (err, rows) {
32 if (err) {
33 callback(err, true);
34 return;
36 callback(null, rows.length > 0);
37 });
40 /**
41 * Looks up a channel
43 lookup: function (name, callback) {
44 if (typeof callback !== "function") {
45 return;
48 if (!valid(name)) {
49 callback("Invalid channel name", null);
50 return;
53 db.query("SELECT * FROM `channels` WHERE name=?",
54 [name],
55 function (err, rows) {
56 if (err) {
57 callback(err, null);
58 return;
61 if (rows.length === 0) {
62 callback("No such channel", null);
63 } else {
64 callback(null, rows[0]);
66 });
69 /**
70 * Searches for a channel
72 search: function (name, callback) {
73 if (typeof callback !== "function") {
74 return;
77 db.query("SELECT * FROM `channels` WHERE name LIKE ?",
78 ["%" + name + "%"],
79 function (err, rows) {
80 if (err) {
81 callback(err, null);
82 return;
84 callback(null, rows);
85 });
88 /**
89 * Searches for a channel by owner
91 searchOwner: function (name, callback) {
92 if (typeof callback !== "function") {
93 return;
96 db.query("SELECT * FROM `channels` WHERE owner LIKE ?",
97 ["%" + name + "%"],
98 function (err, rows) {
99 if (err) {
100 callback(err, null);
101 return;
103 callback(null, rows);
108 * Validates and registers a new channel
110 register: function (name, owner, callback) {
111 if (typeof callback !== "function") {
112 callback = blackHole;
115 if (typeof name !== "string" || typeof owner !== "string") {
116 callback("Name and owner are required for channel registration", null);
117 return;
120 if (!valid(name)) {
121 callback("Invalid channel name. Channel names may consist of 1-30 " +
122 "characters a-z, A-Z, 0-9, -, and _", null);
123 return;
126 module.exports.isChannelTaken(name, function (err, taken) {
127 if (err) {
128 callback(err, null);
129 return;
132 if (taken) {
133 callback("Channel name " + name + " is already taken", null);
134 return;
137 db.query("INSERT INTO `channels` " +
138 "(`name`, `owner`, `time`, `last_loaded`) VALUES (?, ?, ?, ?)",
139 [name, owner, Date.now(), new Date()],
140 function (err, _res) {
141 if (err) {
142 callback(err, null);
143 return;
146 db.users.getGlobalRank(owner, function (err, rank) {
147 if (err) {
148 callback(err, null);
149 return;
152 rank = Math.max(rank, 5);
154 module.exports.setRank(name, owner, rank, function (err) {
155 if (err) {
156 callback(err, null);
157 return;
160 callback(null, { name: name });
168 * Unregisters a channel
170 drop: function (name, callback) {
171 if (typeof callback !== "function") {
172 callback = blackHole;
175 if (!valid(name)) {
176 callback("Invalid channel name", null);
177 return;
180 db.query("DELETE FROM `channels` WHERE name=?", [name], function (err) {
182 module.exports.deleteBans(name, function (err) {
183 if (err) {
184 LOGGER.error("Failed to delete bans for " + name + ": " + err);
188 module.exports.deleteLibrary(name, function (err) {
189 if (err) {
190 LOGGER.error("Failed to delete library for " + name + ": " + err);
194 module.exports.deleteAllRanks(name, function (err) {
195 if (err) {
196 LOGGER.error("Failed to delete ranks for " + name + ": " + err);
200 callback(err, !err);
205 * Looks up channels registered by a given user
207 listUserChannels: function (owner, callback) {
208 if (typeof callback !== "function") {
209 return;
212 db.query("SELECT * FROM `channels` WHERE owner=?", [owner],
213 function (err, res) {
214 if (err) {
215 callback(err, []);
216 return;
219 callback(err, res);
223 listUserChannelsAsync: owner => {
224 return new Promise((resolve, reject) => {
225 module.exports.listUserChannels(owner, (error, rows) => {
226 if (error) {
227 reject(error);
228 } else {
229 resolve(rows);
236 * Loads the channel from the database
238 load: function (chan, callback) {
239 if (typeof callback !== "function") {
240 callback = blackHole;
243 if (!valid(chan.name)) {
244 callback("Invalid channel name", null);
245 return;
248 db.query("SELECT * FROM `channels` WHERE name=?", chan.name, function (err, res) {
249 if (err) {
250 callback(err, null);
251 return;
254 if (res.length === 0) {
255 callback("Channel is not registered", null);
256 return;
259 if (chan.dead) {
260 callback("Channel is dead", null);
261 return;
264 // Note that before this line, chan.name might have a different capitalization
265 // than the database has stored. Update accordingly.
266 chan.name = res[0].name;
267 chan.uniqueName = chan.name.toLowerCase();
268 chan.id = res[0].id;
269 chan.ownerName = typeof res[0].owner === 'string' ? res[0].owner.toLowerCase() : null;
270 chan.setFlag(Flags.C_REGISTERED);
271 chan.logger.log("[init] Loaded channel from database");
272 callback(null, true);
277 * Looks up a user's rank
279 getRank: function (chan, name, callback) {
280 if (typeof callback !== "function") {
281 return;
284 if (!valid(chan)) {
285 callback("Invalid channel name", null);
286 return;
289 db.query("SELECT * FROM `channel_ranks` WHERE name=? AND channel=?",
290 [name, chan],
291 function (err, rows) {
292 if (err) {
293 callback(err, -1);
294 return;
297 if (rows.length === 0) {
298 callback(null, 1);
299 return;
302 callback(null, rows[0].rank);
307 * Looks up multiple users' ranks at once
309 getRanks: function (chan, names, callback) {
310 if (typeof callback !== "function") {
311 return;
314 if (!valid(chan)) {
315 callback("Invalid channel name", null);
316 return;
319 var replace = "(" + names.map(function () { return "?"; }).join(",") + ")";
321 /* Last substitution is the channel to select ranks for */
322 const sub = names.concat([chan]);
324 db.query("SELECT * FROM `channel_ranks` WHERE name IN " +
325 replace + " AND channel=?", sub,
326 function (err, rows) {
327 if (err) {
328 callback(err, []);
329 return;
332 callback(null, rows.map(function (r) { return r.rank; }));
337 * Query all user ranks at once
339 allRanks: function (chan, callback) {
340 if (typeof callback !== "function") {
341 return;
344 if (!valid(chan)) {
345 callback("Invalid channel name", null);
346 return;
349 db.query("SELECT * FROM `channel_ranks` WHERE channel=?", [chan], callback);
353 * Updates a user's rank
355 setRank: function (chan, name, rank, callback) {
356 if (typeof callback !== "function") {
357 callback = blackHole;
360 if (rank < 2) {
361 module.exports.deleteRank(chan, name, callback);
362 return;
365 if (!valid(chan)) {
366 callback("Invalid channel name", null);
367 return;
370 db.query("INSERT INTO `channel_ranks` VALUES (?, ?, ?) " +
371 "ON DUPLICATE KEY UPDATE `rank`=?",
372 [name, rank, chan, rank], callback);
376 * Removes a user's rank entry
378 deleteRank: function (chan, name, callback) {
379 if (typeof callback !== "function") {
380 callback = blackHole;
383 if (!valid(chan)) {
384 callback("Invalid channel name", null);
385 return;
388 db.query("DELETE FROM `channel_ranks` WHERE name=? AND channel=?", [name, chan],
389 callback);
393 * Removes all ranks for a channel
395 deleteAllRanks: function (chan, callback) {
396 if (typeof callback !== "function") {
397 callback = blackHole;
400 if (!valid(chan)) {
401 callback("Invalid channel name", null);
402 return;
405 db.query("DELETE FROM `channel_ranks` WHERE channel=?", [chan], callback);
409 * Adds a media item to the library
411 addToLibrary: function (chan, media, callback) {
412 if (typeof callback !== "function") {
413 callback = blackHole;
416 if (!valid(chan)) {
417 callback("Invalid channel name", null);
418 return;
421 var meta = JSON.stringify({
422 bitrate: media.meta.bitrate,
423 codec: media.meta.codec,
424 scuri: media.meta.scuri,
425 embed: media.meta.embed,
426 direct: media.meta.direct
429 db.query("INSERT INTO `channel_libraries` " +
430 "(id, title, seconds, type, meta, channel) " +
431 "VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE id=id",
432 [media.id, media.title, media.seconds, media.type, meta, chan], callback);
436 * Adds a list of media items to the library
438 addListToLibrary: async function addListToLibrary(chan, list) {
439 if (!valid(chan)) {
440 throw new Error("Invalid channel name");
443 if (list.length > Config.get("playlist.max-items")) {
444 throw new Error("Cannot save list to library: exceeds max-items");
447 const items = list.map(item => ({
448 id: item.id,
449 title: item.title,
450 seconds: item.seconds,
451 type: item.type,
452 meta: JSON.stringify({
453 bitrate: item.meta.bitrate,
454 codec: item.meta.codec,
455 scuri: item.meta.scuri,
456 embed: item.meta.embed,
457 direct: item.meta.direct
459 channel: chan
460 }));
462 await db.getDB().runTransaction(tx => {
463 const insert = tx.table('channel_libraries')
464 .insert(items);
466 const update = tx.raw(createMySQLDuplicateKeyUpdate(
467 ['title', 'seconds', 'meta']
470 return tx.raw(insert.toString() + update.toString());
475 * Retrieves a media item from the library by id
477 getLibraryItem: function (chan, id, callback) {
478 if (typeof callback !== "function") {
479 return;
482 if (!valid(chan)) {
483 callback("Invalid channel name", null);
484 return;
487 db.query("SELECT * FROM `channel_libraries` WHERE id=? AND channel=?", [id, chan],
488 function (err, rows) {
489 if (err) {
490 callback(err, null);
491 return;
494 if (rows.length === 0) {
495 callback("Item not in library", null);
496 } else {
497 callback(null, rows[0]);
503 * Search the library by title
505 searchLibrary: function (chan, search, callback) {
506 if (typeof callback !== "function") {
507 return;
510 db.query("SELECT * FROM `channel_libraries` WHERE title LIKE ? AND channel=?",
511 ["%" + search + "%", chan], callback);
515 * Deletes a media item from the library
517 deleteFromLibrary: function (chan, id, callback) {
518 if (typeof callback !== "function") {
519 callback = blackHole;
522 if (!valid(chan)) {
523 callback("Invalid channel name", null);
524 return;
527 db.query("DELETE FROM `channel_libraries` WHERE id=? AND channel=?",
528 [id, chan], callback);
532 * Deletes all library entries for a channel
534 deleteLibrary: function (chan, callback) {
535 if (typeof callback !== "function") {
536 callback = blackHole;
539 if (!valid(chan)) {
540 callback("Invalid channel name", null);
541 return;
544 db.query("DELETE FROM `channel_libraries` WHERE channel=?", [chan], callback);
548 * Add a ban to the banlist
550 ban: function (chan, ip, name, note, bannedby, callback) {
551 if (typeof callback !== "function") {
552 callback = blackHole;
555 if (!valid(chan)) {
556 callback("Invalid channel name", null);
557 return;
560 db.query("INSERT INTO `channel_bans` (ip, name, reason, bannedby, channel) " +
561 "VALUES (?, ?, ?, ?, ?)",
562 [ip, name, note, bannedby, chan], callback);
566 * Check if an IP address or range is banned
568 isIPBanned: function (chan, ip, callback) {
569 if (typeof callback !== "function") {
570 return;
573 if (!valid(chan)) {
574 callback("Invalid channel name", null);
575 return;
578 var range = util.getIPRange(ip);
579 var wrange = util.getWideIPRange(ip);
581 db.query("SELECT * FROM `channel_bans` WHERE ip IN (?, ?, ?) AND channel=?",
582 [ip, range, wrange, chan],
583 function (err, rows) {
584 callback(err, err ? false : rows.length > 0);
589 * Check if a username is banned
591 isNameBanned: function (chan, name, callback) {
592 if (typeof callback !== "function") {
593 return;
596 if (!valid(chan)) {
597 callback("Invalid channel name", null);
598 return;
601 db.query("SELECT * FROM `channel_bans` WHERE name=? AND channel=?", [name, chan],
602 function (err, rows) {
603 callback(err, err ? false : rows.length > 0);
608 * Check if a user's name or IP is banned
610 isBanned: function (chan, ip, name, callback) {
611 if (typeof callback !== "function") {
612 return;
615 if (!valid(chan)) {
616 callback("Invalid channel name", null);
617 return;
620 var range = util.getIPRange(ip);
621 var wrange = util.getWideIPRange(ip);
623 db.query("SELECT COUNT(1) AS count FROM `channel_bans` WHERE (ip IN (?, ?, ?) OR name=?) AND channel=?",
624 [ip, range, wrange, name, chan],
625 function (err, rows) {
626 callback(err, err ? false : rows.length > 0 && rows[0].count > 0);
631 * Lists all bans
633 listBans: function (chan, callback) {
634 if (typeof callback !== "function") {
635 return;
638 if (!valid(chan)) {
639 callback("Invalid channel name", null);
640 return;
643 db.query("SELECT * FROM `channel_bans` WHERE channel=?", [chan], callback);
647 * Removes a ban from the banlist
649 unbanId: function (chan, id, callback) {
650 if (typeof callback !== "function") {
651 callback = blackHole;
654 if (!valid(chan)) {
655 callback("Invalid channel name", null);
656 return;
659 db.query("DELETE FROM `channel_bans` WHERE id=? AND channel=?",
660 [id, chan], callback);
664 * Removes all bans from a channel
666 deleteBans: function (chan, id, callback) {
667 if (typeof callback !== "function") {
668 callback = blackHole;
671 if (!valid(chan)) {
672 callback("Invalid channel name", null);
673 return;
676 db.query("DELETE FROM `channel_bans` WHERE channel=?", [chan], callback);
680 * Updates the `last_loaded` column to be the current timestamp
682 updateLastLoaded: function updateLastLoaded(channelId) {
683 if (channelId <= 0) {
684 return;
687 db.query("UPDATE channels SET last_loaded = ? WHERE id = ?", [new Date(), channelId], error => {
688 if (error) {
689 LOGGER.error(`Failed to update last_loaded column for channel ID ${channelId}: ${error}`);
695 * Updates the `owner_last_seen` column to be the current timestamp
697 updateOwnerLastSeen: function updateOwnerLastSeen(channelId) {
698 if (channelId <= 0) {
699 return;
702 db.query("UPDATE channels SET owner_last_seen = ? WHERE id = ?", [new Date(), channelId], error => {
703 if (error) {
704 LOGGER.error(`Failed to update owner_last_seen column for channel ID ${channelId}: ${error}`);