From 2310912b24f9a482a440cd2d115fafd11b7cade3 Mon Sep 17 00:00:00 2001 From: not manx Date: Sat, 23 May 2020 01:17:56 +0000 Subject: [PATCH] "clsql -> cl-dbi. Added doc comments." --- bantflags.user.js | 315 +++++++++++++++++++--------------------- src/bantflags.asd | 2 +- src/config.example.lisp | 2 +- src/db.lisp | 47 ++++-- src/main.lisp | 10 +- src/utils.lisp | 2 +- 6 files changed, 189 insertions(+), 189 deletions(-) diff --git a/bantflags.user.js b/bantflags.user.js index ad26da0..a87a420 100644 --- a/bantflags.user.js +++ b/bantflags.user.js @@ -30,7 +30,7 @@ // see the LICENSE file or // Change this if you want verbose debuging information in the console. -const debugMode = false; +const debugMode = true; const isGM4 = typeof GM_setValue === 'undefined'; const setValue = isGM4 ? GM.setValue : GM_setValue; @@ -40,12 +40,12 @@ const xmlHttpRequest = isGM4 ? GM.xmlHttpRequest : GM_xmlhttpRequest; // // DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT // -const version = 2; // Breaking changes. +const version = encodeURIComponent(2); // Breaking changes. const back_end = 'https://flags.plum.moe/'; -const api_flags = 'api/flags'; -const flag_dir = 'flags/'; -const api_get = 'api/get'; -const api_post = 'api/post'; +const api_flags = back_end + 'api/flags'; +const flag_dir = back_end + 'flags/'; +const api_get = back_end + 'api/get'; +const api_post = back_end + 'api/post'; const namespace = 'BintFlegs'; // If you increase this the server will ignore your post. @@ -59,6 +59,11 @@ let flagsLoaded = false; // DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT // +const debug = text => { + if (debugMode) + console.log('[BantFlags] ' + text); +} + // Test unqiue CSS paths to figure out what board software we're using. const software = { yotsuba: window.location.host === 'boards.4chan.org', @@ -66,29 +71,14 @@ const software = { foolfuuka: document.querySelector('div[id="main"] article header .post_data') !== null }; -/** Wrapper around Object.assign and document.createElement - * @param {string} element - The HTML Element to create. - * @param {object} source - The properties to assign to the element. - * @returns {object} The HTML tag created */ const createAndAssign = (element, source) => Object.assign(document.createElement(element), source); const toggleFlagButton = state => document.getElementById('append_flag_button').disabled = state === 'off' ? true : false; -/** Add a stylesheet to the head of the document. - * @param {string} css - The CSS rules for the stylesheet. - * @returns {object} The style element appended to the head */ -const addGlobalStyle = css => document.head.appendChild(createAndAssign('style', { - type: 'text/css', - innerHTML: css -})); - -/** Write extra information to the console if debugMode is set to true. - * @param {string} text - The text to write to the console. */ -function debug(text) { - if (debugMode) { - console.log('[BantFlags] ' + text); - } -} +const flagSource = flag => flag_dir + flag + ".png"; + +/** Add styles to the */ +const addGlobalStyle = css => document.head.appendChild(createAndAssign('style', { innerHTML: css })); /** Wrapper around GM_xmlhttpRequest. * @param {string} method - The HTTP method (GET, POST). @@ -108,7 +98,7 @@ const makeRequest = ((method, url, data, func) => { /** Itterate over selected flags are store them across browser sessions.*/ function saveFlags() { regions = []; - let selectedFlags = document.getElementsByClassName("bantflags_flag"); + const selectedFlags = document.querySelectorAll("bantflags_flag"); for (var i = 0; i < selectedFlags.length; i++) { regions[i] = selectedFlags[i].title; @@ -118,7 +108,7 @@ function saveFlags() { } /** Add a flag to our selection. - * @param {string} flag - The flag to add to our selection. If no value is passed it takes the current value from the flagSelect. */ + * @param {string} flag - The flag to add to our selection. Either passed from saved flags or the current value of flagSelect */ function setFlag(flag) { let UID = Math.random().toString(36).substring(7); let flagName = flag ? flag : document.querySelector('#flagSelect input').value; @@ -126,27 +116,24 @@ function setFlag(flag) { flagContainer.appendChild(createAndAssign('img', { title: flagName, - src: back_end + flag_dir + flagName + '.png', + src: flagSource(flagName), id: UID, className: 'bantflags_flag' })); - if (flagContainer.children.length >= max_flags) { + if (flagContainer.children.length >= max_flags) toggleFlagButton('off'); - } - document.getElementById(UID).addEventListener("click", (e) => { + document.getElementById(UID).addEventListener("click", e => { flagContainer.removeChild(e.target); toggleFlagButton('on'); saveFlags(); }); - if (!flag) { // When we add a flag to our selection, save it for when we reload the page. + if (!flag) // We've added a new flag to our selection saveFlags(); - } } -/** Create flag button and initialise our selected flags */ function init() { let flagsForm = createAndAssign('div', { className: 'flagsForm', @@ -155,15 +142,13 @@ function init() { // Where do we append the flagsForm to? if (software.yotsuba) { document.getElementById('delform').appendChild(flagsForm); } - if (software.nodegucaDoushio) { document.querySelector('section').append(flagsForm); } // As posts are added the flagForm moves up the page. Could we append this after .section? + else if (software.nodegucaDoushio) { document.querySelector('section').append(flagsForm); } // As posts are added the flagForm moves up the page. Could we append this after .section? - for (var i in regions) { + for (let i = 0; i < regions.length; i++) { setFlag(regions[i]); } - document.getElementById('append_flag_button').addEventListener('click', - () => flagsLoaded ? setFlag() : alert('Load flags before adding them.')); - + document.getElementById('append_flag_button').addEventListener('click', () => flagsLoaded ? setFlag() : alert('Load flags before adding them.')); document.getElementById('flagLoad').addEventListener('click', makeFlagSelect, { once: true }); } @@ -171,14 +156,16 @@ function init() { function makeFlagSelect() { makeRequest( "GET", - back_end + api_flags, - "version=" + encodeURIComponent(version), + api_flags, + "", // We can't send data, it's a GET request. function (resp) { debug('Loading flags.'); + if (resp.status !== 200) { - return; - } - + console.log('Couldn\'t get flag list from server') + return; + } + let flagSelect = document.getElementById('flagSelect'); let flagList = flagSelect.querySelector('ul'); let flagInput = flagSelect.querySelector('input'); @@ -186,16 +173,18 @@ function makeFlagSelect() { for (var i = 0; i < flags.length; i++) { let flag = flags[i]; - flagList.appendChild(createAndAssign('li', { - innerHTML: ' ' + flag + '' - })); + flagList.appendChild(createAndAssign('li',{ + innerHTML: `${flag}` + })); } - flagSelect.addEventListener('click', (e) => { - listItem = e.target.nodeName === 'LI' ? e.target : e.target.parentNode; // So we can click the flag image and still select the flag. - if (listItem.nodeName === 'LI') { - flagInput.value = listItem.querySelector('span').innerHTML; - } + flagSelect.addEventListener('click', function (e) { + // So it works if we click the flag image + const node = e.target.nodeName === 'LI' ? e.target : e.target.parentNode; + if (node.nodeName === 'LI') { + flagInput.value = node.querySelector('span').innerHTML; + } + flagList.classList.toggle('hide'); }); @@ -206,80 +195,67 @@ function makeFlagSelect() { }); } -/** add all of thhe post numbers on the page to postNrs. - * @param {string} selector - The CSS selector who's id is the post number. */ +/** add all of the post numbers on the page to postNrs. */ function getPosts(selector) { - let posts = document.querySelectorAll(selector); - - for (var i = 0; i < posts.length; i++) { - let postNumber = software.yotsuba - ? posts[i].id.replace('pc', '') // Fuck you 4chan. - : posts[i].id; - postNrs.push(postNumber); - } - debug(postNrs); -} - -/** Take the response from resolveRefFlags and append flags to their respective post numbers. - * @param {XMLHttpRequest} response - The response data from resolveRefFlags. */ -function loadFlags(response) { - debug('JSON: ' + response.responseText); - var jsonData = JSON.parse(response.responseText); - - Object.keys(jsonData).forEach(function (post) { - - // Get the post header with a CSS selector. Different for each board software. - var flagContainer; - if (software.nodegucaDoushio) { flagContainer = document.querySelector('[id="' + post + '"] header'); } - if (software.yotsuba) { flagContainer = document.querySelector('[id="pc' + post + '"] .postInfo .nameBlock'); } - if (software.foolfuuka) { flagContainer = document.querySelector('[id="' + post + '"] .post_data .post_type'); } - - let flags = jsonData[post]; - if (flags.length > 0) { - console.log('[BantFlags] Resolving flags for >>' + post); - - for (var i = 0; i < flags.length; i++) { - let flag = flags[i]; - - let newFlag = createAndAssign('a', { - innerHTML: '', - className: 'bantFlag', - target: '_blank' - }); - - if (software.foolfuuka) { - newFlag.style = 'padding: 0px 0px 0px ' + (3 + 2 * (i > 0)) + 'px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;'; - } + const posts = document.querySelectorAll(selector); - if (software.nodegucaDoushio) { - newFlag.title = flag; - } - - flagContainer.append(newFlag); - - console.log('\t -> ' + flag); - } - } - }); + for (let i = 0; i < posts.length; i++) { + const postNumber = software.yotsuba + ? posts[i].id.substr(2) // Fuck you 4chan. + : posts[i].id; - postNrs = []; + postNrs.push(postNumber); + } + debug(postNrs); } /** Get flags from the database using values in postNrs and pass the response on to onFlagsLoad */ function resolveFlags() { - debug('Board is: ' + board_id); makeRequest( 'POST', - back_end + api_get, - 'post_nrs=' + encodeURIComponent(postNrs) + '&board=' + encodeURIComponent(board_id) + '&version=' + encodeURIComponent(version), + api_get, + 'post_nrs=' + encodeURIComponent(postNrs) + '&board=' + encodeURIComponent(board_id) + '&version=' + version, function (resp) { + if (resp.status !== 200) { console.log('[bantflags] Couldn\'t load flags. Refresh the page.'); return; } - loadFlags(resp); - } - ); + + const jsonData = JSON.parse(resp.responseText); + debug(`JSON: ${resp.responseText}`); + + Object.keys(jsonData).forEach(post => { + let flags = jsonData[post]; + + if (flags.length <= 0) + return; + + debug(`Resolving flags for >>${post}`); + + let flagContainer; + if (software.yotsuba) { flagContainer = document.querySelector(`[id="pc${post}"] .postInfo .nameBlock`); } + else if (software.foolfuuka) { flagContainer = document.querySelector(`[id="${post}"] .post_data .post_type`); } + else if (software.nodegucaDoushio) { flagContainer = document.querySelector(`[id="${post}"] header`); } + + for (let i = 0; i < flags.length; i++) { + const flag = flags[i]; + + const newFlag = createAndAssign('a', { + innerHTML: ``, + className: 'bantFlag', + target: '_blank', + title: flag + }); + + flagContainer.append(newFlag); + + debug(`\t -> ${flag}`); + } + }); + + postNrs = []; + }); } function main() { @@ -289,19 +265,16 @@ function main() { } // See Docs/styles.css - addGlobalStyle('.flagsForm{float: right; clear: right; margin: 20px 10px;} #flagSelect{display:none;} .bantflags_flag{padding: 1px;} [title^="Romania"]{ position: relative; animation: shakeAnim 0.1s linear infinite;} @keyframes shakeAnim{ 0% {left: 1px;} 25% {top: 2px;} 50% {left: 1px;} 75% {left: 0px;} 100% {left: 2px;}} #flagSelect ul {list-style-type: none;padding: 0;margin-bottom: 0;cursor: pointer;bottom: 100%;height: 200px;overflow: auto;position: absolute;width:200px;background-color:#fff}#flagSelect ul li {display: block;}#flagSelect ul li:hover {background-color: #ddd;}#flagSelect {position: absolute;}#flagSelect input {width: 200px;} #flagSelect .hide {display: none;}#flagSelect img {margin-left: 2px;}'); + addGlobalStyle('.bantFlag{padding: 0px 0px 0px 5px; display: inline-block; width: 16px; height: 11px; position: relative;} .bantflags_flag{padding: 1px;} [title^="Romania"]{ position: relative; animation: shakeAnim 0.1s linear infinite;} @keyframes shakeAnim{ 0% {left: 1px;} 25% {top: 2px;} 50% {left: 1px;} 75% {left: 0px;} 100% {left: 2px;}}.flagsForm{float: right; clear: right; margin: 20px 10px;} #flagSelect{display:none;} #flagSelect ul{list-style-type: none;padding: 0;margin-bottom: 0;cursor: pointer;bottom: 100%;height: 200px;overflow: auto;position: absolute;width:200px;background-color:#fff} #flagSelect ul li {display: block;} #flagSelect ul li:hover {background-color: #ddd;}#flagSelect {position: absolute;}#flagSelect input {width: 200px;} #flagSelect .hide {display: none;}#flagSelect img {margin-left: 2px;}') - // We get flags using different selectors, and we need to align them differently. if (software.yotsuba) { - debug('4chan'); getPosts('.postContainer'); - addGlobalStyle('.bantFlag {padding: 0px 0px 0px 5px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;} .flag{top: 0px;left: -1px}'); + addGlobalStyle('.flag{top: 0px;left: -1px}'); init(); } if (software.nodegucaDoushio) { - debug('Nineball'); getPosts('section[id], article[id]'); addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}'); @@ -309,17 +282,18 @@ function main() { } if (software.foolfuuka) { - debug('FoolFuuka'); getPosts('article[id]'); addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}'); } - + board_id = window.location.pathname.split('/')[1]; - resolveFlags(); + debug(board_id); + + resolveFlags(); } -if (isGM4) { +if (isGM4) { // Fuck you GM4 (async () => { regions = await getValue(namespace); main(); @@ -330,20 +304,20 @@ else { main(); } +const postFlags = (post_nr, func = resp => debug(resp.responseText)) => makeRequest( + 'POST', + api_post, + `post_nr=${encodeURIComponent(post_nr)}&board=${encodeURIComponent(board_id)}®ions=${encodeURIComponent(regions)}&version=${version}`, + func); + if (software.yotsuba) { const GetEvDetail = e => e.detail || e.wrappedJSObject.detail; + + // 4chanX and native extension respectively + document.addEventListener('QRPostSuccessful', e => postFlags(e.detail.postID)); + document.addEventListener('4chanQRPostSuccess', e => postFlags(GetEvDetail(e).postId)); - const postFlags = post_nr => makeRequest( - 'POST', - back_end + api_post, - 'post_nr=' + encodeURIComponent(post_nr) + '&board=' + encodeURIComponent(board_id) + '®ions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version), - func = resp => debug(resp.responseText)); - - // Send flags to the backend when we makle a post. Top is 4chanX, bottom is native extension. - document.addEventListener('QRPostSuccessful', e => postFlags(e.detail.postID), false); - document.addEventListener('4chanQRPostSuccess', e => postFlags(GetEvDetail(e).postId), false); - - // I need to look at these. + // I need to look at these. document.addEventListener('ThreadUpdate', function (e) { var evDetail = GetEvDetail(e); var evDetailClone = typeof cloneInto === 'function' ? cloneInto(evDetail, unsafeWindow) : evDetail; @@ -373,44 +347,49 @@ if (software.yotsuba) { }); resolveFlags(); - }, false); + }, false); } if (software.nodegucaDoushio) { - // This is poking at the mutations made on the page to figure out what happened and thus what actions to take. - // There is full support for nodeguca but I don't have a Doushio board I feel comfortable spamming to ensure it works properly there. There is at least partial support. - new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - if (mutation.addedNodes.length > 0) { // A post was added. - var firstAddedNode = mutation.addedNodes[0].nodeName; - - // Enter a thread or change boards. Checks for when a post is added while in the index. - if (mutation.target.nodeName === 'THREADS' && firstAddedNode !== 'HR' && firstAddedNode !== 'SECTION') { - board_id = window.location.pathname.split('/')[1]; - setTimeout(getPosts('section[id], article[id]'), 2000); - resolveFlags(); - init(); - } - - // You post. - if (firstAddedNode === 'HEADER') { - let data = 'post_nr=' + encodeURIComponent(mutation.target.id) + '&board=' + encodeURIComponent(board_id) + '®ions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version); - makeRequest( - 'POST', - back_end + api_post, - data, - function () { - postNrs.push(mutation.target.id); - resolveFlags(); - }); - } - - // Someone else posts. Checks to see if you're hovering over a post. - if (firstAddedNode === 'ARTICLE' && mutation.target.nodeName !== "BODY" && mutation.target.id !== 'hover_overlay') { - postNrs.push(mutation.addedNodes[0].id); - setTimeout(resolveFlags, 1500); - } + + const postFunc = function() { + postNrs.push(mutation.target.id); + resolveFlags(); + } + + const badNodes = ['HR', 'SECTION']; + + new MutationObserver(mutations => { + mutations.forEach(mutation => { + if (mutation.addedNodes.length <= 0) + return; // We only care if something post was added + + var firstAddedNode = mutation.addedNodes[0].nodeName; + + // Enter a thread / change boards + if (mutation.target.nodeName === 'THREADS') { + if (badNodes.includes(firstAddedNode)) + return; // We are in the index and a post was added, handled properly further down + + board_id = window.location.pathname.split('/')[1]; + setTimeout(getPosts('section[id], article[id]'), 2000); + resolveFlags(); + init(); } - }); - }).observe(document.body, { childList: true, subtree: true }); + + // We post + if (firstAddedNode === 'HEADER') { + postFlags(mutation.target.id, postFunc) + } + + // Someone else posts + if (firstAddedNode === 'ARTICLE') { + if (mutation.target.nodeName === 'BODY' || mutation.target.id === 'hover_overlay') + return; // User is hovering over a post + + postNrs.push(mutation.addedNodes[0].id); + setTimeout(resolveFlags, 1500); + } + }); + }).observe(document.body, { childList: true, subtree: true }); } diff --git a/src/bantflags.asd b/src/bantflags.asd index 47f6eb9..343a6ef 100644 --- a/src/bantflags.asd +++ b/src/bantflags.asd @@ -7,7 +7,7 @@ :serial t :depends-on (:hunchentoot :str - :clsql + :cl-dbi :jonathan) :Components ((:file "utils") diff --git a/src/config.example.lisp b/src/config.example.lisp index c789480..bb053bf 100644 --- a/src/config.example.lisp +++ b/src/config.example.lisp @@ -1,6 +1,6 @@ (defvar config '((boards "bant") (staging-password "not implemented") - (db-conn "localhost" "bantflags" "flags" "default") + (db-conn "bantflags" "flags" "default") (poolsize 3) (www-root #p"/path/to/files/"))) diff --git a/src/db.lisp b/src/db.lisp index 55891ad..8ea9dc4 100644 --- a/src/db.lisp +++ b/src/db.lisp @@ -1,33 +1,52 @@ +;; Databases in common lisp are the fucking worst. +;; Don't even bother. + ;; Comparing strings with both (defparameter *flags* (make-hash-table :test 'equal)) (defparameter *boards* (make-hash-table :test 'equal)) (defparameter *flags-txt* nil) -(defparameter conn nil) +(defparameter conn-str nil) (defvar get-posts-sql "SELECT posts.post_nr, flags.flag from flags left join postflags on (postflags.flag = flags.id) left join posts on (postflags.post_nr = posts.id) where posts.post_nr in (~{'~a'~^,~}) and posts.board = '~a';") -;; (clsql:start-sql-recording) + +(defun fuck-you-fukamachi (conn |;_;|) + "What the fuck is going on with what dbi:fetch returns? why is it a +fucking list with the database columns as a symbols with pipes around +them? How in the dicking shit am I supposed to use :|post_nr| 1234 in +any useful or practical way? Why does this fucking Wumpus of a human +being Fukamachi feel the need to duplicate so much data? Don't get me +wrong, clsql is easily worse to work with, but at least it was fucking +smart enough to make the database fields into (values rows columns)" + (mapcar (lambda (x) (list (nth 1 x) (nth 3 x))) + (dbi:fetch-all (dbi:execute (dbi:prepare conn |;_;|))))) (defmacro dbfun (name &rest body) `(defun ,name ,(car body) - (clsql:with-database (db conn :database-type :mysql :pool t) + (dbi:with-connection (conn :mysql + :database-name (car conn-str) + :username (nth 1 conn-str) + :password (nth 2 conn-str)) ,@(cdr body)))) (defun flag-id (flag) (gethash flag *flags*)) +(dbfun ping () + (dbi:ping conn)) + (dbfun insert-post (post_nr board flags) - (clsql:query (format nil "insert ignore into posts (post_nr, board) values (~a, '~a');" post_nr board)) - (let ((post-id (caar (clsql:query (format nil "select id from posts where post_nr = ~a and board = '~a';" post_nr board))))) - (clsql:execute-command - (with-output-to-string (s) - (format s "insert into postflags (post_nr, flag) values") - (loop for flag in (butlast flags) - do (format s "(~a,~a)," post-id (flag-id flag))) - (format s "(~a,~a);" post-id (flag-id (car (last flags)))) - :database db)))) + (dbi:do-sql conn + (format nil "insert ignore into posts (post_nr, board) values (~a, '~a');" post_nr board)) + (let ((post-id (cadr (dbi:fetch (dbi:execute (dbi:prepare conn "select id from posts where post_nr = 9999 and board = 'bant';")))))) + (dbi:do-sql conn + (with-output-to-string (s) + (format s "insert into postflags (post_nr, flag) values") + (loop for flag in (butlast flags) + do (format s "(~a,~a)," post-id (flag-id flag))) + (format s "(~a,~a);" post-id (flag-id (car (last flags)))))))) (dbfun get-posts (posts board) - (let ((result (clsql:query (format nil get-posts-sql posts board) :database db)) + (let ((result (fuck-you-fukamachi conn (format nil get-posts-sql posts board))) (table (make-hash-table))) (loop for (post_nr . flag) in result do (unless (gethash post_nr table) @@ -36,4 +55,4 @@ (jojo:to-json table))) (dbfun get-flags () - (clsql:query "select flags.id, flags.flag from flags" :database db)) + (fuck-you-fukamachi conn "select flags.id, flags.flag from flags")) diff --git a/src/main.lisp b/src/main.lisp index 5c179f6..1d86847 100644 --- a/src/main.lisp +++ b/src/main.lisp @@ -1,9 +1,11 @@ (defun init () (set-db-conn) (dotimes (_ (cconf 'poolsize)) - (clsql:connect conn :database-type :mysql :pool t :if-exists :new)) - (when (eq nil clsql:*default-database*) - (error "fucked up connecting to database")) + (dbi:connect-cached :mysql + :database-name (car conn-str) + :username (nth 1 conn-str) + :password (nth 2 conn-str))) + (ping) ;; test db conn (set-boards) (set-flags) (defvar +serb+ (make-instance 'hunchentoot:easy-acceptor @@ -42,7 +44,7 @@ (handle :post (api-get :uri "/staging/get") (post_nrs board version) - (@json *reply*) + (@json tbnl:*reply*) (setf post_nrs (str:split "," post_nrs)) (cond ((and (loop for x in post_nrs always (post-number-p x)) diff --git a/src/utils.lisp b/src/utils.lisp index 7b0d3ae..907607c 100644 --- a/src/utils.lisp +++ b/src/utils.lisp @@ -24,7 +24,7 @@ (format nil "~{~a~^~%~}" (mapcan (lambda (x) (cdr x)) flags)) "")))) (defun set-db-conn () - (setq conn (conf 'db-conn))) + (setq conn-str (conf 'db-conn))) (defun get-version (thing) (if (null thing) 0