From a31d24c28dd35fb4d8342554948d8abf75edb680 Mon Sep 17 00:00:00 2001 From: not manx Date: Mon, 25 May 2020 19:48:35 +0000 Subject: [PATCH] Moved hunchentoot utils to seperate package. Update readme --- .gitignore | 1 + README.org | 172 +++++++++------------ bantflags.user.js | 332 ++++++++++++++++++++-------------------- src/bantflags.asd | 4 +- src/config.example.lisp | 4 + src/db.lisp | 1 + src/main.lisp | 16 +- src/package.lisp | 3 + src/utils.lisp | 17 +- 9 files changed, 259 insertions(+), 291 deletions(-) create mode 100644 src/package.lisp diff --git a/.gitignore b/.gitignore index 20a11db..d0a1ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ src/config.lisp +\#*# \ No newline at end of file diff --git a/README.org b/README.org index aea6d31..068009a 100644 --- a/README.org +++ b/README.org @@ -1,117 +1,83 @@ * BantFlags -A user script and backend enabling user created flags on [[https://boards.4chan.org/bant][/bant/]], -originally based on [[https://github.com/flaghunters/Extra-Flags-for-4chan][extraflags]]. + A user script and backend enabling user created flags on [[https://boards.4chan.org/bant][/bant/]], + originally based on [[https://github.com/flaghunters/Extra-Flags-for-4chan][extraflags]]. -[[https://flags.plum.moe/bantflags.user.js][Install bantflags]] + [[https://flags.plum.moe/bantflags.user.js][Install bantflags]] ** Userscript -The userscript uses of ~GM_xmlhttpRequest~ to get and post flags with -the backend . A user's flags are stored between pages using -~GM_setValue~ and ~GM_getValue~. + The userscript uses of ~GM_xmlhttpRequest~ to get and post flags with + the backend . A user's flags are stored between pages using + ~GM_setValue~ and ~GM_getValue~. -Old versions of GreaseMonkey will be able to recieve updates to the -script through the ~@updateURL~ and ~@downloadURL~ directives, though -these were depricated sometime in GreaseMonkey 3.x and updates are -only checked from the location the script was downloaded from so be -careful where you upload links. + Old versions of GreaseMonkey will be able to recieve updates to the + script through the ~@updateURL~ and ~@downloadURL~ directives, though + these were depricated sometime in GreaseMonkey 3.x and updates are + only checked from the location the script was downloaded from so be + careful where you upload links. -On self hosting, changing ~back_end~ to your domain /should/ be all -you need to do, but don't take this as fact. I haven't tested the -example nginx config. - -The userscript has been designed specifically to target ECMAScript -2015 (ES6), making liberal use of arrow functions, and const/let -declarations. Update your hecking browser. + On self hosting, changing ~back_end~ to your domain /should/ be all + you need to do, but don't take this as fact. I haven't tested the + example nginx config. + + The userscript has been designed specifically to target ECMAScript + 2015 (ES6), making liberal use of arrow functions, and const/let + declarations. Update your hecking browser. ** Backend *** Prerequisites -- .NET Core 3.1 -- MariaDB / MySQL - -*** .NET dependancies -- Nito.AsyncEX -- Newtonsoft.Json -- MySql.Data -- Microsoft.AspNetCore.Mvc.NewtonsoftJson -- Microsoft.AspNetCore.StaticFiles -- Microsoft.AspNetCore.Razor -- Microsoft.EntityFrameworkCore.SqlServer -- Microsoft.EntityFrameworkCore.Tools -- Magick.NET-Q8-AnyCPU - + - I use SBCL +*** Dependancies + - hunchentoot + - [[https://github.com/C-xC-c/hunchenhelpers][hunchenhelpers]], my hunchentoot helper library + - clsql + - jonathan, the JSON encoder/decoder + - cl-ppcre *** Setup -1) [[https://dotnet.microsoft.com/download/dotnet-core][Install .NET Core]] -2) Clone and build the BantFlags solution. -3) Create the database using [[https://github.com/C-xC-c/BantFlags/blob/master/Environment/database.sql][database.sql]]. - - *Change the password*. -4) configure ~BantFlags/appsettings.example.json~ with your connection - string and webroot (where you'll serve the flags from *without a - trailing slash*) and rename it to ~appsettings.json~ - - [[./BantFlags/appsettings.example.json][example appsettings.json]] - - ASP.NET Core applications look for a folder called ~wwwroot~ in - the same directory as the application for static files. However - you can choose to logically seperate these by providing a vaild - directory to ~webroot~. - - That is to say, if the bantflags application is in - ~/var/www/bantflags/BantFlags.dll~, the program will look for - the folder ~/var/www/bantflags/wwwroot/~ to host static content, - or whatever directory is provided to ~wwwroot~. -5) If you're hosting on your GNU/Linux distribution of choice, Create a - folder called ~keys~ in the same directory as the bantflags - executable. - - E.G. ~/var/www/bantflags/keys/~ - - This is because ASP.NET Core uses some cryptic bullshit anti - forgery token when processing HTML forms, and it's unable to - persistantly store the decryption keys in memory on - GNU/Linux. This directory will store said keys when you or - users upload flags to /upload. The path uses - ~AppDomain.CurrentDomain.BaseDirectory~ internally, - I.E. wherever the program is. -6) Add flags to the backend by uploading them to the flag console (/Upload). - - Flags must be 16x11 pixels and under 15kb. Their names must not - exceed 100 characters and cannot contain either "||" or ",". -7) Configure your webserver of choice to forward requests to kestral - - [[https://github.com/C-xC-c/BantFlags/blob/master/Environment/nginx.conf][Example nginx config.]] -8) Run with ~dotnet BantFlags.dll~ or create a service to run it as a - daemon. - - [[https://github.com/C-xC-c/BantFlags/blob/master/Environment/bantflags.service][Example systemd service.]] -9) ??? -10) profit. - + 1. clone the project + 2. Symlink src/ to your ~/quicklisp/local-projects + 3. Move ~src/config.example.org~ to ~src/config.org~ and change it + to whatever your settings are. + 4. Type the following into your repl: + #+BEGIN_SRC lisp + (ql:quickload :bantflags) + (bantflags:main) + #+END_SRC + You will almost certainly have several issues building clsql, the + database connector used. I've [[https://plum.moe/words/bludgeoning-clsql-and-mariadb.html][written a blog post]] on some of the + issues I've encountered personally, but there's no guarantee it'll + work. Piece of shit. *** Database -Tables look like this: + Tables look like this: -*posts* -| id | post_nr | board | -| 1 | 12345 | bant | -| 2 | 56789 | bant | -*flags* -| id | flag | -| 1 | patchouli | -| 2 | chen | -*postflags* -| id | post_nr | flag | -| 1 | 1 | 1 | -| 2 | 1 | 2 | -| 2 | 2 | 2 | -where ~post_nr~ and ~flag~ in *postflags* are the id fields in their -respective tables. + *posts* + | id | post_nr | board | + | 1 | 12345 | bant | + | 2 | 56789 | bant | + *flags* + | id | flag | + | 1 | patchouli | + | 2 | chen | + *postflags* + | id | post_nr | flag | + | 1 | 1 | 1 | + | 2 | 1 | 2 | + | 2 | 2 | 2 | + where ~post_nr~ and ~flag~ in *postflags* are the id fields in their + respective tables. *** API -The backend exposes three endpoints for the userscript to get and post -flags. Flags themselves are hosted from the ~flags/~ directory. This -will be whatever value you gave to ~webroot~ (or -~/path/to/bantflags/wwwroot/~ if no value is provided) + ~flags/~. - -| route | purpse | -|------------+--------------------------------------------| -| /api/get | Get flags using post numbers in the thread | -| /api/post | Add flags to the database | -| /api/flags | List the flags we support | -| /flags/* | The flag images | + The backend exposes three endpoints for the userscript to get and + post flags. Flags themselves are hosted from ~flags/~ which is + ~www-root/flags/~ from ~config.lisp~ on the filesystem -** Backwards Compatibility -The API is 1:1 compatable with all previous versions of -bantflags. Further improvements are achieved by encoding a ~version~ -variable when poking endpoints which allows for breaking changes in -the script and backend while guaranteeing data can be parsed on both -ends. See [[https://github.com/C-xC-c/BantFlags/tree/master/Docs/][Docs/{endpoint}]] for changes and compatibility. + | route | purpse | + |------------+--------------------------------------------| + | /api/get | Get flags using post numbers in the thread | + | /api/post | Add flags to the database | + | /api/flags | List the flags we support | + | /flags/* | The flag images | +** Notes + You will get an error like =Recursive lock attempt #>.= if you + try and log to a file that doesn't exist / you don't have permissions + to read/write. diff --git a/bantflags.user.js b/bantflags.user.js index 4874d99..0fd5a06 100644 --- a/bantflags.user.js +++ b/bantflags.user.js @@ -60,15 +60,15 @@ let flagsLoaded = false; // const debug = text => { - if (debugMode) - console.log('[BantFlags] ' + 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', - nodegucaDoushio: document.querySelector('b[id="sync"], span[id="sync"]') !== null, - foolfuuka: document.querySelector('div[id="main"] article header .post_data') !== null + yotsuba: window.location.host === 'boards.4chan.org', + nodegucaDoushio: document.querySelector('b[id="sync"], span[id="sync"]') !== null, + foolfuuka: document.querySelector('div[id="main"] article header .post_data') !== null }; const makeElement = (tag, options) => Object.assign(document.createElement(tag), options); @@ -86,142 +86,142 @@ const addGlobalStyle = css => document.head.appendChild(makeElement('style', { i * @param {string} data - text for the form body. * @param {Function} func - The function run when we recieve a response. Response data is sent directly to it. */ const makeRequest = ((method, url, data, func) => { - xmlHttpRequest({ - method: method, - url: url, - data: data, - headers: { "Content-Type": 'application/x-www-form-urlencoded' }, - onload: func - }); + xmlHttpRequest({ + method: method, + url: url, + data: data, + headers: { "Content-Type": 'application/x-www-form-urlencoded' }, + onload: func + }); }); /** Itterate over selected flags are store them across browser sessions.*/ function saveFlags() { - regions = []; - const selectedFlags = document.querySelectorAll(".bantflags_flag"); + regions = []; + const selectedFlags = document.querySelectorAll(".bantflags_flag"); - for (var i = 0; i < selectedFlags.length; i++) { - regions[i] = selectedFlags[i].title; - } + for (var i = 0; i < selectedFlags.length; i++) { + regions[i] = selectedFlags[i].title; + } - setValue(namespace, regions); + setValue(namespace, regions); } /** Add a flag to our selection. * @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; - let flagContainer = document.getElementById('bantflags_container'); - - flagContainer.appendChild(makeElement('img', { - title: flagName, - src: flagSource(flagName), - id: UID, - className: 'bantflags_flag' - })); - - if (flagContainer.children.length >= max_flags) - toggleFlagButton('off'); - - document.getElementById(UID).addEventListener("click", e => { - flagContainer.removeChild(e.target); - toggleFlagButton('on'); - saveFlags(); - }); + let UID = Math.random().toString(36).substring(7); + let flagName = flag ? flag : document.querySelector('#flagSelect input').value; + let flagContainer = document.getElementById('bantflags_container'); + + flagContainer.appendChild(makeElement('img', { + title: flagName, + src: flagSource(flagName), + id: UID, + className: 'bantflags_flag' + })); + + if (flagContainer.children.length >= max_flags) + toggleFlagButton('off'); + + document.getElementById(UID).addEventListener("click", e => { + flagContainer.removeChild(e.target); + toggleFlagButton('on'); + saveFlags(); + }); - if (!flag) // We've added a new flag to our selection + if (!flag) // We've added a new flag to our selection saveFlags(); - + } function init() { - let flagsForm = makeElement('div', { - className: 'flagsForm', - innerHTML: '
    ' - }); + let flagsForm = makeElement('div', { + className: 'flagsForm', + innerHTML: '
      ' + }); - // Where do we append the flagsForm to? - if (software.yotsuba) { document.getElementById('delform').appendChild(flagsForm); } - 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? + // Where do we append the flagsForm to? + if (software.yotsuba) { document.getElementById('delform').appendChild(flagsForm); } + 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 (let i = 0; i < regions.length; i++) { - setFlag(regions[i]); - } + 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('flagLoad').addEventListener('click', makeFlagSelect, { once: true }); + document.getElementById('append_flag_button').addEventListener('click', () => flagsLoaded ? setFlag() : alert('Load flags before adding them.')); + document.getElementById('flagLoad').addEventListener('click', makeFlagSelect, { once: true }); } /** Get flag data from server and fill flags form. */ function makeFlagSelect() { - makeRequest( - "GET", - api_flags, - "", // We can't send data, it's a GET request. - function (resp) { - debug('Loading flags.'); + makeRequest( + "GET", + api_flags, + "", // We can't send data, it's a GET request. + function (resp) { + debug('Loading flags.'); - if (resp.status !== 200) { + if (resp.status !== 200) { 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'); - let flags = resp.responseText.split('\n'); - - for (var i = 0; i < flags.length; i++) { - let flag = flags[i]; - flagList.appendChild(makeElement('li',{ + let flagSelect = document.getElementById('flagSelect'); + let flagList = flagSelect.querySelector('ul'); + let flagInput = flagSelect.querySelector('input'); + let flags = resp.responseText.split('\n'); + + for (var i = 0; i < flags.length; i++) { + let flag = flags[i]; + flagList.appendChild(makeElement('li',{ innerHTML: `${flag}` })); - } + } - flagSelect.addEventListener('click', function (e) { + 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') { + if (node.nodeName === 'LI') flagInput.value = node.querySelector('span').innerHTML; - } - flagList.classList.toggle('hide'); - }); - - document.getElementById('flagLoad').style.display = 'none'; - document.querySelector('.flagsForm').style.marginRight = "200px"; // Element has position: absolute and is ~200px long. - flagSelect.style.display = 'inline-block'; - flagsLoaded = true; - }); + flagList.classList.toggle('hide'); + }); + + document.getElementById('flagLoad').style.display = 'none'; + document.querySelector('.flagsForm').style.marginRight = "200px"; // flagsForm has position: absolute and is ~200px long. + flagSelect.style.display = 'inline-block'; + flagsLoaded = true; + }); } /** add all of the post numbers on the page to postNrs. */ function getPosts(selector) { - const posts = document.querySelectorAll(selector); + const posts = document.querySelectorAll(selector); - for (let i = 0; i < posts.length; i++) { - const postNumber = software.yotsuba + for (let i = 0; i < posts.length; i++) { + const postNumber = software.yotsuba ? posts[i].id.substr(2) // Fuck you 4chan. : posts[i].id; postNrs.push(postNumber); } - debug(postNrs); + debug(postNrs); } /** Get flags from the database using values in postNrs and pass the response on to onFlagsLoad */ function resolveFlags() { - makeRequest( - 'POST', - 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; - } + makeRequest( + 'POST', + 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'); + debug(resp.responseText); + return; + } const jsonData = JSON.parse(resp.responseText); debug(`JSON: ${resp.responseText}`); @@ -256,93 +256,93 @@ function resolveFlags() { }); postNrs = []; - }); + }); } function main() { // See Docs/styles.css - 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;}') + 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;}') - if (software.yotsuba) { - getPosts('.postContainer'); + if (software.yotsuba) { + getPosts('.postContainer'); - addGlobalStyle('.flag{top: 0px;left: -1px}'); - init(); - } + addGlobalStyle('.flag{top: 0px;left: -1px}'); + init(); + } - if (software.nodegucaDoushio) { - getPosts('section[id], article[id]'); + if (software.nodegucaDoushio) { + getPosts('section[id], article[id]'); - addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}'); - init(); - } + addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}'); + init(); + } - if (software.foolfuuka) { - getPosts('article[id]'); + if (software.foolfuuka) { + getPosts('article[id]'); - addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}'); - } + addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}'); + } - board_id = window.location.pathname.split('/')[1]; + board_id = window.location.pathname.split('/')[1]; debug(board_id); resolveFlags(); } if (isGM4) { // Fuck you GM4 - (async () => { - regions = await getValue(namespace); - main(); - })(); + (async () => { + regions = await getValue(namespace); + main(); + })(); } else { - regions = getValue(namespace); - main(); + regions = getValue(namespace); + main(); } const postFlags = (post_nr, func = resp => debug(resp.responseText)) => makeRequest( - 'POST', - api_post, + '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; + 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)); + // 4chanX and native extension respectively + document.addEventListener('QRPostSuccessful', e => postFlags(e.detail.postID)); + document.addEventListener('4chanQRPostSuccess', e => postFlags(GetEvDetail(e).postId)); // I need to look at these. - document.addEventListener('ThreadUpdate', function (e) { - var evDetail = GetEvDetail(e); - var evDetailClone = typeof cloneInto === 'function' ? cloneInto(evDetail, unsafeWindow) : evDetail; - - //ignore if 404 event - if (evDetail[404] === true) { - return; - } - - evDetailClone.newPosts.forEach(function (post_board_nr) { - var post_nr = post_board_nr.split('.')[1]; - postNrs.push(post_nr); - }); - - resolveFlags(); - }, false); - - document.addEventListener('4chanThreadUpdated', function (e) { - var evDetail = GetEvDetail(e); - let threadID = window.location.pathname.split('/')[3]; - let postsContainer = Array.prototype.slice.call(document.getElementById('t' + threadID).childNodes); - let lastPosts = postsContainer.slice(Math.max(postsContainer.length - evDetail.count, 1)); //get the last n elements (where n is evDetail.count) - - lastPosts.forEach(function (post_container) { - var post_nr = post_container.id.replace('pc', ''); - postNrs.push(post_nr); - }); - - resolveFlags(); + document.addEventListener('ThreadUpdate', function (e) { + var evDetail = GetEvDetail(e); + var evDetailClone = typeof cloneInto === 'function' ? cloneInto(evDetail, unsafeWindow) : evDetail; + + //ignore if 404 event + if (evDetail[404] === true) { + return; + } + + evDetailClone.newPosts.forEach(function (post_board_nr) { + var post_nr = post_board_nr.split('.')[1]; + postNrs.push(post_nr); + }); + + resolveFlags(); + }, false); + + document.addEventListener('4chanThreadUpdated', function (e) { + var evDetail = GetEvDetail(e); + let threadID = window.location.pathname.split('/')[3]; + let postsContainer = Array.prototype.slice.call(document.getElementById('t' + threadID).childNodes); + let lastPosts = postsContainer.slice(Math.max(postsContainer.length - evDetail.count, 1)); //get the last n elements (where n is evDetail.count) + + lastPosts.forEach(function (post_container) { + var post_nr = post_container.id.replace('pc', ''); + postNrs.push(post_nr); + }); + + resolveFlags(); }, false); } @@ -355,37 +355,37 @@ if (software.nodegucaDoushio) { const badNodes = ['HR', 'SECTION']; - new MutationObserver(mutations => { - mutations.forEach(mutation => { + 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; + var firstAddedNode = mutation.addedNodes[0].nodeName; // Enter a thread / change boards - if (mutation.target.nodeName === 'THREADS') { + 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(); - } - - // We post - if (firstAddedNode === 'HEADER') { + board_id = window.location.pathname.split('/')[1]; + setTimeout(getPosts('section[id], article[id]'), 2000); + resolveFlags(); + init(); + } + + // We post + if (firstAddedNode === 'HEADER') { postFlags(mutation.target.id, postFunc) } // Someone else posts - if (firstAddedNode === 'ARTICLE') { + 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); - } + 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 d3011ce..c9ea289 100644 --- a/src/bantflags.asd +++ b/src/bantflags.asd @@ -11,11 +11,13 @@ :version "0.0.1" :serial t :depends-on (:hunchentoot + :hunchenhelpers :cl-ppcre :clsql :jonathan) :Components - ((:file "utils") + ((:file "package") + (:file "utils") (:file "db") (:file "config") (:file "main"))) diff --git a/src/config.example.lisp b/src/config.example.lisp index c86bcd1..ae409f3 100644 --- a/src/config.example.lisp +++ b/src/config.example.lisp @@ -1,3 +1,5 @@ +(in-package #:bantflags) + (defvar config '((boards "bant") (staging-password "not implemented") @@ -6,5 +8,7 @@ (www-root #p"/path/to/files/") (port 4242) ;; These can be a file or stream, make them nil to disable logging + ;; If the file can't be accessed, throws a weird error. See + ;; README.org (access-log *standard-output*) (error-log #p"/path/to/error/log/"))) diff --git a/src/db.lisp b/src/db.lisp index 9232fb2..9076625 100644 --- a/src/db.lisp +++ b/src/db.lisp @@ -2,6 +2,7 @@ ;; This file is part of bantflags. ;; bantflags is licensed under the GNU AGPL Version 3.0 or later. ;; see the LICENSE file or +(in-package #:bantflags) ;; Databases in common lisp are the fucking worst. ;; Don't even bother. diff --git a/src/main.lisp b/src/main.lisp index 1c451b1..6feaf0e 100644 --- a/src/main.lisp +++ b/src/main.lisp @@ -2,6 +2,7 @@ ;; This file is part of bantflags. ;; bantflags is licensed under the GNU AGPL Version 3.0 or later. ;; see the LICENSE file or +(in-package :bantflags) (defun init () (setf conn (conf 'db-conn)) @@ -13,20 +14,23 @@ :port (cconf 'port) :document-root (cconf 'www-root) :access-log-destination (cconf 'access-log) - :message-log-destination (cconf 'error-log))) - (hunchentoot:start +serb+)) + :message-log-destination (cconf 'error-log)))) (defun main () (handler-case (init) (error (c) (format t "Init fucked up, exiting ~a" c) - (return-from main))) + (return-from main))) + (handler-case (hunchentoot:start +serb+) + (error (c) + (format t "couldn't start serb: ~a" c) + (return-from main))) (loop (sleep 43200) (gc :full t))) (defmethod hunchentoot:acceptor-status-message (acceptor (http-status-code (eql 404)) &key) (format nil "")) ;; Empty 404 page -(handle :post (api-post :uri "/api/post") @json +(henh:handle :post (api-post :uri "/api/post") @json (post_nr regions board version) (multiple-value-bind (result msg) (insert-post-p post_nr (cl-ppcre:split "," regions) board) (cond @@ -35,12 +39,12 @@ (format nil "{\"~a\": [~{\"~a\"~^,~}]}~%" post_nr msg)) ;; This makes JSON (t (format nil "{\"Error\": \"~a\"}~%" msg))))) -(handle :post (api-get :uri "/api/get") @json +(henh:handle :post (api-get :uri "/api/get") @json (post_nrs board version) (if (get-posts-p (cl-ppcre:split "," post_nrs) board) (format nil "~a~%" (get-posts post_nrs board)) (t (format nil "{[\"~a\"]}~%" "bad")))) -(handle :get (api-flags :uri "/api/flags") @plain +(henh:handle :get (api-flags :uri "/api/flags") @plain () (format nil "~a~%" *flags-txt*)) diff --git a/src/package.lisp b/src/package.lisp new file mode 100644 index 0000000..f97002a --- /dev/null +++ b/src/package.lisp @@ -0,0 +1,3 @@ +(defpackage #:bantflags + (:use #:cl) + (:export :init)) diff --git a/src/utils.lisp b/src/utils.lisp index e20d9c2..d7f3155 100644 --- a/src/utils.lisp +++ b/src/utils.lisp @@ -2,6 +2,7 @@ ;; This file is part of bantflags. ;; bantflags is licensed under the GNU AGPL Version 3.0 or later. ;; see the LICENSE file or +(in-package #:bantflags) (defvar empty-flag '("empty, or there were errors. Re-set your flags.")) @@ -26,7 +27,7 @@ do (setf (gethash (car flag) *flags*) id)) ;; We don't want users to select `empty-flag` (setf *flags-txt* - (cl-ppcre:regex-replace (concatenate 'string empty-flag "\\n") ;; newline + (cl-ppcre:regex-replace (concatenate 'string (car empty-flag) "\\n") ;; newline (format nil "~{~a~^~%~}" (mapcan (lambda (x) (cdr x)) flags)) "")))) @@ -59,20 +60,6 @@ (every #'post-number-p post_nrs) (boardp board))) -;; hunchentoot -(defmacro handle (method uri content-type params &body body) - "Creates an easy handles for a specific HTTP request method. If the -method provided sent from the client isn't correct, return 404 and -stop processing the request. - -(handle :get (uri-fun :uri \"/path/to/page\"/) @content-type (args) (body))" - `(hunchentoot:define-easy-handler ,uri ,params - (unless (eq ,method (hunchentoot:request-method*)) - (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) - (hunchentoot:abort-request-handler)) - (setf (tbnl:content-type* tbnl:*reply*) ,content-type) - ,@body)) - ;; Content types (defvar @json "application/json") (defvar @plain "text/plain")