1 var sqlite3 = require("sqlite3")
2 var logger = require("./logger")
3 var async = require("async")
6 init: function(logger, maxVideoLength) {
7 var db = new Database(logger, maxVideoLength)
12 function Database(logger, maxVideoLength) {
13 this.db = new sqlite3.Database("./cytubebot.db")
15 this.maxVideoLength = maxVideoLength
19 // Creates the tables if they do not exist
20 Database.prototype.createTables = function() {
22 this.db.run("CREATE TABLE IF NOT EXISTS users(uname TEXT, blacklisted TEXT, block TEXT, primary key(uname))")
23 this.db.run("CREATE TABLE IF NOT EXISTS chat(timestamp INTEGER, username TEXT, msg TEXT, channel TEXT)")
24 this.db.run("CREATE TABLE IF NOT EXISTS " +
25 "videos(type TEXT, id TEXT, duration_ms INTEGER, title TEXT, flags INTEGER, primary key(type, id))")
26 this.db.run("CREATE TABLE IF NOT EXISTS video_stats(type TEXT, id TEXT, uname TEXT)")
27 this.db.run("CREATE TABLE IF NOT EXISTS user_count(timestamp INTEGER, count INTEGER, primary key(timestamp, count))")
28 this.db.run("CREATE TABLE IF NOT EXISTS version(key TEXT, value TEXT, PRIMARY KEY(key))")
33 // Updates the tables as needed
34 Database.prototype.updateTables = function() {
36 this.getVersion(function(version) {
38 var update = self.db.prepare("INSERT INTO version(key, value) VALUES (?, ?)", ['dbversion', '1'])
39 update.run(function() {
40 self.db.run("ALTER TABLE users ADD rank INTEGER")
47 // Sets a flag on a video
48 // type - The video type eg. "yt"
49 // id - The ID of the video
50 // flags - The flag, should be 1
51 // title - Title of the video
52 Database.prototype.flagVideo = function(type, id, flags, title) {
53 this.logger.syslog.log("*** Flagging video: " + title + " with flag: " + flags)
55 var stmt = this.db.prepare("UPDATE videos SET flags = ? WHERE type = ? AND id = ?", [flags, type, id])
61 // WARNING - This is experimental
62 // Deletes videos from the database that are like like
63 // We serialize the database to stop the final getVideosCount from executing
64 // before the other queries have run
65 // like - What to match. Example: %skrillex% will delete all videos
66 // with the word "skrillex" in it
67 // callback - The callback function, sends a chatMsg with how many videos
69 Database.prototype.deleteVideos = function(like, callback) {
71 this.logger.syslog.log("*** Deleting videos where title like " + like)
76 var getAfter = function() {
77 db.getVideosCount(function(num) {
79 callback(before - after)
83 var deleteVideos = function() {
84 for (var i = 0; i < videoIds.length; i++) {
85 var stmt1 = db.db.prepare("DELETE FROM videos WHERE id = ? " +
86 "AND type = ?", [videoIds[i]["id"], videoIds[i]["type"]])
87 var stmt2 = db.db.prepare("DELETE FROM video_stats WHERE id = ? AND type = ?", [videoIds[i]["id"], videoIds[i]["type"]])
95 var getVideoIds = function() {
96 db.db.all("SELECT id, type FROM videos WHERE title LIKE ? AND flags = 0", (like), function(err, rows) {
105 var start = function() {
106 db.getVideosCount(function(num) {
112 // Lets get on the ride
113 this.db.serialize(start)
116 // Inserts a chatMsg into the chat table
117 // msg - The message that we are inserting
118 // time - The timestamp of the message
119 // nick - The user who said it
120 // room - The room in which it was said
121 Database.prototype.insertChat = function(msg, time, nick, room) {
122 var stmt = this.db.prepare("INSERT INTO chat VALUES(?, ?, ?, ?)", [time, nick, msg, room])
128 // Inserts a video into the database
129 // site - The type of video eg. "yt"
130 // vid - The ID of the video
131 // title - The title of the video
132 // dur - The duration of the video
133 // nick - The user who added the video
134 Database.prototype.insertVideo = function(site, vid, title, dur, nick) {
135 this.logger.syslog.log("*** Inserting: " + title + " into the database")
137 var stmt1 = this.db.prepare("INSERT OR IGNORE INTO videos VALUES(?, ?, ?, ?, ?)", [site, vid, dur * 1000, title, 0])
138 var stmt2 = this.db.prepare("INSERT INTO video_stats VALUES(?, ?, ?)", [site, vid, nick])
147 // Inserts a user into the user table
148 // username - The user we are adding
149 // rank - The users rank
150 Database.prototype.insertUser = function(username, rank) {
155 var stmt = this.db.prepare("INSERT OR IGNORE INTO users VALUES (?, 'false', 'false', ?)", [username, rank])
161 // Sets the blacklisted flag on the user table
162 // username - The user we are setting the flag on
163 // flag - The flag to set
164 // callback - The callback function
165 Database.prototype.insertUserBlacklist = function(username, flag, callback) {
166 this.logger.syslog.log("Setting blacklist: " + flag + " on user: " + username)
168 var stmt = this.db.prepare("UPDATE users SET blacklisted = ? WHERE uname = ?", [flag, username])
172 // Sets the block column of user
175 Database.prototype.insertUserBlock = function(username, flag, callback) {
176 this.logger.syslog.log("*** Setting block: " + flag + " on user: " + username)
177 var stmt = this.db.prepare("UPDATE users SET block = ? WHERE uname = ?", [flag, username])
182 // Handles changes to a user's rank
183 // user - The user whose rank we are changing
184 // rank - The rank to set
185 Database.prototype.insertUserRank = function(username, rank) {
186 var stmt = this.db.prepare("UPDATE users SET rank = ? WHERE uname = ?", [rank, username])
190 // Inserts the usercount, from a usercount frame
191 // count - The number of users at timestamp
192 // timestamp - The time the frame was sent
193 Database.prototype.insertUsercount = function(count, timestamp) {
194 var stmt = this.db.prepare("INSERT INTO user_count VALUES(?, ?)", [timestamp, count])
198 // Gets all the users with a blacklist
199 // callback - The callback function
200 Database.prototype.getAllBlacklistedUsers = function(callback) {
201 var stmt = this.db.prepare("SELECT uname FROM users WHERE blacklisted = '1'")
204 stmt.all(function(err, rows) {
206 for (var i = 0; i < rows.length; i++) {
207 users.push(rows[i]["uname"])
214 // Gets all the blocked users
215 Database.prototype.getAllBlockedUsers = function(callback) {
216 var stmt = this.db.prepare("SELECT uname FROM users WHERE block = '1'")
219 stmt.all(function(err, rows) {
221 for (var i = 0; i < rows.length; i++) {
222 users.push(rows[i]["uname"])
229 // Gets the usercounts for the average users chart
230 // Basically ported from naoko
231 // callback - The callback function
232 Database.prototype.getAverageUsers = function(callback) {
233 var select_cls = "SELECT STRFTIME('%s', STRFTIME('%Y-%m-%dT%H:00', timestamp/1000, 'UNIXEPOCH'))*1000 AS timestamp," +
234 " CAST(ROUND(AVG(count)) AS INTEGER) AS count FROM user_count "
235 var group_cls = " GROUP BY STRFTIME('%Y%m%d%H', timestamp/1000, 'UNIXEPOCH')"
236 var sql = select_cls + group_cls
238 var stmt = this.db.prepare(sql)
241 stmt.all(function(err, rows) {
246 // Format data for google charts
247 for (var i = 0; i < rows.length; i++) {
248 returnData.push([rows[i]["timestamp"], rows[i]["count"]])
250 callback(null, returnData)
254 // Gets the amount of messages by each user
255 // Used for the chat stats chart
256 // callback - The callback function
257 Database.prototype.getChatStats = function(callback) {
258 var select_cls = "SELECT username, count(*) as count FROM chat "
259 var group_cls = " GROUP BY username ORDER BY count(*) DESC"
260 var sql = select_cls + group_cls
261 var stmt = this.db.prepare(sql)
264 stmt.all(function(err, rows) {
269 // Format data for google charts
270 for (var i = 0; i < rows.length; i++) {
271 if (rows[i]["username"] !== "") {
272 returnData.push([rows[i]["username"], rows[i]["count"]])
275 callback(null, returnData)
279 // Does ANALYZE on the database
280 // Used to get the counts of videos, users, and chat
281 // callback - The callback function
282 Database.prototype.getGeneralStats = function(callback) {
285 var stmt2 = "SELECT stat FROM sqlite_stat1 WHERE tbl = 'users' OR tbl = 'videos' OR tbl = 'chat'"
287 this.db.serialize(function() {
289 self.db.all(stmt2, function(err, rows) {
296 // Gets the 10 most popular videos
297 // Used for the popular videos chart
298 // callback - The callback function
299 Database.prototype.getPopularVideos = function(callback) {
300 var select_cls = "SELECT videos.type, videos.id, videos.title, videos.flags & 1, count(*) AS count FROM videos, video_stats"
301 var where_cls = " WHERE video_stats.type = videos.type AND video_stats.id = videos.id AND NOT videos.flags & 2 "
302 var group_cls = " GROUP BY videos.type, videos.id ORDER BY count(*) DESC LIMIT 10"
303 var sql = select_cls + where_cls + group_cls
305 var stmt = this.db.prepare(sql)
309 stmt.all(function(err, rows) {
314 // Format data for google charts
315 for (var i = 0; i < rows.length; i++) {
316 returnData.push([rows[i]["type"], rows[i]["id"], rows[i]["title"],
317 rows[i]["flags"], rows[i]["count"]
320 callback(null, returnData)
324 // Gets a chat message
325 // If nick is given, it will select a quote from that user
326 // If no nick is given, it will select a random quote
327 // nick - The username we are getting a quote for
328 // callback - The callback function
329 Database.prototype.getQuote = function(nick, callback) {
330 nick = nick.split(" ")[0]
334 stmt = this.db.prepare("SELECT username, msg, timestamp FROM chat WHERE " +
335 "username = ? COLLATE NOCASE ORDER BY RANDOM() LIMIT 1", [nick])
337 stmt.get(function(err, row) {
344 stmt = "SELECT username, msg, timestamp FROM chat WHERE msg NOT LIKE '/me%' " +
345 "AND msg NOT LIKE '$%' ORDER BY RANDOM() LIMIT 1"
346 this.db.get(stmt, function(err, row) {
353 // Fetches all of the stats required by the stats page
354 // Functions are chained together with the last function
355 // giving the callback the final returnData object
356 // room - The room the bot is currently in
357 // callback - The callback function
358 Database.prototype.getStats = function(room, callback) {
361 // Lets go on another ride
363 userVideoStats: self.getVideoStats.bind(self),
364 userChatStats: self.getChatStats.bind(self),
365 popularVideos: self.getPopularVideos.bind(self),
366 averageUsers: self.getAverageUsers.bind(self)
367 }, function(err, results) {
372 results["room"] = room
377 // Checks whether a user is blacklisted
378 // username - The user we are checking
379 // callback - The callback function
380 Database.prototype.getUserBlacklist = function(username, callback) {
381 var stmt = this.db.prepare("SELECT blacklisted FROM users WHERE uname = ?", [username])
383 stmt.get(function(err, row) {
384 if (typeof row !== "undefined") {
385 callback(row["blacklisted"])
390 // Selects the autodelete column for user
391 // username - The user we are looking up
392 // callback - Callback function
393 Database.prototype.getUserBlock = function(username, callback) {
394 var stmt = this.db.prepare("SELECT block FROM users WHERE uname = ?", [username])
396 stmt.get(function(err, row) {
397 if (typeof row !== "undefined") {
398 callback(row["block"])
403 // Gets a user's rank
404 // Callback - callback function
405 Database.prototype.getUserRank = function(username, callback) {
406 var stmt = this.db.prepare("SELECT rank FROM users WHERE uname = ?", [username])
408 stmt.get(function(err, row) {
409 if (typeof row !== "undefined") {
410 callback(row["rank"])
415 // Gets the database version
416 Database.prototype.getVersion = function(callback) {
417 var stmt = this.db.prepare("SELECT value FROM version WHERE key = 'dbversion'")
419 stmt.get(function(err, row) {
420 if (row === undefined) {
428 // Used by the addRandom() method
429 // Fetches num random videos, if num is zero it fetches 1 video
430 // Limits videos to those under 10 minutes and whose type is yt, dm, or vm
431 // num - The number of videos we are getting
432 // callback - The callback function
433 Database.prototype.getVideos = function(num, callback) {
438 var stmt = this.db.prepare("SELECT type, id, duration_ms, title FROM videos " +
439 "WHERE flags = 0 AND duration_ms < ? AND (type = 'yt' OR type = 'dm' OR type = 'vm') " +
440 "ORDER BY RANDOM() LIMIT ?", [this.maxVideoLength, num])
442 stmt.all(function(err, rows) {
447 Database.prototype.getVideosCountForClean = function(callback) {
450 this.db.get("SELECT count(*) AS count FROM videos WHERE flags = 0 "
451 + "AND type = 'yt' AND duration_ms < ?", [this.maxVideoLength], function(err, row) {
453 return self.logger.errlog.log(err)
456 callback(row["count"])
460 // Gets the number of videos in the database
461 // callback - The callback function
462 Database.prototype.getVideosCount = function(callback) {
465 this.db.get("SELECT count(*) AS count FROM videos", function(err, row) {
467 return self.logger.errlog.log(err)
470 callback(row["count"])
474 // Gets the number of videos added by each user
475 // Used by the video by user chart
476 // callback - The callback function
477 Database.prototype.getVideoStats = function(callback) {
478 var select_cls = "SELECT uname, count(*) AS count FROM video_stats vs, videos v "
479 var where_cls = " WHERE vs.type = v.type AND vs.id = v.id AND NOT v.flags & 2 "
480 var group_cls = " GROUP BY uname ORDER BY count(*) DESC"
481 var sql = select_cls + where_cls + group_cls
482 var stmt = this.db.prepare(sql)
485 stmt.all(function(err, rows) {
490 // Format data for google charts
491 for (var i = 0; i < rows.length; i++) {
492 if (rows[i]["uname"] !== "") {
493 returnData.push([rows[i]["uname"], rows[i]["count"]])
497 callback(null, returnData)
501 // Gets the flag of a video
502 // type - The type of the video we are looking up
503 // id - The ID of the video we are looking up
504 // callback - The callback function
505 Database.prototype.getVideoFlag = function(type, id, callback) {
506 var stmt = this.db.prepare("SELECT flags FROM videos videos WHERE type = ? AND id = ?", [type, id])
508 stmt.get(function(err, row) {