├── .gitattributes ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── README.md ├── booru-augmentation-project.user.js ├── image_search ├── controller.js └── index.html └── js ├── listPage.js ├── optionsPage.js └── postPage.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 4 * * 3' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['javascript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Booru Augmentation Project 2 | A userscript that improves usability of basic *.booru.org hosted sites. 3 | 4 | It's no secret that free accounts on booru.org offer very little convenience compared to well-developed sites like Danbooru. 5 | My goal is to do what is possible on client-side to compensate for the lack of functionality of the server side. 6 | 7 | ## List of features: 8 | 9 | * Drop-down select tag list for the search field 10 | 11 | ![screen](http://puu.sh/lD5FK/c90fd506d8.png) 12 | 13 | Like on Danbooru, but so far only works for the first tag entered. The data for autocompletion is being collected as you navigate the booru, so its accuracy depends on your booru usage. 14 | 15 | * AJAX tag editor 16 | 17 | ![pic](http://puu.sh/lwWff/d89ecf28d3.png) 18 | 19 | Allows you to quickly add, edit and remove tags with just a few clicks and without page reloading. No more scrolling to the bottom of the page to open the edit form, do everything directly in the tag list to the left. 20 | 21 | * Revamp Statistics area below the tag list 22 | 23 | ![screen](http://puu.sh/lyCVk/363400f0e5.png) 24 | 25 | Improve readability, add user links and image title, remove unnecessary info. 26 | 27 | * Shift page number links, so that links to both 5 previous and 5 next pages are available 28 | 29 | ![screen](http://puu.sh/lC2cz/0a406af9a0.png) 30 | 31 | By default booru only shows 10 next links, which makes jumping several pages back impossible. 32 | 33 | * The scripts adds its settings section to your account options. 34 | 35 | ![screen](http://puu.sh/qAgtv/15a2dac554.png) 36 | 37 | From here you can see the full list of tags the script collected so far, paired with their amount of posts (this data is used for search autocompletion). Another option is to enable the advanced booru tools: 38 | 39 | * Booru scanner 40 | 41 | ![screen](http://puu.sh/qAgNc/3b2b832a0a.png) 42 | 43 | This utility allows you to scan the entire booru or just the posts with selected tags. Scanning builds up the tag database for autocompletion and also allows you to download it in JSON format, as well as data for every post (id, rating, score, tags and image cluster) and a list of links to both image thumbnails and full-sizes. The latter can be fed to any mass-downloader to obtain a booru dump. Having both a collection of image thumbnails and a post database allows you to 44 | 45 | * Search posts by images 46 | 47 | ![screen](http://puu.sh/qAiKm/b5d6d120fb.jpg) 48 | 49 | You can find what post a saved thumbnail corresponds to by using this tool. It's useful when you're running duplicate checks on your booru and end up with a list of image doubles to delete. 50 | 51 | * Remove tagme tags 52 | 53 | ![screen](http://puu.sh/qAiR9/811148b73e.png) 54 | 55 | Made to overcome the needless "minimum of 5 tags" requirement that spams your uploads with the tagme tag, making it impossible to find posts that actually need to be tagged. Do note that this tool is only useful for admins, because it will attempt to edit any post it encounters and if you don't have right for it it'll fail and stop the progress. It is also advised to avoid editing other tags manually until the process finishes since booru limits the amount of consecutive edit queries and they're all used up by the script. 56 | 57 | Aside from these, I added some minor tweaks such as fitting the post image into screen width, fixing links to user accounts, linkifying users and links in comments as well as tags in the alias list, highlighting potential mistakes in tags input, fixing incorrect handling of _ in tags and some other. 58 | 59 | You feedback is required, please go to the [Issues](https://github.com/Seedmanc/Booru-Augmentation-Project/issues) section and make suggestions which features I should add, and what would you like to see. 60 | 61 | Be sure to check out my other Booru-related userscript project - the [Mass Uploader](https://github.com/Seedmanc/Booru-mass-uploader) 62 | -------------------------------------------------------------------------------- /booru-augmentation-project.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Booru Augmentation Project 3 | // @description Enhance your basic booru experience 4 | // @version 1.1.2 5 | // @author Seedmanc 6 | // @include https://*.booru.org/*index.php?page=post* 7 | // @include https://*.booru.org/*index.php?page=alias* 8 | // @include https://*.booru.org/*index.php?page=comment* 9 | // @include https://*.booru.org/*index.php?page=history* 10 | // @include https://*.booru.org/*index.php?page=forum&s=view* 11 | // @include https://*.booru.org/*index.php?page=account-options* 12 | // @include https://*.booru.org/*index.php?page=account&s=profile&uname=* 13 | // @grant none 14 | // @run-at document-body 15 | // @require https://ajax.googleapis.com/ajax/libs/prototype/1.7.3.0/prototype.js 16 | // @noframes 17 | // ==/UserScript== 18 | 19 | var pages = { 20 | 'account-options': optionsPage, 21 | 'account': { 22 | 'profile': profilePage 23 | }, 24 | 'alias': aliasPage, 25 | 'comment': commentPage, 26 | 'forum': { 27 | 'view': linkify 28 | }, 29 | 'history&type=tag_history': historyPage, 30 | 'post': { 31 | 'list': listPage, 32 | 'view': postPage 33 | } 34 | }; 35 | var hosting = 'https://seedmanc.github.io/Booru-Augmentation-Project/'; 36 | 37 | window.BAPtags = {}; 38 | window.BAPopts = JSON.parse(localStorage.getItem('BAPopts') || '{"ansiOnly":true, "solo":true, "tagme":true}'); 39 | window.currentBooru = document.location.host.split('.')[0]; 40 | window.taglist = {}; 41 | window.linklist = []; 42 | window.thumblist = []; 43 | window.postlist = {}; 44 | 45 | if (~document.location.href.indexOf('s=search_image')) { 46 | var frame = document.createElement('iframe'); 47 | 48 | document.title = 'BAP - Search by image'; 49 | 50 | frame.src = hosting + 'image_search/index.html?booru=' + window.currentBooru; 51 | frame.width = "100%"; 52 | frame.height = "96%"; 53 | document.body.appendChild(frame); 54 | 55 | } else if (!~document.location.href.indexOf('s=mass_upload')) { 56 | if (document.readyState == 'loading') { 57 | document.addEventListener('DOMContentLoaded', main, false); 58 | } else { 59 | main(); 60 | } 61 | } 62 | 63 | function loadAndExecute(url, callback){ 64 | var scriptNode = document.createElement ("script"); 65 | scriptNode.addEventListener("load", callback); 66 | scriptNode.onerror=function(){ 67 | throw new Error("Can't load "+url); 68 | }; 69 | scriptNode.src = url; 70 | document.head.appendChild(scriptNode); 71 | }; 72 | 73 | function optionsPage() { 74 | loadAndExecute(hosting + '/js/optionsPage.js'); 75 | } 76 | 77 | function listPage() { 78 | loadAndExecute(hosting + '/js/listPage.js'); 79 | } 80 | 81 | function postPage() { 82 | loadAndExecute(hosting + '/js/postPage.js'); 83 | } 84 | 85 | function parseUrl(prefix, handlers) { 86 | 87 | for (var key in handlers) { 88 | if (~document.location.href.indexOf(prefix + key)) { 89 | if (typeof handlers[key] == 'function') { 90 | handlers[key](); 91 | 92 | break; 93 | } else { 94 | parseUrl('s=', handlers[key]); // yay recursion 95 | 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | 102 | function main() { 103 | 104 | parseUrl('page=', pages); 105 | 106 | var ad; 107 | try { 108 | ad = document.querySelectorAll('center div[id*="adbox"]')[0]; 109 | if (ad) { 110 | ad.parentNode.parentNode.removeChild(ad.parentNode); 111 | } 112 | ad = document.querySelectorAll('#right-col div[id*="adbox"]')[0]; 113 | if (ad) { 114 | ad.parentNode.parentNode.removeChild(ad.parentNode); 115 | } 116 | ad = $$('center a[href*="patreon"]')[0]; 117 | if (ad) { 118 | ad.parentNode.parentNode.removeChild(ad.parentNode); 119 | } 120 | new Insertion.Bottom($$('head')[0], 121 | '' 126 | ); 127 | } catch (any) { 128 | } 129 | } 130 | 131 | function profilePage() { 132 | document.location.href = document.location.href.replace('account&s=profile', 'account_profile'); 133 | } 134 | 135 | window.loadOptions = function(that) { 136 | searchField(); 137 | that.onfocus = ''; 138 | } 139 | 140 | window.storeTags = function () { 141 | var tags = $$('#tag_list ul li span'); 142 | var newtags; 143 | 144 | window.BAPtags = Object.keys(window.BAPtags).length && window.BAPtags || JSON.parse(localStorage.getItem('BAPtags') || '{}'); 145 | 146 | tags.each(function (span) { 147 | var tag = span.down('a').href; 148 | 149 | tag = tag && tag.split('tags=')[1]; 150 | if (tag) { 151 | tag = decodeURIComponent(tag).replace(/\"|\'/g, ''); 152 | window.BAPtags[tag] = Number(span.textContent.split(/\s+/).last()); 153 | } 154 | }); 155 | 156 | newtags = JSON.stringify(window.BAPtags); 157 | if (Object.keys(newtags).length) { 158 | localStorage.setItem('BAPtags', newtags); 159 | } 160 | 161 | $$('input[name^="tag"]')[0].onfocus = function () { 162 | loadOptions(this); 163 | }; 164 | } 165 | 166 | function searchField() { 167 | var datalist = $('datags'); 168 | var goFullRetard = ~navigator.userAgent.toLowerCase().indexOf('firefox'); 169 | 170 | if (!datalist) { 171 | new Insertion.Top(document.body, ''); 172 | } 173 | datalist = $('datags'); 174 | 175 | Object.keys(window.BAPtags).each(function (tag) { 176 | if (!datalist.down('option[value="' + tag + '"]')) { 177 | var content = goFullRetard ? tag + ' (' + window.BAPtags[tag] + ')' : window.BAPtags[tag]; 178 | 179 | new Insertion.Bottom(datalist, ''); 180 | } 181 | }); 182 | 183 | $$('input#tags, input#stags, input[name="tag"]')[0].oninput = function () { 184 | enableDatalist(this); 185 | }; 186 | } 187 | 188 | window.enableDatalist = function(that) { 189 | if (that.value.length >= 1) { 190 | that.setAttribute('list', 'datags'); 191 | } else { 192 | that.removeAttribute('list'); 193 | } 194 | } 195 | 196 | window.markTags = function (li) { 197 | var q = li.textContent.trim().split(/\s+/); 198 | var q1 = q[q.length - 1];; 199 | 200 | if (~q.indexOf('tagme') && window.BAPopts.tagme) { 201 | li.style.backgroundColor = 'rgba(255,0,0,0.25)'; 202 | } 203 | if (isNaN(q1) || q1 >= 5) { 204 | li.down('span').style.color = "#A0A0A0"; 205 | 206 | return; 207 | } 208 | if (q1 <= 1) { 209 | li.style.backgroundColor = "rgba(255,225,0,0.66)"; 210 | } else { 211 | li.style.backgroundColor = "rgba(255,255,0,0.33)"; 212 | } 213 | 214 | li.down('span').style.color = "#000"; 215 | } 216 | 217 | function aliasPage() { // idea by Usernam, how it's actually done by Seedmanc 218 | var example = $$('body > div:nth-child(4)'); 219 | 220 | if (example.length && ~example[0].textContent.indexOf('example')) { 221 | example[0].style.color = "black"; 222 | example[0].innerHTML = example[0].innerHTML.replace(/(An example)(.+)(Evangelion)(.+)(Neon_Genesis_Evangelion)(.+)\)/gi, "$1: $3$4$5$6"); 223 | } 224 | 225 | storeTags(); 226 | 227 | $$("th")[2].hide(); 228 | 229 | $$('.highlightable td').each(function (td, index) { 230 | var tag = td.textContent; 231 | 232 | if ((index + 1) % 3 == 0) { 233 | td.hide(); 234 | } else { 235 | td.innerHTML = "" + tag + "" + 236 | (tag && window.BAPtags[tag.toLowerCase()] ? ' (' + window.BAPtags[tag.toLowerCase()] + ')' : ""); 237 | } 238 | }); 239 | } 240 | 241 | function commentPage() { 242 | 243 | var commentsData = $$('ul.post-info'); 244 | 245 | $A(commentsData).forEach(function (el) { 246 | var user = el.childNodes[2].textContent.split(':')[1].trim(); 247 | 248 | var userlink = user == 'Anonymous' ? 249 | 'Anonymous' : 250 | '' + user + ''; 251 | el.removeChild(el.childNodes[2]); 252 | new Insertion.Before(el.childNodes[2], '
  • user:'+userlink+'
  • '); 253 | 254 | }); 255 | 256 | linkifyContainer('div.body,div[id^="cbody"] > p'); 257 | } 258 | 259 | function linkify() { 260 | linkifyContainer('div.body,div[id^="cbody"] > p'); 261 | } 262 | 263 | window.linkifyContainer = function(selector) { 264 | var containers = $$(selector); 265 | 266 | if (!containers || !containers.length) { 267 | return; 268 | } 269 | 270 | containers.forEach(function (container) { 271 | $A(container.childNodes).each(function (node) { 272 | if (node.nodeType != 3) { 273 | return true; 274 | } 275 | linkifyTextNode(node); 276 | }); 277 | }); 278 | } 279 | 280 | // from https://arantius.com/misc/greasemonkey/linkify-plus.user.js 281 | function linkifyTextNode(node) { 282 | var urlRE = new RegExp( 283 | '(' 284 | // leading scheme:// or "www." 285 | + '\\b([a-z][-a-z0-9+.]+://|www\\.)' 286 | // everything until non-URL character 287 | + '[^\\s\'"<>()]+' 288 | + ')', 'gi'); 289 | 290 | var l, m; 291 | var txt = node.textContent; 292 | var span = null; 293 | var p = 0; 294 | while (m = urlRE.exec(txt)) { 295 | if (null == span) { 296 | // Create a span to hold the new text with links in it. 297 | span = document.createElement('span'); 298 | } 299 | 300 | //get the link without trailing dots 301 | l = m[0].replace(/\.*$/, ''); 302 | var lLen = l.length; 303 | //put in text up to the link 304 | span.appendChild(document.createTextNode(txt.substring(p, m.index))); 305 | //create a link and put it in the span 306 | a = document.createElement('a'); 307 | a.appendChild(document.createTextNode(l)); 308 | if (!~l.indexOf(":/")) { 309 | l = "https://" + l; 310 | } 311 | a.setAttribute('href', l); 312 | if (!~l.indexOf(document.location.host)) { 313 | a.setAttribute('target', '_blank'); 314 | } 315 | span.appendChild(a); 316 | //track insertion point 317 | p = m.index + lLen; 318 | } 319 | if (span) { 320 | //take the text after the last link 321 | span.appendChild(document.createTextNode(txt.substring(p, txt.length))); 322 | //replace the original text with the new span 323 | try { 324 | node.parentNode.replaceChild(span, node); 325 | } catch (e) { 326 | console.error(e); 327 | console.log(node); 328 | } 329 | } 330 | } 331 | 332 | // idea by Usernam, madness removal by Seedmanc 333 | function historyPage() { 334 | var regexp = /([\S]+)/g; 335 | 336 | var table = $('history'); 337 | table.style = "width: 100%; table-layout: auto;"; 338 | 339 | $$('#history th').each(function (th) { 340 | th.removeAttribute("width"); 341 | }); 342 | 343 | // Delta 344 | table.down('th:nth-of-type(1)').textContent = '∆'; 345 | 346 | // Tags 347 | table.down('th:nth-of-type(5)').style.width = "100%"; 348 | 349 | // Options 350 | table.down('th:nth-of-type(6)').textContent = 'Undo'; 351 | 352 | var rows = $$('#history tr:not(:first-of-type)'); 353 | 354 | rows.each(function (tr, i) { 355 | var tdTags = tr.down('td:nth-of-type(5)'); 356 | 357 | tr.down('td:nth-of-type(3)').style = "white-space: nowrap;"; 358 | 359 | var tags1 = tdTags.textContent.trim().split(' '); 360 | var tags2 = rows[i + 1] ? rows[i + 1].down('td:nth-of-type(5)').textContent.trim().split(' ') : tags1; 361 | 362 | var addTags = tags1.filter(function (el) { 363 | return !~tags2.indexOf(el); 364 | }); 365 | var delTags = tags2.filter(function (el) { 366 | return !~tags1.indexOf(el); 367 | }); 368 | var sameTags = tags1.filter(function(n) { 369 | return tags2.indexOf(n) != -1; 370 | }); 371 | 372 | 373 | var usernam = tr.down('td:nth-of-type(4)'); 374 | if (usernam.textContent == "Anonymous") { 375 | usernam.innerHTML = 'Anonymous'; 376 | } else { 377 | usernam.innerHTML = usernam.innerHTML.replace(regexp, '$1'); 378 | } 379 | 380 | if (tags2.length > tags1.length) { 381 | tr.down('td:nth-of-type(1)').style.backgroundColor = 'red'; 382 | } else if (tags2.length < tags1.length) { 383 | tr.down('td:nth-of-type(1)').style.backgroundColor = 'green'; 384 | } 385 | 386 | // this needs testing for tags with weird characters inside, although you shouldn't be having those anyway 387 | var html = addTags.join(' ').replace(regexp, '+$1 ') + 388 | delTags.join(' ').replace(regexp, '$1 ') + 389 | sameTags.join(' ').replace(regexp, '$1'); 390 | 391 | tdTags.innerHTML = html; 392 | }); 393 | } 394 | 395 | // todo: tag categories? 396 | // todo: crop and autocontrast by tags 397 | // todo: store post data internally and reuse it in image search 398 | -------------------------------------------------------------------------------- /image_search/controller.js: -------------------------------------------------------------------------------- 1 | angular.module('myApp', []) 2 | .controller('myCtrl', function ($scope) { 3 | $scope.booruName = document.location.href.split('?booru=')[1]; 4 | $scope.orderByField = 'size'; 5 | $scope.reverseSort = false; 6 | $scope.imagesEven = []; 7 | $scope.imagesOdd = []; 8 | $scope.fail = []; 9 | $scope.db = {}; 10 | var images = []; 11 | var filenames = {}; 12 | 13 | $('#db') .on('change', loadb).val(''); 14 | $('#files').on('change', loadFiles).val('').attr('disabled', ''); 15 | $('table') .on('mouseover',highlighth); 16 | $('#mode') .on('change', function () { 17 | if (this.value == 'folder') { 18 | $('#files').attr({'directory': true, 'mozdirectory': true, 'webkitdirectory': true}); 19 | } else { 20 | $('#files').removeAttr('directory mozdirectory webkitdirectory'); 21 | } 22 | }); 23 | 24 | function loadb(evt) { 25 | var file = evt.target.files[0]; 26 | var reader = new FileReader(); 27 | 28 | if (file.name.split('.').reverse()[0] != 'json') { 29 | alert('Wrong filetype: must be json'); 30 | 31 | return false; 32 | } 33 | 34 | reader.onloadend = function (e) { 35 | 36 | try { 37 | o = JSON.parse(e.target.result); 38 | } catch (err) { 39 | alert('Error loading DB: ' + err.message); 40 | 41 | return false; 42 | } 43 | 44 | for (var attrname in o) { 45 | $scope.db[attrname] = o[attrname]; 46 | } 47 | 48 | if (Object.keys(o).length) { 49 | $('#files').removeAttr('disabled'); 50 | } else { 51 | alert('Database is empty'); 52 | } 53 | 54 | }; 55 | reader.readAsText(file); 56 | } 57 | 58 | function highlighth(evt) { 59 | var i, col, th, secondTable; 60 | 61 | if ($(evt.target).is('td')) { 62 | i = $(evt.target).index(); 63 | col = $(evt.target); 64 | th = col.closest('table').find('th'); 65 | secondTable = $('#images-even,#images-odd').not(col.closest('table')); 66 | 67 | $('th').removeClass('hover'); 68 | th.eq(i).addClass('hover'); 69 | secondTable.find('th').eq(7 - i).addClass('hover'); 70 | } 71 | } 72 | 73 | function loadFiles(evt) { 74 | var files = evt.target.files; 75 | var image = {}, missing = $('#missing').prop('checked'), hash, reader, failed; 76 | 77 | $.each(files, function (idx, file) { 78 | file.name = file.name.toLowerCase(); 79 | if (filenames[file.name]) { 80 | return true; 81 | } 82 | filenames[file.name] = true; 83 | image = {}; 84 | hash = /([a-z]|\d){40}/.exec(file.name); 85 | 86 | if (hash && hash[0] && $scope.db[hash[0]]) { 87 | if (!missing) { 88 | hash = hash[0]; 89 | image.hash = [hash.slice(0, 20), hash.slice(20, 40)]; 90 | image.tags = $scope.db[hash].t; 91 | image.tagnum = $scope.db[hash].t.length; 92 | image.score = $scope.db[hash].s; 93 | image.ext = $scope.db[hash].e; 94 | image.cluster = $scope.db[hash].c; 95 | image.post = $scope.db[hash].i; 96 | image.size = file.size / (image.ext == 'png' ? 3 : 1); 97 | image.pic = "https://thumbs.booru.org/" + $scope.booruName + "/thumbnails/" + image.cluster + "/thumbnail_" + image.hash.join('') + "." + image.ext; 98 | 99 | images.push(image); 100 | } 101 | } else { 102 | (function (file) { 103 | if (!~file.type.indexOf('image/')) { 104 | return; 105 | } 106 | reader = new FileReader(); 107 | reader.onloadend = function (e) { 108 | failed = {}; 109 | 110 | failed.src = e.target.result; 111 | failed.name = file.name.length > 7 ? [file.name.slice(0, file.name.length >> 1), file.name.slice(file.name.length >> 1, file.name.length)] : file.name; 112 | $scope.fail.push(failed); 113 | $scope.$apply(); 114 | }; 115 | reader.readAsDataURL(file); 116 | })(file); 117 | } 118 | }); 119 | $('#files')[0].value = null; 120 | 121 | if (missing) 122 | return; 123 | 124 | $scope.imagesOdd = []; 125 | $scope.imagesEven = []; 126 | $scope.fail.sort(function (a, b) { 127 | return String.prototype.localeCompare(a.name, b.name); 128 | }); 129 | 130 | images.sort(function (a, b) { 131 | return Math.abs(a.size - b.size) / (a.size - b.size) || 0; 132 | }); 133 | 134 | images.forEach(function (image, i) { 135 | if (i % 2) 136 | $scope.imagesOdd.push(image) 137 | else 138 | $scope.imagesEven.push(image); 139 | }); 140 | 141 | $scope.$apply(); 142 | } 143 | 144 | $scope.sm = function () { 145 | var a, blob, bat = [], folder = '!missing', reader; 146 | 147 | bat.push('md "' + folder + '"'); 148 | $scope.fail.forEach(function (image) { 149 | bat.push('move "' + image.name.join('') + '" "' + folder + '\\' + image.name.join('') + '"'); 150 | }); 151 | bat.push('pause'); 152 | 153 | blob = new Blob([bat.join('\r\n')], {type: 'application/octet-stream'}); 154 | a = window.document.createElement('a'); 155 | a.download = 'move missing images to \'' + folder + '\' directory.bat'; 156 | document.body.appendChild(a); 157 | 158 | if (window.URL && window.URL.createObjectURL) { 159 | a.href = window.URL.createObjectURL(blob); 160 | 161 | a.click(); 162 | document.body.removeChild(a); 163 | } else { 164 | reader = new window.FileReader(); 165 | 166 | reader.readAsDataURL(blob); 167 | reader.onloadend = function () { 168 | a.href = reader.result; 169 | a.click(); 170 | document.body.removeChild(a); 171 | }; 172 | } 173 | 174 | }; 175 | }); -------------------------------------------------------------------------------- /image_search/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | BAP - search by image 7 | 8 | 9 | 10 | 11 | 12 | 87 | 88 | 89 | 90 |
    91 |
    92 |
    93 |
    94 | 95 | 96 |
    97 |
    98 | 102 | 103 |
    104 |
    105 | 106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    The following images has failed to identify (hash missing from filename or database): 112 |
    113 |
    114 |
    115 |
    116 |
    117 | 118 | {{name}}
    119 |
    120 |
    121 |
    122 |  \t Download .bat to separate these images 123 |
    124 |
    125 |
    126 |
    127 | 130 |
    131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 157 | 158 | 161 | 166 | 167 | 168 | 169 | 172 | 176 | 177 | 178 |
    #hashlist of tags▴tag#▾▴score▾▴type▾▴post▾image
    {{($index+1)*2-1}} 159 |
    {{hash}}
    160 |
    162 |
    163 | {{tag}} 164 |
    165 |
    {{image.tags.length}}{{image.score}}{{image.ext}}{{image.post}} 171 | 174 | 175 |
    179 |
    180 |
    181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 207 | 211 | 214 | 215 | 216 | 217 | 223 | 226 | 227 | 228 | 229 |
    image▴post▾▴type▾▴score▾▴tag#▾list of tagshash#
    209 | 210 | {{image.post}} 213 | {{image.ext}}{{image.score}}{{image.tags.length}} 218 |
    219 | {{tag}} 221 |
    222 |
    224 |
    {{hash}}
    225 |
    {{($index+1)*2}}
    230 |
    231 |
    232 |
    233 | 234 | 235 | -------------------------------------------------------------------------------- /js/listPage.js: -------------------------------------------------------------------------------- 1 | listPageMain() 2 | 3 | function listPageMain() { 4 | var tags = $$('#tag_list ul li'); 5 | var posts = $$('span.thumb'); 6 | 7 | storeTags(); 8 | 9 | if (!posts.length && ~document.location.href.indexOf('tags=')) { 10 | var t = (document.location.href.split('tags=')[1] || '').split('+'); 11 | 12 | if (t.length == 1 && t[0]) { 13 | delete window.BAPtags[t[0]]; 14 | 15 | if (Object.keys(window.BAPtags).length) { 16 | localStorage.setItem('BAPtags', JSON.stringify(window.BAPtags)); 17 | } 18 | } 19 | } 20 | 21 | if (tags && tags.length) { 22 | tags.each(function (li) { 23 | if (!li.textContent.trim()) { 24 | return; 25 | } 26 | var s = li.down('span'); 27 | var a = li.down('a'); 28 | var query = s.down('a').href.split('&tags=')[1]; 29 | 30 | if (query) { 31 | s.down('a').update(decodeURIComponent(query)); 32 | } 33 | 34 | if (a && a.textContent == '+') { 35 | new Insertion.After(a, ' '); 36 | a = a.next('a'); 37 | if (a && a.textContent == '-') { 38 | a.textContent = a.textContent.replace('-', '–'); 39 | } 40 | } 41 | 42 | if (s.childNodes[0].textContent == '? ') { 43 | s.childNodes[0].textContent = ' '; 44 | new Insertion.Before(s.childNodes[0], '?'); 45 | } 46 | markTags(li); 47 | }); 48 | pagination(); 49 | } else { 50 | return; 51 | } 52 | } 53 | 54 | function pagination () { 55 | var paginator = $('paginator'); 56 | var current = paginator && paginator.down('b'); 57 | var contents = paginator && paginator.immediateDescendants().without(paginator.down('script')); 58 | var pos = contents && contents.indexOf(current); 59 | var shift = Math.min(current.textContent - 2, 4); 60 | var newPos = paginator.down('a:not([alt])', shift); 61 | var next = current.next(); 62 | var pageLinks; 63 | var pid; 64 | 65 | if (!paginator.down('a[alt="first page"]')|| 66 | !paginator.down('a[alt="next"]') || 67 | contents.length < 15 || 68 | pos >= 7 || 69 | (contents.last().href == contents.last().previous('a:not([alt])').href) && (contents.first().href == contents.first().next('a:not([alt])').href) 70 | ) { 71 | return; 72 | } 73 | 74 | pid = ~document.location.search.indexOf('pid=') ? 75 | document.location.search.split('&').findAll(function (el) { 76 | return ~el.indexOf('pid'); 77 | })[0].replace('pid=', '') : 78 | 0; 79 | 80 | 81 | if (next == newPos) { 82 | next = current; 83 | } else { 84 | paginator.insertBefore(current, newPos); 85 | } 86 | paginator.insertBefore(newPos, next); 87 | 88 | pageLinks = document.querySelectorAll('div#paginator > a:not([alt]), div#paginator > b'); 89 | 90 | for (var i = 0; i < pageLinks.length; i++) { 91 | pageLinks[i].textContent = pid / 20 - shift + i; 92 | if (!pageLinks[i].href) { 93 | continue; 94 | } 95 | pageLinks[i].href = pageLinks[i].href.replace(/&pid=\d+/gi, '&pid=' + ((pageLinks[i].textContent - 1) * 20)); 96 | } 97 | } -------------------------------------------------------------------------------- /js/optionsPage.js: -------------------------------------------------------------------------------- 1 | optionsPageMain(); 2 | 3 | var Current = 0, start = 0, timeouts=[], failure = false; 4 | 5 | function optionsPageMain() { 6 | delete Array.prototype.toJSON; 7 | var table = $$('div.option table tbody')[0]; 8 | var submit = $$('div.option input[type="submit"]')[0]; 9 | BAPtags = BAPtags && Object.keys(BAPtags).length || JSON.parse(localStorage.getItem('BAPtags') || '{}'); 10 | 11 | new Insertion.Bottom(table, '
    Booru Augmentation Project'); 12 | new Insertion.Bottom(table, '

    Do not accept non-ANSI tags when editing tags in-place


    '); 13 | new Insertion.Bottom(table, '

    Mark green/add a solo tag if there are less than 2 existing tags


    \ 14 |

    Mark red an existing tagme tag for easier removal


    '); 15 | 16 | new Insertion.Bottom(table, '

    Display here a list of all tags sorted by their amount of posts


    \ 17 |

    Allows to fetch complete post and tag data from the booru, as well as a list of image links.


    '); 18 | 19 | 20 | Object.keys(BAPopts).each(function (opt) { 21 | if ($$('input.BAPoption#' + opt)[0]) { 22 | $$('input.BAPoption#' + opt)[0].checked = BAPopts[opt]; 23 | } 24 | }); 25 | 26 | $('showTags').onchange = showTags 27 | $('showScanner').onchange = showScanner; 28 | 29 | submit.onclick = function () { 30 | $$('input.BAPoption').each(function (el) { 31 | BAPopts[el.id] = el.checked; 32 | }); 33 | localStorage.BAPopts = JSON.stringify(BAPopts); 34 | }; 35 | showScanner(); 36 | } 37 | 38 | function removeTagme(offset) { 39 | var rttProgress = $('rttProgress'); 40 | 41 | $('rttError').textContent = ''; 42 | 43 | getPage('https://' + currentBooru + '.booru.org/index.php?page=post&s=list&tags=tagme&pid=' + offset, function (html) { 44 | var total = html.querySelector('a[alt="last page"]'), completed = 0; 45 | var next = html.querySelector('a[alt="next"]'); 46 | var ilinks = html.querySelectorAll('.content .thumb > a'); 47 | var delay = 3000; 48 | 49 | rttProgress.value = offset || 1; 50 | 51 | next = (next && next.getAttribute('href').split("pid=").last()) || 0; 52 | total = (total && total.getAttribute('href').split("pid=").last()) || 20; 53 | 54 | if (offset === 0) { 55 | $('rttProgress').max = total; 56 | } 57 | 58 | $A(ilinks).forEach(function (v, i) { 59 | if (failure) { 60 | processError(v); 61 | return false; 62 | } 63 | 64 | var t = setTimeout(function(){ 65 | 66 | getPage('https://' + currentBooru + '.booru.org/index.php?page=post&s=view&id=' + v.id.replace('p',''), function (html2) { 67 | if (failure) { 68 | processError(v); 69 | return false; 70 | } 71 | var form = html2.querySelector('#edit_form'); 72 | 73 | form.tags.value = form.tags.value.replace(/^tagme | tagme$| tagme /i, ' '); 74 | form.pconf.value = 1; 75 | 76 | timeouts.push(setTimeout(function(){ 77 | if (failure) { 78 | processError(v); 79 | return false; 80 | } 81 | form.request({ 82 | onComplete: function (xhr) { 83 | if (xhr.responseText && ~xhr.responseText.indexOf('ou are not logged in')) { 84 | failure = true; 85 | processError('you are not logged in'); 86 | } 87 | if (failure) return false; 88 | 89 | completed++; 90 | rttProgress.value++; 91 | 92 | if (completed >= ilinks.length && next) { 93 | timeouts.push(setTimeout(function(){removeTagme(next);}, delay/2)); 94 | } else if (completed >= ilinks.length) { 95 | rttProgress.max = rttProgress.value; 96 | } 97 | }, 98 | onFailure: function () { 99 | failure = true; 100 | processError(v); 101 | } 102 | }); 103 | }, delay/3)); 104 | }); 105 | }, delay*(i+1)); 106 | timeouts.push(t); 107 | }); 108 | }); 109 | 110 | function processError(err) { 111 | $('removeTagme').removeAttribute('disabled'); 112 | $('rttError').innerHTML = typeof err == 'object' ? 'error removing tagme at post ' + err.outerHTML : err; 113 | timeouts.forEach(function(to) { 114 | window.clearTimeout(to); 115 | }); 116 | } 117 | } 118 | //TODO sequential execution in case of booru limitations 119 | 120 | function getPage(url, callback) { 121 | if (!url) { 122 | return; 123 | } 124 | 125 | new Ajax.Request(url, { 126 | method: 'get', 127 | onSuccess: function (xhr) { 128 | var tmplt = document.createElement('template'), html; 129 | 130 | tmplt.innerHTML = xhr.responseText; 131 | html = (tmplt.content || tmplt); 132 | 133 | if (typeof callback == 'function') { 134 | callback(html); 135 | } 136 | } 137 | }); 138 | } 139 | 140 | function showScanner() { 141 | var table; 142 | 143 | Current = 0; 144 | start = start || 0; 145 | 146 | if ($('allTags')) { 147 | $('allTags').up('.option').hide(); 148 | } 149 | 150 | if ($('scanner')) { 151 | $('scanner').up('.option').show(); 152 | 153 | return; 154 | } 155 | 156 | new Insertion.After($$('form > p')[0], 157 | '
    Booru scanner
    '); 158 | table = $$('#scanner tbody')[0]; 159 | 160 | new Insertion.Bottom(table, 161 | '
     '); 162 | new Insertion.Bottom(table, 163 | '
     '); 164 | new Insertion.Bottom(table, 165 | '

    ' + Current + ' posts remaining

    \ 166 | '); 167 | new Insertion.Bottom(table, 168 | '

    Incomplete until scanning finishes

    \ 169 | \ 170 | \ 171 | \ 172 | \ 173 | \ 174 | '); 175 | 176 | new Insertion.After($('scanner'), 177 | '
    Search by image (hash)

    Having the complete post DB allows you to do the advanced post searching.

    \ 178 |

    For example, here is how you can search for duplicating images at your booru:

    1. Get the complete post DB by hash and the list of links to thumbnails.
    2. \ 179 |
    3. DL all thumbs by feeding the list to some mass downloader
    4. \ 180 |
    5. Run a duplicate finder software on the thumbs, moving all dupes to a folder
    6. \ 181 |
    7. Open the hash db and the found images in the Image Search to get links to posts on booru that have duplicating images

    \ 182 | Note: in Chrome the amount of pictures opened simulaneously might be quite limited by their collective filename length. You can open a folder of images instead.\ 183 |
    '); 184 | 185 | new Insertion.After($('sbi'), 186 | '
    Remove tagme

    Booru automatically adds the "tagme" tag if your uploads have less than 5 tags. You can\'t turn that off, however, you can batch-delete that tag from the posts that have it.

    '); 187 | new Insertion.Bottom($('rtt'), 188 | '
    '); 189 | 190 | $('removeTagme').onclick = function () { 191 | $('removeTagme').disable(); 192 | failure = false; 193 | removeTagme(0); 194 | }; 195 | 196 | $('scanTags').onfocus = function () { 197 | loadOptions(this); 198 | }; 199 | 200 | $('dllinks').onclick = function (evt) { 201 | if (evt.target.className == "dllink") { 202 | var tl = evt.target.id == 'taglist' ? 203 | Object.keys(window.taglist).length == 0 ? 204 | BAPtags : 205 | false : 206 | false; 207 | var b = new Blob([JSON.stringify(tl || window[evt.target.id], null, '\t')], {type: typeof URL != 'undefined' ? 'text/plain' : 'application/octet-stream'}); 208 | var a = document.createElement('a'); 209 | var scanQuery = $('scanTags').value.trim(); 210 | var isNum = scanQuery && /^\d+$/.test(scanQuery); 211 | 212 | a.download = evt.target.id.replace('list', ' list for ' + (scanQuery ? 213 | (isNum ? 214 | (scanQuery + ' posts @ ') : 215 | ('\'' + scanQuery + '\' @ ') 216 | ) : 217 | '') 218 | + currentBooru + 'booru' + isNum ? 219 | '' : 220 | (', ' + (start - Current) + ' of ' + start + ' posts scanned') 221 | ) + '.json'; 222 | document.body.appendChild(a); 223 | 224 | if (typeof URL != 'undefined') { 225 | var fileURL = URL.createObjectURL(b); 226 | 227 | a.href = fileURL; 228 | a.click(); 229 | a.parentNode.removeChild(a); 230 | } else { 231 | var reader = new window.FileReader(); 232 | 233 | reader.readAsDataURL(b); 234 | reader.onloadend = function () { 235 | a.href = reader.result; 236 | a.click(); 237 | a.parentNode.removeChild(a); 238 | }; 239 | } 240 | } 241 | }; 242 | 243 | $('start').onclick = function () { 244 | $('start').disable(); 245 | $('scanTags').disable(); 246 | $('time').show(); 247 | 248 | window.linklist = []; 249 | window.thumblist = []; 250 | 251 | scanPage(start); 252 | }; 253 | 254 | 255 | $('scanTags').onchange = function (evt) { 256 | var query = (evt && evt.target.value.trim()), lastpic; 257 | var isNum = query && /^\d+$/.test(query); 258 | var limit; 259 | 260 | if (isNum) { 261 | limit = parseInt(query, 10); 262 | query = 'all'; 263 | start = (limit-1)*20; 264 | $('scanProgress').max = start; 265 | $('current').update(start+20 + ' posts remaining'); 266 | $('current').up('p').style.color = ""; 267 | $('start').enable(); 268 | 269 | if (limit === 0) { 270 | $('start').disable(); 271 | } 272 | } else { 273 | query = query || 'all'; 274 | getPage('https://' + currentBooru + '.booru.org/index.php?page=post&s=list' + (query ? '&tags=' + query : ''), function (html) { 275 | start = html.querySelector('a[alt="last page"]'); 276 | 277 | if (html.querySelectorAll('span.thumb').length) { 278 | $('current').up('p').style.color = ""; 279 | $('start').enable(); 280 | 281 | start = parseInt((start && start.getAttribute('href').split("pid=").last()) || '1', 10); 282 | $('scanProgress').max = start; 283 | $('current').update(start + ' posts remaining'); 284 | } else { 285 | $('current').update('Nothing found'); 286 | $('current').up('p').style.color = "#FF0000"; 287 | $('start').disable(); 288 | } 289 | }); 290 | 291 | } 292 | }; 293 | 294 | $('scanTags').onchange(); 295 | } 296 | 297 | function scanPage(offset) { 298 | var query = $('scanTags').value; 299 | var isNum = query && /^\d+$/.test(query); 300 | var limit; 301 | 302 | if (isNum) { 303 | limit = parseInt(query, 10); 304 | query = 'all'; 305 | } 306 | 307 | getPage('https://' + currentBooru + '.booru.org/index.php?page=post&s=list' + (query ? '&tags=' + query : '') + '&pid=' + offset, function (html) { 308 | var tags, ilinks, next, tag, temp = {}; 309 | 310 | Current = offset; 311 | $('current').update(Current + ' posts remaining'); 312 | $('scanProgress').value = start - Current; 313 | 314 | tags = $A(html.querySelectorAll('#tag_list ul li span')); 315 | tags.forEach(function (span) { 316 | tag = span.querySelector('a').getAttribute('href'); 317 | tag = tag && tag.split('tags=')[1]; 318 | 319 | if (tag) { 320 | tag = decodeURIComponent(tag).replace(/\"|\'/g, ''); 321 | window.taglist[tag] = Number(span.textContent.split(/\s+/).last()); 322 | BAPtags[tag] = window.taglist[tag]; 323 | } 324 | }); 325 | 326 | if (Object.keys(BAPtags).length) { 327 | localStorage.setItem('BAPtags', JSON.stringify(BAPtags)); 328 | } 329 | 330 | ilinks = html.querySelectorAll('.content .thumb > a'); 331 | $A(ilinks).forEach(function (v) { 332 | var id = v.id.replace('p', ''); 333 | var data = v.querySelector('img').title.trim(); 334 | var itags = data.split('score')[0].trim().split(/\s+/).sort(); 335 | var score = data.split('score:')[1].split('rating')[0].trim(); 336 | var rating = data.split('rating:')[1].split('')[0].toLowerCase(); 337 | var src = v.querySelector('img').src; 338 | var ext = src.split('.').last(); 339 | var hash = src.split('thumbnail_')[1].split('.')[0]; 340 | var cluster = src.split('thumbnail')[1].replace(/\/+|s/g, ''); 341 | 342 | window.thumblist.push(src); 343 | window.linklist.push(src.replace('thumbs', 'img').replace('thumbnails', 'images').replace('thumbnail_', '')); 344 | window.postlist[hash] = { 345 | c: Number(cluster), 346 | e: ext, 347 | i: Number(id), 348 | r: rating, 349 | s: Number(score), 350 | t: itags 351 | }; 352 | }); 353 | 354 | next = html.querySelector('a[alt="back"]'); 355 | next = (next && next.getAttribute('href').split("pid=").last()) || ''; 356 | 357 | if (next && start) { 358 | window.setTimeout(function () { 359 | scanPage(next); 360 | }, 500); 361 | var mins = Math.floor(0.6 * offset / 20 / 60); 362 | var secs = Math.floor((0.6 * offset / 20) % 60); 363 | 364 | $('timeValue').update(("0" + mins).slice(-2) + ':' + ("0" + secs).slice(-2)); 365 | } else { 366 | Object.keys(window.taglist).sort().forEach(function (key) { 367 | temp[key] = window.taglist[key]; 368 | }); 369 | window.taglist = temp; 370 | 371 | if ((Object.keys(window.taglist).length) && (query == 'all') && !isNum) { 372 | localStorage.setItem('BAPtags', JSON.stringify(window.taglist)); 373 | } 374 | 375 | $('start').enable(); 376 | $('scanTags').enable(); 377 | $('time').hide(); 378 | } 379 | }); 380 | 381 | } 382 | 383 | function showTags() { 384 | var allTags = $('allTags'); 385 | 386 | if (!allTags) { 387 | new Insertion.After($$('form > p')[0], 388 | '
    \ 389 | \ 390 | \ 391 | \ 392 | \ 393 | \ 394 | \ 395 | \ 396 | \ 397 |
    Tag list by post amount
    tagposts
    \ 398 |
    '); 399 | allTags = $('allTags'); 400 | } else { 401 | allTags.up('.option').show(); 402 | } 403 | $('scanner').up('.option').hide(); 404 | 405 | if (allTags.down('a')) { 406 | return; 407 | } 408 | 409 | Object.keys(BAPtags).sort(function (a, b) { 410 | a = BAPtags[a]; 411 | b = BAPtags[b]; 412 | 413 | return a == b ? 0 : ((b - a) / Math.abs(b - a)); 414 | }).each(function (tag) { 415 | if (BAPtags.hasOwnProperty(tag)) { 416 | if (BAPtags[tag] < 1) { 417 | delete BAPtags[tag]; 418 | } else { 419 | new Insertion.Bottom(allTags, '' + tag + '' + BAPtags[tag] + ''); 420 | } 421 | } 422 | }); 423 | if (Object.keys(BAPtags).length) { 424 | localStorage.setItem('BAPtags', JSON.stringify(BAPtags)); 425 | } 426 | } -------------------------------------------------------------------------------- /js/postPage.js: -------------------------------------------------------------------------------- 1 | postPageMain(); 2 | 3 | function postPageMain() { 4 | var image = $('image'); 5 | var taglist = $$('div#tag_list li a'); 6 | 7 | storeTags(); 8 | 9 | if (image.getWidth() > 1480) { 10 | $('note-container').setAttribute('style', 'cursor:pointer'); 11 | } 12 | 13 | new Insertion.Bottom($$('head')[0], 14 | '' 21 | ); 22 | 23 | image.setAttribute('style', ''); 24 | image.onclick = function () { 25 | toggleFitIn(this); 26 | }; 27 | 28 | taglist.each(function (tagli) { 29 | var taghref = tagli.href.split('&tags=')[1]; 30 | 31 | if (tagli.textContent.trim() || (taghref && ~taghref.indexOf('_'))) { 32 | 33 | inserTag({ 34 | text: decodeURIComponent(taghref), 35 | num: tagli.up('span').textContent.split(' ').last() 36 | }, tagli.up('li')); 37 | } 38 | }); 39 | 40 | var br1 = $$('div#tag_list br')[0]; 41 | var customTags = (readCookie("tags") || '').toLowerCase().split(/[, ]|%20+|\+/g).sort().reverse(); 42 | var currentTags = $('tags').value.split(/\s+/); 43 | 44 | currentTags.each(function (tag) { 45 | customTags = customTags.without(tag); 46 | }); 47 | if ((customTags.length) && (readCookie('tags'))) { 48 | customTags.each(function (tag) { 49 | if (tag) { 50 | inserTag({ 51 | text: tag 52 | }, br1); 53 | } 54 | }); 55 | if (window.BAPopts.solo) { 56 | var taglistString = taglist.join(' '); 57 | if ((taglist.length == 1 && taglist[0].textContent.trim() != 'solo') || 58 | (taglist.length == 2 && !~taglistString.indexOf('solo') && ~taglistString.indexOf('tagme'))) { 59 | if (!$$('.customTag').some(function (ct) { 60 | var ctli = ct.up('li'); 61 | if (~ctli.textContent.split(/\s+/).indexOf('solo')) { 62 | ctli.style.backgroundColor = 'rgba(0,255,0,0.25)'; 63 | return true; 64 | } 65 | })) { 66 | inserTag({text: 'solo'}, br1); 67 | } 68 | } 69 | } 70 | new Insertion.After($$('a.aAdd').last().up('li'), '
    '); 71 | } 72 | 73 | var strong = $$('#tag_list ul strong')[0]; 74 | 75 | inserTag({}, strong.previous()); 76 | new Insertion.Before(strong, '
    '); 77 | statisticsArea(strong); 78 | 79 | linkifyContainer('#note-container > div[id^="c"][style],#tag_list > ul'); 80 | } 81 | 82 | function statisticsArea(strong) { 83 | strong.innerHTML = '' + strong.innerHTML + ''; 84 | var pointer = strong.nextSibling; 85 | 86 | while (pointer && pointer.tagName != 'li') { 87 | if (pointer.nodeType === 3) { 88 | var split = pointer.data.split(':'); 89 | if (split.length > 1) { 90 | if (~split[0].indexOf('Score') || (~split[0].indexOf('Source') && !$('source').value)) { 91 | pointer.parentNode.removeChild(pointer.previousSibling); 92 | pointer.data = ''; 93 | } else { 94 | if (~split[0].indexOf('By')) { 95 | var userlink = split[1].trim() == 'Anonymous' ? 96 | 'Anonymous' : 97 | '' + split[1].trim() + ''; 98 | new Insertion.Before(pointer.nextSibling, userlink); 99 | pointer.data = split[0] + ': '; 100 | split[1] = ' '; 101 | } 102 | new Insertion.After(pointer.previousSibling, '' + split[0] + ''); 103 | split[0] = ''; 104 | pointer.data = split.join(':'); 105 | } 106 | } 107 | } 108 | if ((!pointer.nextSibling || pointer.nextSibling.tagName == 'li') && $('title').value) { 109 | new Insertion.Before(pointer, 'Title: ' + $('title').value); 110 | break; 111 | } 112 | pointer = pointer.nextSibling; 113 | } 114 | } 115 | 116 | function inserTag(tag, where) { 117 | var aAdd = ' '; 118 | var aEdit = ' '; 119 | var aDelete = ' '; 120 | var editField = ''; 121 | var tagLink = ' '; 122 | 123 | if (tag.text && !tag.num) { //custom tag 124 | aAdd = aAdd.replace('style="display:none;"', 'style=""'); 125 | tagLink = '' + tag.text + ''; 126 | } else if (!tag.text) { //new tag input 127 | tagLink = tagLink.replace('style=""', 'style="display:none;"'); 128 | editField = editField.replace('style="display:none;"', 'style=""'); 129 | } else { //existing tag 130 | tagLink = tagLink.replace('&tags=', '&tags=' + encodeURIComponent(tag.text)).replace('>' + tag.text + '' + aAdd + aEdit + tagLink + editField + aDelete + ' ' + (tag.num || '') + ''; 136 | 137 | new Insertion.After(where, '
  • ' + span + '
  • '); 138 | markTags(where.next('li')); 139 | 140 | where.next().down('.aAdd').onclick = function () { 141 | addTag(this); 142 | }; 143 | where.next().down('.aEdit').onclick = function () { 144 | togglEdit(this); 145 | }; 146 | where.next().down('.aDelete').onclick = function () { 147 | exclude(this); 148 | }; 149 | if (tag.text) { 150 | where.next().down('.editField').onblur = function () { 151 | applyEdit(this); 152 | }; 153 | where.next().down('.editField').onkeydown = function (event) { 154 | if (event.keyCode == 13) { 155 | this.blur(); 156 | } 157 | }; 158 | } else { 159 | where.next().down('.editField').onchange = function (event) { 160 | applyEdit(this); 161 | }; 162 | where.next().down('.editField').id = 'newTag'; 163 | where.next().down('.editField').onfocus = function () { 164 | loadOptions(this); 165 | }; 166 | where.next().down('.editField').oninput = function () { 167 | enableDatalist(this); 168 | }; 169 | } 170 | if (tag.text && ~where.textContent.indexOf(tag.text.replace(/_/g, ' '))) { 171 | where.parentNode.removeChild(where); 172 | } 173 | } 174 | 175 | function showButton() { 176 | if ($('btnSubmit')) { 177 | return; 178 | } 179 | 180 | new Insertion.Before($$('div#tag_list ul strong')[0], '

    '); 181 | $('btnSubmit').onclick = function () { 182 | mySubmit(); 183 | }; 184 | } 185 | 186 | function mySubmit() { 187 | var btnSubmit = $('btnSubmit'); 188 | var spinner; 189 | 190 | new Insertion.Before(btnSubmit, ''); 191 | spinner = $('spinner'); 192 | btnSubmit.hide(); 193 | $('edit_form').request({ 194 | onComplete: function (response) { 195 | if (~response.responseText.indexOf('ou are not logged in')) { 196 | $('spinner').hide(); 197 | new Insertion.Before($$('div#tag_list ul strong')[0], '

    You are not logged in

    '); 198 | return; 199 | } 200 | var br = $$('#tag_list ul strong')[0].previous('br'); 201 | 202 | br.parentNode.removeChild(br); 203 | spinner.parentNode.removeChild(spinner); 204 | btnSubmit.parentNode.removeChild(btnSubmit.previous('br')); 205 | btnSubmit.parentNode.removeChild(btnSubmit); 206 | 207 | var taglist; 208 | 209 | taglist = $$('div#tag_list li a.aDelete').findAll(function (el) { 210 | return el.visible(); 211 | }); 212 | var lis = {}; 213 | 214 | taglist.each(function (taglink) { 215 | lis[taglink.previous('a').textContent.trim()] = true; 216 | }); 217 | taglist.each(function (taglink) { 218 | taglink.up('li').parentNode.removeChild(taglink.up('li')); 219 | }); 220 | var sorted = Object.keys(lis).sort().reverse(); 221 | 222 | new Insertion.Top($$('#tag_list ul')[0], '
    '); 223 | sorted.each(function (tag) { 224 | inserTag({ 225 | text: tag, 226 | num: window.BAPtags[tag.replace(/\s+/g, '_').replace(/\'/g, '')] 227 | }, $$('#tag_list ul br')[0]); 228 | }); 229 | br = $$('#tag_list ul br')[0]; 230 | br.parentNode.removeChild(br); 231 | 232 | if (Object.keys(window.BAPtags).length) { 233 | localStorage.setItem('BAPtags', JSON.stringify(window.BAPtags)); 234 | } 235 | 236 | searchField(); 237 | }, 238 | onFailure: function () { 239 | spinner.parentNode.removeChild(spinner); 240 | btnSubmit.show().disable().value = "error"; 241 | } 242 | }); 243 | } 244 | 245 | function toggleFitIn(that) { 246 | //TODO this really needs improvement, CSS 247 | if (that.getAttribute('style')) { 248 | that.setAttribute('style', ''); 249 | } else { 250 | that.setAttribute('style', 'max-width:90000px !important;'); 251 | } 252 | } 253 | 254 | function addTag(that) { 255 | var tag = that.next('span.customTag').textContent.trim().replace(/\s+/g, '_'); 256 | var currentNum = that.up('span').lastChild; 257 | 258 | currentNum.data = ' ' + String(window.BAPtags[tag] || ''); 259 | that.hide(); 260 | that.next('.aEdit').show(); 261 | that.next('.aDelete').show(); 262 | that.next('span.customTag').replace('' + tag + ''); 263 | $('tags').value += ' ' + tag; 264 | window.BAPtags[tag] = Number(window.BAPtags[tag] || 0) + 1; 265 | 266 | showButton(); 267 | } 268 | 269 | 270 | function isANSI(s) { 271 | var is = true; 272 | 273 | s = s.split(''); 274 | s.each(function (v) { 275 | is = is && (/[\u0000-\u00ff]/.test(v)); 276 | }); 277 | 278 | return is; 279 | } 280 | 281 | function applyEdit(that) { 282 | var value = that.value.trim().replace(/\s/g, '_').toLowerCase(); 283 | if (!value) { 284 | if (that.id != 'newTag') { 285 | exclude(that); 286 | } 287 | return; 288 | } 289 | 290 | var existing = $$('#tag_list ul > li > span > a:not([class])'); 291 | var element; 292 | var exists = existing.some(function (tag) { 293 | if (tag.textContent.trim().replace(/\s/g, '_') == value) { 294 | element = tag; 295 | 296 | return true; 297 | } 298 | }); 299 | var oldTag = that.previous('a').textContent.trim().replace(/\s/g, '_') || ''; 300 | 301 | if (exists && (value != oldTag)) { 302 | element.up('li').style.backgroundColor = 'rgba(255,255,0,0.5)'; 303 | setTimeout(function () { 304 | element.up('li').style.backgroundColor = ''; 305 | }, 1000); 306 | that.focus(); 307 | 308 | return; 309 | } 310 | 311 | if (!isANSI(value) && window.BAPopts.ansiOnly) { 312 | that.style['backgroundColor'] = '#fc0'; 313 | that.value = oldTag; 314 | that.focus(); 315 | return; 316 | } else { 317 | that.style['backgroundColor'] = ''; 318 | } 319 | var link = that.previous('span > a'); 320 | link.textContent = value; 321 | 322 | link.href = 'index.php?page=post&s=list&tags=' + encodeURIComponent(value); 323 | 324 | link.show(); 325 | that.hide(); 326 | 327 | that.onkeydown = function (event) { 328 | if (event.keyCode == 13) { 329 | this.blur(); 330 | } 331 | }; 332 | link.previous('.aEdit').show(); 333 | link.next('.aDelete').show(); 334 | 335 | $('tags').value = ($('tags').value.replace(oldTag, '') + ' ' + value.replace(/\s/g, '_')).replace(/\s+/g, ' '); 336 | 337 | if (oldTag != value) { 338 | var currentNum = that.up('span').lastChild; 339 | 340 | currentNum.data = ' ' + String(window.BAPtags[value] || '0'); 341 | markTags(that.up('li')); 342 | 343 | window.BAPtags[value] = Number(window.BAPtags[value] || 0) + 1; 344 | if (oldTag) { 345 | window.BAPtags[oldTag] = Math.max(Number(window.BAPtags[oldTag] || 0) - 1, 0); 346 | if (window.BAPtags[oldTag] === 0) { 347 | delete window.BAPtags[oldTag]; 348 | } 349 | } 350 | showButton(); 351 | } 352 | if (that.id == 'newTag') { 353 | that.id = ''; 354 | that.removeAttribute('oninput'); 355 | inserTag({}, $('btnSubmit').previous('li')); 356 | $('newTag').focus(); 357 | } 358 | that.placeholder = 'edit tag'; 359 | that.onblur = function () { 360 | applyEdit(this); 361 | }; 362 | } 363 | 364 | 365 | function exclude(that) { 366 | var li = that.up('li'); 367 | var tag = li.down('.aEdit').next('a').textContent.trim().replace(/\s+/g, '_'); 368 | 369 | li.parentNode.removeChild(li); 370 | $('tags').value = $('tags').value.split(/\s+/).without(tag).join(' '); 371 | window.BAPtags[tag] = Math.min(Number(window.BAPtags[tag] || 0) - 1, 0); 372 | if (window.BAPtags[tag] === 0) { 373 | delete window.BAPtags[tag]; 374 | } 375 | showButton(); 376 | } 377 | 378 | function togglEdit(that) { 379 | var span = that.parentNode; 380 | 381 | span.down('input').show().focus(); 382 | span.down('a.aEdit').next('a').hide(); 383 | } 384 | --------------------------------------------------------------------------------