code:
# turjanis

## Getting started

To make it easy for you to get started with GitLab, here's a list of recommended next steps.

Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!

## Add your files

- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:

```
cd existing_repo
git remote add origin https://gitgud.io/aetiawas/turjanis.git
git branch -M master
git push -uf origin master
```

## Integrate with your tools

- [ ] [Set up project integrations](https://gitgud.io/aetiawas/turjanis/-/settings/integrations)

## Collaborate with your team

- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)

## Test and Deploy

Use the built-in continuous integration in GitLab.

- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)

***

# Editing this README

When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.

## Suggestions for a good README

Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.

## Name
Choose a self-explaining name for your project.

## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.

## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.

## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.

## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.

## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.

## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.

## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.

## Contributing
State if you are open to contributions and what your requirements are for accepting them.

For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.

You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.

## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.

## License
For open source projects, say how it is licensed.

## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

repo_name: aetiawas/turjanis


code:
**KC-Addon**

Requirements:

- LynxChan 2.4.x: https://gitgud.io/LynxChan/LynxChan/tree/2.4.x
- KohlNumbra: https://gitgud.io/Tjark/KohlNumbra
- Kohlcash: https://gitgud.io/Tjark/Kohlcash (optional)
- split-html to handle shortened posts with multiline markdown: npm install split-html
- Create dont-reload/globalSalt (random) and config/inlineImages.json (copy config/inlineImages.json.example)
- mimetypes (perl); libfile-mimeinfo-perl on Debian => used as a fallback for some files that are not recognized by file(1)

repo_name: LynxChan/lynxchanaddon

license: unknown


code:
'use strict';

var domCommon = require('../../engine/domManipulator').common;
var db = require('../../db');
var threads = db.threads();
var posts = db.posts();

var projectionBlock = {
  projection : {
    _id : 0,
    id : 1,
    name : 1,
    flag : 1,
    files : 1,
    email : 1,
    postId : 1,
    message : 1,
    subject : 1,
    boardUri : 1,
    markdown : 1,
    creation : 1,
    threadId : 1,
    flagName : 1,
    flagCode : 1,
    signedRole : 1,
    innerCache : 1,
    banMessage : 1,
    lastEditTime : 1,
    lastEditLogin : 1,
    alternativeCaches : 1
  }
};

exports.getPostsFromQuery = function(query, callback) {

  threads.find(query, projectionBlock).sort({
    creation : -1
  }).toArray(function gotThreads(error, foundThreads) {

    if (error) {
      callback(error);
    } else {

      // style exception, too simple
      posts.find(query, projectionBlock).sort({
        creation : -1
      }).toArray(function gotPosts(error, foundPosts) {

        if (error) {
          callback(error);
        } else {

          callback(null, foundPosts.concat(foundThreads).sort(function(a, b) {
            return b.creation - a.creation;
          }));

        }

      });
      // style exception, too simple

    }

  });

};

exports.buildPostList = function(postings, language) {

  var postsContent = '';

  var operations = [];

  for (var i = 0; i < postings.length; i++) {

    var postContent = domCommon.getPostCellBase(postings[i]);

    postContent += domCommon.getPostInnerElements(postings[i], true, language, operations);

    postsContent += postContent + '</div>';
  }

  domCommon.handleOps(operations);

  return postsContent;

};


template: <img class="__class__" src="__path__">

whitelist: a b c


code:
var postingOpsCommon = require('../../engine/postingOps').common;
var postingOpsPost = require('../../engine/postingOps').post;
var postingOpsThread = require('../../engine/postingOps').thread;
var settings = require('../../settingsHandler').getGeneralSettings();
var miscOps = require('../../engine/miscOps');
var katex = require('katex');
var hljs = require('highlight.js');
const crypto = require("crypto");

var dontProcessLinks;

exports.loadSettings = function() {
  dontProcessLinks = settings.dontProcessLinks;
};

// inline image stuff
var fs = require('fs');
var config;
var limit = 0;
var data;

var italicFunction2 = function(match) {
  return '' + match.substring(3, match.length - 4) + '';
};

var boldFunction2 = function(match) {
  return '' + match.substring(3, match.length - 4) + '';
};

var underlineFunction2 = function(match) {
  return '' + match.substring(3, match.length - 4) + '';
};

var strikeFunction2 = function(match) {
  return '<s>' + match.substring(3, match.length - 4) + '</s>';
};

var greenTextFunction = function(match) {
  return '' + match + '';
};

var greenTextInlineFunction = function(match) {
  return '>' + match.substring(7, match.length - 8) + '';
};

var orangeTextFunction = function(match) {
  return '' + match + '';
};

var orangeTextInlineFunction = function(match) {
  return '<' + match.substring(8, match.length - 9) + '';
};

var redTextFunction = function(match) {
  var content = match.substring(2, match.length - 2);

  return '' + content + '';
};

var italicFunction = function(match) {
  return '' + match.substring(12, match.length - 12) + '';
};

var boldFunction = function(match) {
  return '' + match.substring(18, match.length - 18) + '';
};

var underlineFunction = function(match) {
  return '' + match.substring(2, match.length - 2) + '';
};

var strikeFunction = function(match) {
  return '<s>' + match.substring(2, match.length - 2) + '</s>';
};

var spoilerFunction = function(match) {

  var content = match.substring(9, match.length - 10);

  return '' + content + '';
};

var altSpoilerFunction = function(match) {

  var content = match.substring(2, match.length - 2);

  return '' + content + '';
};

var htmlReplaceRegexReversed = new RegExp(/<|>|"|'/g);

var htmlReplaceTableReversed = {
  '<' : '<',
  '>' : '>',
  '"': '\"',
  ''': '\''
};

// HTML-escaped characters need to be undone for katex to interpret them correctly
var reverseHtmlReplacements = function(content) {

  return content.replace(htmlReplaceRegexReversed, function(match) {
    return htmlReplaceTableReversed[match];
  }).replace(/&/g, '&');

};

var replaceWithHtmlChar = function(character) {
  return '&#' + character.charCodeAt(0) + ';';
};

var escapeRelevantCharacters = function(message) {

  message = message.replace(/\$|\:|_|~|\[|\]/g, replaceWithHtmlChar); // cannot do = because of html tag attributes
  message = message.replace(/>/g, '>')
  message = message.replace(/</g, '<')

  return message;

};

var codeFunction = function(match) {

  var firstNewlinePosition = match.indexOf('\n');

  if (firstNewlinePosition === -1) {

    return match;

  }

  var language = match.substring(5, firstNewlinePosition);

  var content = match.substring(firstNewlinePosition + 1, match.length - 5);
  content = reverseHtmlReplacements(content);

  var value = '';

  if (language === '' || language === 'auto') {

    value = '
' + hljs.highlightAuto(content).value + '
';

  } else {

    try {

      value = '
' + hljs.highlight(language, content).value + '
';

    } catch(error) {

      return match;

    }

  }

  return escapeRelevantCharacters(value);

};

var katexFunction = function(match, obj) {

  var content = match.substring(2, match.length - 2);

  content = reverseHtmlReplacements(content);

  if (content.indexOf('<') > -1) {
    return match;
  }

  // Katex should always return a safe html string in the end

  content = katex.renderToString(content, {
    throwOnError: false,
    trust: false,
    output: 'html',
    maxExpand: 100,
    maxSize: 25
  });

  return escapeRelevantCharacters(content);
};

exports.initInlineImages = function() {

  try {
    config = JSON.parse(fs.readFileSync(__dirname + '/config/inlineImages.json', 'utf8'))
  } catch (error) {
    console.log(error);
    return;
  }

  data = {};

  limit = config.limit || 30;

  for (var j = 0; j < config.collections.length; ++j) {

    var collection = config.collections[j];

    for (var i = 0; i < config.collections[j].images.length; ++i) {

      var image = config.collections[j].images[i];
      var width;
      var height;

      if (image.size) {
        var size = image.size.split(',');
        width = size[0];
        height = size[1];
      }

      data[image.name] = {
        'html': config.template.replace(
          '__class__', 'emote'
        ).replace(
          '__path__', collection.path + image.path
        ).replace(
          '__width__', width ? width + 'px' : 'auto'
        ).replace(
          '__height__', height ? height + 'px' : 'auto'
        ),
        'whitelist': collection.whitelist || null,
        'blacklist': collection.blacklist || null
      };

      data['flip.' + image.name] = {
        'html': data[image.name].html.replace(
          'class="emote"', 'class="emote flipped"'
        ),
        'whitelist': collection.whitelist || null,
        'blacklist': collection.blacklist || null
      };

    }
  }

}

exports.init = function() {

  var inlineImg = function(message, board) {

    var counter = 0;

    return message.replace(/\:[\.0-9a-zA-Z\-äöüÄÖÜßẞ]+\:/g, function found(match) {

      var word = match.slice(1, -1).toLowerCase();

      if (counter++ < limit && word in data) {

        if (data[word].whitelist && !data[word].whitelist.includes(board)) {
          return match;
        }

        if (data[word].blacklist && data[word].blacklist.includes(board)) {
          return match;
        }

        match = data[word].html;

      }

      return match;

    });

  };

  var removeZalgo = function(message) {

    if (!message)
      return message;

    var filteredMessage = "";

    var zalgocount = 0;

    for (var i = 0; i < message.length; i++) {
      var ord = message.charCodeAt(i);

      if ((ord >= 768 && ord <= 879) ||
        (ord >= 1536 && ord <= 1562) ||
        (ord >= 1611 && ord <= 1631) ||
        (ord == 1648) ||
        (ord >= 1750 && ord <= 1775) ||
        (ord >= 3626 && ord <= 3661) ||
        (ord >= 3655 && ord <= 3659) ||
        (ord == 3662) ||
        (ord >= 7616 && ord <= 7679) ||
        (ord >= 8400 && ord <= 8447) ||
        (ord >= 65056 && ord <= 65071)) {
        zalgocount += 1;
        if (zalgocount <= 5) {
          filteredMessage += message.charAt(i);
        }
      }
      else {
        zalgocount = 0;
        filteredMessage += message.charAt(i);
      }
    }

    return filteredMessage;

  }

  var removeAds = function(message) {

    if (!message)
      return message;

    message = message.replace(
      /[\u200B-\u200D\uFEFF\u200E\u200F]/ig,
      ''
    );

    message = message.replace(
      /brave\.com\/(.*?)(\W|$)/ig,
      'brave.com/Adolf1488$2'
    );

    message = message.replace(
      /coinbase\.com\/earn\/xlm\/invite\/(.*?)(\W|$)/ig,
      'coinbase.com/earn/xlm/invite/Neger32cm$2'
    );

    message = message.replace(
      /amzn\.to\/(.*?)(\W|$)/ig,
      'amzn.to/P0W4L0W5K1$2'
    );

    message = message.replace(
      /amazon\.(.*?)tag=(.*?)(\&|\s|$)/ig,
      'amazon.$1tag=fernsehkrit09-21$3'
    );

    return message;

  };

  // + board parameter
  postingOpsCommon.replaceChunkMarkdown = function(message, board) {

    var aaSplits = postingOpsCommon.getTagSplits(message, 'aa');

    var lastEnding = 0;

    var finalMessage = '';

    for (var i = 0; i < aaSplits.length; i++) {

      var split = aaSplits[i];

      /* KC-CHANGE */
      finalMessage += postingOpsCommon.getSubChunkMarkdown(message.substring(lastEnding,
        split.start), board);
      /* KC-CHANGE */

      var aaChunk = message.substring(split.start + 4, split.end);

      finalMessage += '' + aaChunk + '';

      lastEnding = split.end + 5;

    }

    /* KC-CHANGE */
    finalMessage += postingOpsCommon.getSubChunkMarkdown(message.substring(lastEnding), board);
    /* KC-CHANGE */
    return finalMessage;
  };

  // + board parameter
  postingOpsCommon.replaceStyleMarkdown = function(message, replaceCode, boardMessage, board) {

    var codeSplits = replaceCode ? postingOpsCommon.getTagSplits(message, 'code') : [];

    var lastEnding = 0;

    var finalMessage = '';

    for (var i = 0; i < codeSplits.length; i++) {

      var split = codeSplits[i];

      /* KC-CHANGE */
      finalMessage += postingOpsCommon.replaceChunkMarkdown(message.substring(lastEnding, split.start), board);
      /* KC-CHANGE */

      var codeChunk = message.substring(split.start + 6, split.end);

      var firstNewlinePosition = codeChunk.indexOf('\n');

      if (firstNewlinePosition === 0) {
        codeChunk = codeChunk.substring(1);
      }

      finalMessage += '' + codeChunk + '';

      lastEnding = split.end + 7;

    }

    /* KC-CHANGE */
    finalMessage += postingOpsCommon.replaceChunkMarkdown(message.substring(lastEnding), board);
    /* KC-CHANGE */

    if (!dontProcessLinks && !boardMessage) {

      finalMessage = finalMessage.replace(/(http|https)\:\/\/[^<\s]+/g,
        function links(match) {

          match = miscOps.cleanHTML(match).replace(/[_='~*]/g,
            function sanitization(innerMatch) {
              return postingOpsCommon.linkSanitizationRelation[innerMatch];
            });

          return '' + match + '';

        });

    }

    return finalMessage;

  };

  var originalReplaceMarkdown = function(message, posts, board, replaceCode, cb) {

    var postObject = {};

    for (var i = 0; i < posts.length; i++) {
      var post = posts[i];

      var boardPosts = postObject[post.boardUri] || {};

      boardPosts[post.postId] = post.threadId;

      postObject[post.boardUri] = boardPosts;
    }

    /* KC-CHANGE */
    message = message.replace(/XXXXX.+?XXXXX/sg, codeFunction);
    message = message.replace(/(\$\$.+?\$\$)/sg, katexFunction);
    /* KC-CHANGE */

    message = message.replace(/>>>\/\w+\/\d+/g, function crossQuote(
      match) {

      match = match.substring(12);

      var quoteParts = match.match(/(\w+|\d+)/g);

      var quotedBoard = quoteParts[0];
      var quotedPost = +quoteParts[1];

      var boardPosts = postObject[quotedBoard] || {};

      var quotedThread = boardPosts[quotedPost] || quotedPost;

      var link = '/' + quotedBoard + '/res/';

      link += quotedThread + '.html#' + quotedPost;

      var toReturn = '>>>';

      return toReturn + match + '';

    });

    message = message.replace(/>>>\/\w+\/(?!\d)/g,
      function board(match) {
        return '' + match + '';
      });

    message = message.replace(/>>\d+/g, function quote(match) {

      var quotedPost = match.substring(8);

      var boardPosts = postObject[board] || {};

      var quotedThread = boardPosts[quotedPost] || quotedPost;

      var link = '/' + board + '/res/';

      link += quotedThread + '.html#' + quotedPost;

      var toReturn = '>>';

      toReturn += quotedPost + '';

      return toReturn;

    });

    /* KC-CHANGE */
    cb(null, postingOpsCommon.replaceStyleMarkdown(message.replace(/\r\n/gm, '\n').replace(/\r/gm, '\n'), replaceCode, null, board));
    /* KC-CHANGE */

  };

  postingOpsCommon.replaceMarkdown = function(message, posts, board, replaceCode, cb) {

    message = removeAds(message);
    message = removeZalgo(message);

    originalReplaceMarkdown(message, posts, board, replaceCode, cb);
  };

  var originalCreatePost = postingOpsPost.createPost;
  var originalCreateThread = postingOpsThread.createThread;

  postingOpsPost.createPost = function(req, parameters, newFiles, userData, postId,
    thread, board, wishesToSign, enabledCaptcha, cb) {

    parameters.subject = removeAds(parameters.subject);
    parameters.subject = removeZalgo(parameters.subject);

    if (parameters.name !== board.anonymousName) {
      parameters.name = removeAds(parameters.name);
      parameters.name = removeZalgo(parameters.name);
    }

    originalCreatePost(req, parameters, newFiles, userData, postId,
      thread, board, wishesToSign, enabledCaptcha, cb);

  }

  postingOpsThread.createThread = function(req, userData, parameters, newFiles, board,
    threadId, wishesToSign, enabledCaptcha, callback) {

    parameters.subject = removeAds(parameters.subject);
    parameters.subject = removeZalgo(parameters.subject);

    if (parameters.name !== board.anonymousName) {
      parameters.name = removeAds(parameters.name);
      parameters.name = removeZalgo(parameters.name);
    }

    originalCreateThread(req, userData, parameters, newFiles, board,
      threadId, wishesToSign, enabledCaptcha, callback);

  }

  postingOpsCommon.getSubChunkMarkdown = function(message, board) {

    message = inlineImg(message, board);

    message = message.replace(/^>.*/gm, greenTextFunction);
    message = message.replace(/\=\=.+?\=\=/g, redTextFunction);
    message = message.replace(/'''.+?'''/g,
      boldFunction);
    message = message.replace(/\_\_.+?\_\_/g, underlineFunction);
    message = message.replace(/\~\~.+?\~\~/g, strikeFunction);
    message = message.replace(/\[spoiler\].+?\[\/spoiler\]/sg, spoilerFunction);
    message = message.replace(/\*\*.+?\*\*/g, altSpoilerFunction);

    message = message.replace(/\[s\].+?\[\/s\]/sg, strikeFunction2);
    message = message.replace(/\[b\].+?\[\/b\]/sg, boldFunction2);
    message = message.replace(/\[u\].+?\[\/u\]/sg, underlineFunction2);
    message = message.replace(/\[i\].+?\[\/i\]/sg, italicFunction2);

    message = message.replace(/\[quote\].+?\[\/quote\]/sg, greenTextInlineFunction);
    message = message.replace(/\[quote2\].+?\[\/quote2\]/sg, orangeTextInlineFunction);

    message = message.replace(/^<.*/gm, orangeTextFunction);

    var split = message.split('\n');

    return split.join('\n');

  };

  exports.initInlineImages();

};


code:
globalSalt
blockedASN


code:
These are the specs of the extra templates required:

opCell:
Extra element: linkHistory, have it's href modified.

Extra element: linkAgent, have it's href manipulated.

Extra element: labelAgent, have it's innerHTML manipulated.

--------------------------------------------------------

postCell:
Extra element: linkHistory, have it's href modified.

Extra element: linkAgent, have it's href manipulated.

Extra element: labelAgent, have it's innerHTML manipulated.

--------------------------------------------------------

uploadCell:
Extra element: linkSpoil, have it's href modified and might be removed.

Extra element: linkDelete, have it's href modified and might be removed.

--------------------------------------------------------

Extra templates:

--------------------------------------------------------

ipSearchPage: used for the ip search feature.

Elements:

divPostings: have children appended to it.

--------------------------------------------------------

textSearchPage: used for the text search feature.

Elements:

divPostings: have children appended to it.

fieldSubject: have it's value manipulated.

fieldMessage: have it's value manipulated.

fieldName: have it's value manipulated.

fieldAgent: have it's value manipulated.

fieldFilename: have it's value manipulated.


code:
Features:
Global ip salting: global moderators that are not allowed to see the clear ip of users will see a hashed version of the ip using a global salt.
To create the salt, write it down on a file named "globalSalt" on this directory.
Important: if the engine is ever ran without this addon, make sure to run -cc after enabling the addon.
This is due to the fact the engine have individual caches for the posts cells and this addon disables the caching of these cells
for the version that contains hashed ips. The engine doesn't have a different place to store multiple versions of this hashed cache, so the first one
that would be seen would be cached and shown for everyone. Thus the need of this hack to ensure all users would see the correct hash.

Ip search: allows to see all posts from a given ip. Users must either be on the global staff or on the board staff of the board for the searched post.
If not on the global staff they will be able to only see posts on that same board.

Fake ips: non-traceable users are assigned fake ips generated by hashing the bypass id used. This allows for their post history to be tracked and for bans to be issues.

ASN lookup: a file named blockedASN on this directory is required. In this file, write down any ASN separated by new lines that should be handled as if they were tor users.
Use the KC branch of LocationDownloader to generate the binary file with the ip to ASN relation when updating the location data.

tor flag: tor users will be assigned a flag, this flag expects the static file 'flags/onion.png' to exist.
This feature is a little quirky when interacting with board flags. Unlike regular users, the priority of flag assignment will be tor flag and only then board flag. Regular users have
priority of the board flag they pick over their location flag.

Posters map: access the url /addon.js/kc?feature=map to see a json list of poster's locations in the last hour and in the last day. The list for the last hour is named H and the one
for the last day is named D.


code:
'use strict';

var formOps = require('../../engine/formOps');
var logger = require('../../logger');
var uploadHandler = require('../../engine/uploadHandler');

exports.init = function() {

  logger.MIMETYPES = {
    a : 'application/octet-stream',
    ai : 'application/postscript',
    aif : 'audio/x-aiff',
    aifc : 'audio/x-aiff',
    aiff : 'audio/x-aiff',
    au : 'audio/basic',
    avi : 'video/x-msvideo',
    bat : 'text/plain',
    bin : 'application/octet-stream',
    bmp : 'image/x-ms-bmp',
    c : 'text/plain',
    cdf : 'application/x-cdf',
    csh : 'application/x-csh',
    css : 'text/css',
    dll : 'application/octet-stream',
    doc : 'application/msword',
    dot : 'application/msword',
    dvi : 'application/x-dvi',
    eml : 'message/rfc822',
    eps : 'application/postscript',
    etx : 'text/x-setext',
    exe : 'application/octet-stream',
    flac : 'audio/flac',
    gif : 'image/gif',
    gtar : 'application/x-gtar',
    gz : 'application/gzip',
    h : 'text/plain',
    hdf : 'application/x-hdf',
    htm : 'text/html',
    html : 'text/html',
    jpe : 'image/jpeg',
    jpeg : 'image/jpeg',
    jpg : 'image/jpeg',
    js : 'application/x-javascript',
    ksh : 'text/plain',
    latex : 'application/x-latex',
    m1v : 'video/mpeg',
    man : 'application/x-troff-man',
    me : 'application/x-troff-me',
    mht : 'message/rfc822',
    mhtml : 'message/rfc822',
    mif : 'application/x-mif',
    mov : 'video/quicktime',
    movie : 'video/x-sgi-movie',
    mp2 : 'audio/mpeg',
    mp3 : 'audio/mpeg',
    mp4 : 'video/mp4',
    mpa : 'video/mpeg',
    mpe : 'video/mpeg',
    mpeg : 'video/mpeg',
    mpg : 'video/mpeg',
    ms : 'application/x-troff-ms',
    nc : 'application/x-netcdf',
    nws : 'message/rfc822',
    o : 'application/octet-stream',
    obj : 'application/octet-stream',
    oda : 'application/oda',
    ogg : 'audio/ogg',
    ogv : 'video/ogg',
    pbm : 'image/x-portable-bitmap',
    pdf : 'application/pdf',
    pfx : 'application/x-pkcs12',
    pgm : 'image/x-portable-graymap',
    png : 'image/png',
    pnm : 'image/x-portable-anymap',
    pot : 'application/vnd.ms-powerpoint',
    ppa : 'application/vnd.ms-powerpoint',
    ppm : 'image/x-portable-pixmap',
    pps : 'application/vnd.ms-powerpoint',
    ppt : 'application/vnd.ms-powerpoint',
    pptx : 'application/vnd.ms-powerpoint',
    ps : 'application/postscript',
    pwz : 'application/vnd.ms-powerpoint',
    py : 'text/x-python',
    pyc : 'application/x-python-code',
    pyo : 'application/x-python-code',
    qt : 'video/quicktime',
    ra : 'audio/x-pn-realaudio',
    ram : 'application/x-pn-realaudio',
    ras : 'image/x-cmu-raster',
    rdf : 'application/xml',
    rgb : 'image/x-rgb',
    roff : 'application/x-troff',
    rtx : 'text/richtext',
    sgm : 'text/x-sgml',
    sgml : 'text/x-sgml',
    sh : 'application/x-sh',
    shar : 'application/x-shar',
    snd : 'audio/basic',
    so : 'application/octet-stream',
    src : 'application/x-wais-source',
    svg : 'image/svg+xml',
    swf : 'application/x-shockwave-flash',
    t : 'application/x-troff',
    tar : 'application/x-tar',
    tcl : 'application/x-tcl',
    tex : 'application/x-tex',
    texi : 'application/x-texinfo',
    texinfo : 'application/x-texinfo',
    tif : 'image/tiff',
    tiff : 'image/tiff',
    tr : 'application/x-troff',
    tsv : 'text/tab-separated-values',
    txt : 'text/plain',
    ustar : 'application/x-ustar',
    vcf : 'text/x-vcard',
    wav : 'audio/x-wav',
    webm : 'video/webm',
    wiz : 'application/msword',
    wsdl : 'application/xml',
    xbm : 'image/x-xbitmap',
    xlb : 'application/vnd.ms-excel',
    xls : 'application/vnd.ms-excel',
    xlsx : 'application/vnd.ms-excel',
    xml : 'text/xml',
    xpdl : 'application/xml',
    xpm : 'image/x-xpixmap',
    xsl : 'application/xml',
    xwd : 'image/x-xwindowdump',
    zip : 'application/zip',
    webp : 'image/webp',
    '7z': 'application/x-7z-compressed',
    opus : 'audio/ogg',
    epub : 'application/epub+zip'
  };

  for ( var key in logger.MIMETYPES) {
    logger.reverseMimes[logger.MIMETYPES[key]] = key;
  }

  var originalGetMetaData = formOps.getMetaData;

  formOps.getMetaData = function(fields, files) {

    var toRet = originalGetMetaData(fields);

    for (var i = 0; i < toRet.length; i++) {

      var element = toRet[i];

      if (uploadHandler.correctedMimesRelation[element.mime]) {
        continue;
      }

      var newMime = logger.getMime(element.title);

      if (newMime !== 'application/octet-stream') {
        element.mime = newMime;
      }

    }

    return toRet;

  };

  var originalpProcessParsedRequest = formOps.processParsedRequest;

  formOps.processParsedRequest = function(res, fields, files, callback,
    parsedCookies, exceptionalMimes, language, json) {

    for (var i = 0; files.files && i < files.files.length; i++) {

      var file = files.files[i];

      if (uploadHandler.correctedMimesRelation[file.headers['content-type']]) {
        continue;
      }

      var newMime = logger.getMime(file.originalFilename);

      if (newMime !== 'application/octet-stream') {
        file.headers['content-type'] = newMime;
      }

    }

    originalpProcessParsedRequest(res, fields, files, callback, parsedCookies,
      exceptionalMimes, language, json);

  };

};


code:
'use strict';

exports.engineVersion = '2.4';

var mongo = require('mongodb');
var ObjectID = mongo.ObjectID;
var verbose;
var fs = require('fs');
var url = require('url');
var db = require('../../db');
var map = require('./map');
var threads = db.threads();
var latestPosts = db.latestPosts();
var posts = db.posts();
var bans = db.bans();
var boards = db.boards();
var logger = require('../../logger');
var locationLineSize = 60;
var compiledLocations = __dirname + '/../../locationData/compiledLocations';
var compiledIpsV6 = __dirname + '/../../locationData/compiledIpsV6';
var locationOps = require('../../engine/locationOps');
var lang = require('../../engine/langOps').languagePack;
var formOps = require('../../engine/formOps');
var miscOps = require('../../engine/miscOps');
var templateHandler = require('../../engine/templateHandler');
var mod = require('../../form/mod');
var domCommon = require('../../engine/domManipulator').common;
var domStatic = require('../../engine/domManipulator/static');
var domPostingContent = require('../../engine/domManipulator/postingContent');
var domMiscPages = require('../../engine/domManipulator').dynamicPages.miscPages;
var postingOpsPost = require('../../engine/postingOps').post;
var postingOpsThread = require('../../engine/postingOps').thread;
var postingOpsCommon = require('../../engine/postingOps').common;
var globalSalt;
var minClearIpRole;
var unlockHistory;
var validateMimes;
var maxGlobalLatestPosts;
var settings = require('../../settingsHandler').getGeneralSettings();
var jsonBuilder = require('../../engine/jsonBuilder');
var cacheHandler = require('../../engine/cacheHandler');
var editOps = require('../../engine/modOps/editOps.js');
var uploadHandler = require('../../engine/uploadHandler.js');
var gsHandler = require('../../engine/gridFsHandler');
var exec = require('child_process').exec;
var modOpsCommon = require('../../engine/modOps').common;
var modOpsSpecific = require('../../engine/modOps').ipBan.specific;
var modOpsGeneral = require('../../engine/modOps').ipBan.general;
var modOpsVersatile = require('../../engine/modOps').ipBan.versatile;
var loginOps = require('../../form/login.js');
var accountOps = require('../../engine/accountOps.js');
var transferOps = require('../../engine/modOps').transfer;
var logOps = require('../../engine/logOps');
var latestOps = require('../../engine/boardOps').latest;
var boardOps = require('../../form/boards.js');

var kernel = require('../../kernel');
var genericThumb = kernel.genericThumb();
var genericAudioThumb = kernel.genericAudioThumb();

var generatorGlobal = require('../../engine/generator').global;
var logs = db.logs();
var individualCaches = !kernel.feDebug();
var staffLogs = db.logs();
var generator = require('../../engine/generator');
var jitCache = require('../../engine/jitCacheOps');
var aggregatedLogs = db.aggregatedLogs();
var settingsHandler = require('../../settingsHandler');

var spamOps = require('../../engine/spamOps');
var native = kernel.native;

var crypto = require("crypto");

// Addon files
var extensionMime = require('./extensionMime');
var torFlags = require('./torFlags');
var customMarkdown = require('./customMarkdown');

exports.requestAlias = 'kc';

exports.loadSettings = function() {

  minClearIpRole = settings.clearIpMinRole;
  verbose = settings.verbose || settings.verboseCache;
  maxGlobalLatestPosts = settings.globalLatestPosts;
  validateMimes = settings.validateMimes;

  // 2.5
  unlockHistory = true;

  customMarkdown.initInlineImages();

  customMarkdown.loadSettings();

};

exports.globalSalt = function() {

  try {
    globalSalt = fs.readFileSync(__dirname + '/dont-reload/globalSalt')
      .toString();
  } catch (error) {
    throw error;
  }

}

// This adds a moderative autosage
exports.setAutosage = function() {

  editOps.setNewThreadSettings = function(parameters, thread, callback) {

    parameters.lock = !!parameters.lock;
    parameters.pin = !!parameters.pin;
    parameters.cyclic = !!parameters.cyclic;
    parameters.autoSage = !!parameters.autoSage;

    var changePin = parameters.pin !== thread.pinned;
    var changeLock = parameters.lock !== thread.locked;
    var changeCyclic = parameters.cyclic !== thread.cyclic;
    var changeAutoSage = parameters.autoSage !== thread.autoSage;

    if (!changeLock && !changePin && !changeCyclic && !changeAutoSage) {
      callback();

      return;
    }

    threads.updateOne({
      _id : thread._id
    }, {
      $set : {
        locked : parameters.lock,
        pinned : parameters.pin,
        cyclic : parameters.cyclic,
        autoSage : parameters.autoSage && !parameters.cyclic
      },
      $unset : miscOps.individualCaches
    }, function updatedThread(error) {

      if (!error) {
        // signal rebuild of thread
        process.send({
          board : thread.boardUri,
          thread : thread.threadId
        });

        if (changePin) {

          // signal rebuild of board pages
          postingOpsCommon.setThreadsPage(thread.boardUri, function(errr) {
            if (error) {
              console.log(error);
            } else {
              process.send({
                board : thread.boardUri
              });
            }
          });

        } else {
          // signal rebuild of page
          process.send({
            board : thread.boardUri,
            page : thread.page
          });
        }

      }

      callback(error);

    });
  };

};

// This function creates better video thumbs. Frame is selected from middle of video.
exports.setFfmpegThumb = function () {

  uploadHandler.videoThumbCommand = 'ffmpeg -ss {$starttime} -i {$path} -y -vframes 1 -vf scale=';

  uploadHandler.generateVideoThumb = function(identifier, file, tooSmall, callback) {

    var videoDurationCommand = 'ffprobe -v quiet -print_format json -show_format -show_streams ';

    var path = file.pathInDisk;

    exec(videoDurationCommand + path, function gotDuration(error, output) {

      if (error) {
        callback(error);
      } else {

        var duration = 2;

        if (JSON.parse(output).format) {
          duration = JSON.parse(output).format.duration;
        }

        var command = uploadHandler.videoThumbCommand.replace('{$path}', file.pathInDisk);

        command = command.replace('{$starttime}', Math.floor(duration/2));

        var extensionToUse = settings.thumbExtension || 'png';

        var thumbDestination = file.pathInDisk + '_.' + extensionToUse;

        if (tooSmall) {
          command += '-1:-1';
        } else if (file.width > file.height) {
          command += settings.thumbSize + ':-1';
        } else {
          command += '-1:' + settings.thumbSize;
        }

        command += ' ' + thumbDestination;

        file.thumbMime = logger.getMime(thumbDestination);
        file.thumbOnDisk = thumbDestination;
        file.thumbPath = '/.media/t_' + identifier;

        exec(command, {maxBuffer: Infinity}, function createdThumb(error) {
          if (error || !fs.existsSync(file.thumbOnDisk)) {

            var videoThumbCommandOriginal = 'ffmpeg -i {$path} -y -vframes 1 -vf scale=';

            var commandOriginal = videoThumbCommandOriginal.replace('{$path}', file.pathInDisk);

            var extensionToUse = settings.thumbExtension || 'png';

            var thumbDestination = file.pathInDisk + '_.' + extensionToUse;

            if (tooSmall) {
              commandOriginal += '-1:-1';
            } else if (file.width > file.height) {
              commandOriginal += settings.thumbSize + ':-1';
            } else {
              commandOriginal += '-1:' + settings.thumbSize;
            }

            commandOriginal += ' ' + thumbDestination;

            file.thumbMime = logger.getMime(thumbDestination);
            file.thumbOnDisk = thumbDestination;
            file.thumbPath = '/.media/t_' + identifier;

            exec(commandOriginal, function createdThumb(error) {
              if (error) {
                callback(error);
              } else {
                uploadHandler.transferThumbToGfs(identifier, file, callback);
              }
            });

          } else {
            uploadHandler.transferThumbToGfs(identifier, file, callback);
          }

        });

      }

    });

  };

}

// This function allows to hide ban messages.
exports.setNoDefaultBanMessage = function () {

  var originalUpdateThreadsBanMessage = modOpsSpecific.updateThreadsBanMessage;

  modOpsSpecific.updateThreadsBanMessage = function(pages, parentThreads, userData,
    parameters, callback, informedThreads, informedPosts, board) {

    if (parameters.hideBan) {
      modOpsSpecific.logBans(userData, board, informedPosts, informedThreads,
        parameters, callback);

      modOpsSpecific.reloadPages(pages, board, informedThreads, informedPosts,
        parentThreads);

    } else {

      originalUpdateThreadsBanMessage(pages, parentThreads, userData,
        parameters, callback, informedThreads, informedPosts, board);

    }

  };

};

// This function adds thread setting changes (like pin, close, ...) to the log.
exports.setThreadActionLog = function() {

  var originalSetThreadSettings = editOps.setThreadSettings;

  exports.logThreadSettings = function(userData, parameters, callback) {

    var logs = [];
    var pieces = {
      "startPiece": "User REDACTED edited a ",
      "secondPiece": "thread {$thread} on board /{$board}/. "
    }

    var closureDate = new Date();

    var logMessage = pieces.startPiece.replace('{$login}', userData.login);

    logMessage += pieces.secondPiece.replace('{$thread}', parameters.threadId)
      .replace('{$board}', parameters.boardUri);

    logs.push({
      user : userData.login,
      description : logMessage,
      time : closureDate,
      boardUri : parameters.boardUri,
      type : 'threadTransfer'
    });

    logOps.insertLog(logs, callback);

  }

  editOps.setThreadSettings = function(userData, parameters, language, callback) {

    originalSetThreadSettings(userData, parameters, language, callback);

    exports.logThreadSettings(userData, parameters, callback);

  }

}

// This function hides some global stats from frontpage.
exports.cleanStats = function() {

  domStatic.setGlobalStats = function(document, globalStats, language) {

    document = document.replace('__divStats_location__',
      templateHandler.getTemplates(language).index.removable.divStats);

    document = document.replace('__labelTotalPosts_inner__',
      globalStats.totalPosts || 0);
    /*document = document.replace('__labelTotalIps_inner__',
        globalStats.totalIps || 0);*/
    document = document.replace('__labelTotalPPH_inner__',
      globalStats.totalPPH || 0);
    /*document = document.replace('__labelTotalBoards_inner__',
        globalStats.totalBoards || 0);*/
    document = document.replace('__labelTotalFiles_inner__',
      globalStats.totalFiles || 0);
    document = document.replace('__labelTotalSize_inner__',
      globalStats.totalSize || 0);

    return document;

  };

  jsonBuilder.setGlobalStats = function(globalStats, object) {

    if (globalStats) {
      object.totalPosts = globalStats.totalPosts;
      /*object.totalIps = globalStats.totalIps;*/
      object.totalPPH = globalStats.totalPPH;
      /*object.totalBoards = globalStats.totalBoards;*/
      object.totalFiles = globalStats.totalFiles;
      object.totalSize = globalStats.totalSize;
    }

  };

  boardOps.countDocuments = function(userData, auth, parameters, req, res) {

    var page = parameters.page || 1;
    var queryBlock = boardOps.getQueryBlock(parameters);
    var pageSize = settingsHandler.getGeneralSettings().boardsPerPage;
    var json = parameters.json;

    boards.countDocuments(queryBlock, function(error, count) {
      if (error) {
        formOps.outputError(error, 500, res, req.language, json);
      } else {
        var pageCount = Math.ceil(count / pageSize);

        pageCount = pageCount || 1;

        var toSkip = (parameters.page - 1) * pageSize;

        // style exception, too simple
        boards.find(queryBlock, {
          projection : {
            _id : 0,
            boardName : 1,
            boardUri : 1,
            inactive : 1,
            specialSettings : 1,
            //uniqueIps : 0,  // KC-CHANGE
            tags : 1,
            boardDescription : 1,
            postsPerHour : 1,
            lastPostId : 1
          }
        }).sort(boardOps.getSortBlock(parameters)).skip(toSkip).limit(pageSize)
            .toArray(
                function(error, foundBoards) {
                  if (error) {
                    formOps
                        .outputError(error, 500, res, req.language, json, auth);
                  } else {

                    if (json) {
                      formOps.outputResponse('ok', jsonBuilder.boards(pageCount,
                          foundBoards), res, null, auth, null, true);
                    } else {
                      res.writeHead(200, miscOps.getHeader('text/html', auth));
                      res.end(domMiscPages.boards(parameters, foundBoards,
                          pageCount, req.language));
                    }

                  }
                });
        // style exception, too simple

      }
    });

  };

}

// This function allows to set custom names for team members in the thread moderation.
exports.allowNonAnonPosting = function() {

 domStatic.page = function(page, threads, pageCount, boardData, flagData,
    latestPosts, language, mod, userRole, callback) {

    var template = templateHandler.getTemplates(language).boardPage;

    // KC-Change: Add false cause of added method parameter
    var document = domCommon
      .setHeader(template, language, boardData, flagData, null, false);
    // KC-Change

    var boardUri = domCommon.clean(boardData.boardUri);

    var title = '/' + boardUri + '/ - ' + domCommon.clean(boardData.boardName);
    document = document.replace('__title__', title);

    document = document.replace('__linkManagement_href__',
        '/boardManagement.js?boardUri=' + boardUri).replace(
        '__linkModeration_href__', '/boardModeration.js?boardUri=' + boardUri)
        .replace('__linkLogs_href__', '/logs.js?boardUri=' + boardData.boardUri);

    var modLink = '/mod.js?boardUri=' + boardUri + '&page=' + page;
    document = document.replace('__linkMod_href__', modLink);

    document = domStatic.addPagesLinks(document, pageCount, page, mod,
        boardData.boardUri, template.removable);

    document = document.replace('__divThreads_children__', domStatic
        .getThreadListing(latestPosts, threads, mod, userRole, boardData,
            language));

    if (mod) {
      callback(null, document.replace('__divMod_location__',
          template.removable.divMod));
    } else {
      domStatic.writePage(boardUri, page, boardData, language, document.replace(
          '__divMod_location__', ''), callback);
    }

  };

  domStatic.setCatalogPosting = function(boardData, flagData, document, language,
    removable) {

    if (!settings.disableCatalogPosting) {
      document = document.replace('__postingForm_location__',
        removable.postingForm);
      // KC-Change: Added false cause of new method head.
      document = domCommon.setBoardPosting(boardData, flagData, document, null,
        language, removable, false);
      // KC-Change
    } else {
      document = document.replace('__postingForm_location__', '');
    }

    return document;

  };

  // New parameter: modding
  domCommon.setBoardPostingNameAndCaptcha = function(bData, document, thread,
    removable, modding) {

    var captchaMode = bData.captchaMode || 0;

    if ((captchaMode < 1 || (captchaMode < 2 && thread)) && !settings.forceCaptcha) {
      document = document.replace('__captchaDiv_location__', '');
    } else {
      document = document
        .replace('__captchaDiv_location__', removable.captchaDiv);
    }
    // KC-Change
    if (bData.settings.indexOf('forceAnonymity') > -1) {

      if(!modding) {
        document = document.replace('__divName_location__', '');
      } else {
        document = document.replace('__divName_location__', removable.divName);
      }
    } else {
      document = document.replace('__divName_location__', removable.divName);
    }
    // KC-Change
    return document;

  };

  // New parameter: modding
  domCommon.setBoardPosting = function(boardData, flagData, document, thread,
    language, removable, modding) {

    // KC-Change
    document = domCommon.setBoardPostingNameAndCaptcha(boardData, document, thread,
      removable, modding);
    // KC-Change

    var locationFlagMode = boardData.locationFlagMode || 0;

    if (locationFlagMode !== 1) {
      document = document.replace('__noFlagDiv_location__', '');
    } else {
      document = document.replace('__noFlagDiv_location__', removable.noFlagDiv);
    }

    if (boardData.settings.indexOf('textBoard') > -1) {
      document = document.replace('__divUpload_location__', '');
    } else {
      document = document.replace('__divUpload_location__', removable.divUpload);
      document = domCommon.setFileLimits(document, boardData, language);
    }

    document = document.replace('__boardIdentifier_value__', domCommon
      .clean(boardData.boardUri));
    document = document.replace('__labelMessageLength_inner__', settings.messageLength);

    return domCommon.setFlags(document, flagData, language, removable);

  };

  // New parameter: modding
  domCommon.setHeader = function(template, language, bData, flagData, thread, modding) {

    var boardUri = domCommon.clean(bData.boardUri);

    var title = '/' + boardUri + '/ - ' + domCommon.clean(bData.boardName);
    var document = template.template.replace('__labelName_inner__', title);

    var linkBanner = '/randomBanner.js?boardUri=' + boardUri;
    document = document.replace('__bannerImage_src__', linkBanner);

    // KC-Change
    document = domCommon.setBoardPosting(bData, flagData, document, thread,
      language, template.removable, modding);
    // KC-Change

    return domCommon.setBoardCustomization(document, bData, template.removable);

  };

  domStatic.setThreadCommonInfo = function(template, threadData, boardData,
    language, flagData, posts, modding, userRole, last) {

    // KC-Change
    var document = domCommon.setHeader(template, language, boardData, flagData,
      threadData, modding);
    // KC-Change

    document = domStatic.setThreadTitle(document, threadData);

    var linkModeration = '/mod.js?boardUri=' + domCommon.clean(boardData.boardUri);
    linkModeration += '&threadId=' + threadData.threadId;
    document = document.replace('__linkMod_href__', linkModeration);

    document = document.replace('__linkManagement_href__',
      '/boardManagement.js?boardUri=' + domCommon.clean(boardData.boardUri));

    var operations = [];

    document = document.replace('__divThreads_children__', domCommon.getThread(
      threadData, posts, true, modding, boardData, userRole, language,
      operations, last));

    if (!last) {
      domCommon.handleOps(operations);
    }

    document = document
      .replace('__threadIdentifier_value__', threadData.threadId);

    return domStatic.setModElements(modding, document, userRole,
      template.removable, threadData.archived);

  };

}

// This function increases the Locklimit to times 4 (instead of times 2). Also allows to set custom name for moderators as parameter.
exports.increaseThreadLockLimit = function() {

  postingOpsPost.getThread = function(req, parameters, userData, board, callback) {

    // KC-Change
    var autoLockLimit = postingOpsPost.getBumpLimit(board) * 8;
    // KC-Change

    threads.findOne({
      boardUri : parameters.boardUri,
      threadId : parameters.threadId
    }, {
      projection : {
        _id : 1,
        sfw : 1,
        salt : 1,
        page : 1,
        cyclic : 1,
        locked : 1,
        autoSage : 1,
        creation : 1,
        archived : 1,
        postCount : 1,
        latestPosts : 1
      }
    }, function gotThread(error, thread) {
      if (error) {
        callback(error);
      } else if (!thread) {
        callback(lang(req.language).errThreadNotFound);
      } else if (thread.locked || thread.archived) {
        callback(lang(req.language).errThreadLocked);
      } else if (thread.postCount >= autoLockLimit && !thread.cyclic) {
        callback(lang(req.language).errThreadAutoLocked);
      } else {
        // KC-Change FORCE ANONYMITY CHANGE
        if (board.settings.indexOf('forceAnonymity') > -1) {
          //if (board.settings.indexOf('forceAnonymity') > -1) {
          if (!userData || (userData && userData.globalRole > 2)) {
            parameters.name = null;
          }
        }
        // KC-Change
        miscOps.sanitizeStrings(parameters, postingOpsCommon.postingParameters);

        parameters.message = parameters.message || '';

        var wishesToSign = postingOpsCommon.doesUserWishesToSign(userData, parameters);

        // style exception, too simple
        postingOpsCommon.checkForTripcode(parameters, function setTripCode(error,
          parameters) {
          if (error) {
            callback(error);
          } else {
            postingOpsPost.applyFilters(req, parameters, userData, thread, board,
              wishesToSign, callback);
          }
        });
        // style exception, too simple

      }
    });

  };

};

exports.setMisc = function() {

  templateHandler.cellTests.push({
    template : 'catalogThumbCell',
    prebuiltFields : {
      linkThumb : ['inner', 'href', 'path', 'mime'],
    }
  });

  var oldDataAttributes = templateHandler.dataAttributes;

  templateHandler.dataAttributes = Object.assign(oldDataAttributes, {
    'path' : true
  });

  var oldSimpleAttributes = templateHandler.simpleAttributes;

  templateHandler.simpleAttributes = Object.assign(oldSimpleAttributes, {
    'for' : true,
    'id' : true
  });

  var mimes = logger.MIMETYPES;
  mimes['7zip'] = 'application/x-7z-compressed';
  logger.MIMETYPES = mimes;

  for (var i = 0; i < templateHandler.cellTests.length; i++) {
    var test = templateHandler.cellTests[i];

    if (test.template === 'uploadCell') {
      test.prebuiltFields.originalNameLink.push('title');
    } else if (test.template === 'opCell' || test.template === 'postCell') {
      delete test.prebuiltFields['contentOmissionIndicator'];
      delete test.prebuiltFields['linkFullText'];
      test.prebuiltFields['sage'] = [ 'inner' ];
      test.prebuiltFields['linkName'] = [ 'inner', 'class' ];
      test.prebuiltFields['expandLabel'] = [ 'for' ];
      test.prebuiltFields['collapseLabel'] = [ 'for' ];
      test.prebuiltFields['expandCheckBox'] = [ 'id' ];
    } else if (test.template === 'catalogCell') {
      test.prebuiltFields['catalogFlag'] = [ 'src', 'removal' ];
      delete test.prebuiltFields['linkThumb'];
      test.prebuiltFields.labelSubject.push('href');
      test.prebuiltFields['thumbGridTop'] = [ 'inner' ];
      test.prebuiltFields['thumbGridBottom'] = [ 'inner' ];
    } else {
      continue;
    }

  }

  var fileMimeFunction = function(perl, file, index, fields, files, callback) {

    var command = perl ? 'mimetype -bM' : 'file -b --mime-type';

    exec(command + ' ' + file.path, function(error, receivedMime) {

      if (error === '') {
        error = 'mimetype error';
      }

      if (error) {
        callback(error);
      } else {

        file.realMime = receivedMime.trim();

        if (!file.realMime.indexOf('text/')) {
          var actualRealMime = logger.getMime(file.originalFilename);

          if (actualRealMime !== 'application/octet-stream') {
            file.realMime = actualRealMime;
          }

        }

        var tryAgain = file.realMime === 'application/octet-stream' && !perl;

        if (!tryAgain) {
          formOps.validateMimes(fields, files, callback, ++index);
        } else {
          fileMimeFunction(true, file, index, fields, files, callback);
        }

      }
    });

  };

  formOps.validateMimes = function(fields, files, callback, index) {

    if (!validateMimes) {
      return formOps.stripExifs(fields, files, callback);
    }

    index = index || 0;

    var file = files.files[index];

    if (!file) {
      return formOps.applyRealMimes(fields, files, callback);
    }

    fileMimeFunction(false, file, index, fields, files, callback);

  };

  domPostingContent.addMessage = function(innerPage, modding, cell, posting, removable) {

    var markdown = posting.markdown;

    var arrayToUse = (markdown.match(/\n/g) || []);

    // KC-Change
    var id = posting.boardUri + '-' + posting.threadId + '-' + posting.postId;

    cell = cell.replace('__expandCheckBox_id__', id);
    cell = cell.replace('__expandLabel_for__', id);
    cell = cell.replace('__collapseLabel_for__', id);

    // KC-Change

    return cell.replace('__divMessage_inner__', domCommon.clean(domCommon
      .matchCodeTags(markdown)));

  };

  domCommon.formatDateToDisplay = function(d, noTime, language) {
    var day = domCommon.padDateField(d.getUTCDate());

    var month = domCommon.padDateField(d.getUTCMonth() + 1);

    var year = d.getUTCFullYear();

    var toReturn = lang(language).guiDateFormat.replace('{$month}', month)
      .replace('{$day}', day).replace('{$year}', year);

    if (noTime) {
      return toReturn;
    }

    var weekDay = lang(language).guiWeekDays[d.getUTCDay()];

    var hour = domCommon.padDateField(d.getUTCHours());

    var minute = domCommon.padDateField(d.getUTCMinutes());

    var second = domCommon.padDateField(d.getUTCSeconds());

    /* KC-CHANGE */
    return toReturn + ' ' + hour + ':' + minute + ':' + second;
    /* KC-CHANGE */
  };

  domPostingContent.setSharedSimpleElements = function(postingCell, posting, innerPage,
    modding, removable, language) {

    var name = domCommon.clean(posting.name);

    postingCell = postingCell.replace('__linkName_inner__', name);
    postingCell = postingCell.replace('__linkName_class__', ' noEmailName');

    if (posting.email && posting.email === 'sage') {
      postingCell = postingCell.replace('__sage_inner__', "SÄGE!");
    } else {
      postingCell = postingCell.replace('__sage_inner__', '');
    }

    postingCell = postingCell.replace('__labelCreated_inner__', domCommon
      .formatDateToDisplay(posting.creation, null, language));

    postingCell = domPostingContent.addMessage(innerPage, modding, postingCell, posting, removable);

    return postingCell;

  };

  domPostingContent.setAllSharedPostingElements = function(postingCell, posting, removable,
    language, modding, innerPage, userRole, boardData, preview) {

    postingCell = domPostingContent.setPostingModdingElements(modding || preview, posting,
      postingCell, preview ? boardData[posting.boardUri] : boardData, userRole,
      removable);

    postingCell = domPostingContent.setSharedHideableElements(posting, removable,
      postingCell, preview, language);

    postingCell = domPostingContent.setPostingLinks(postingCell, posting, innerPage,
      modding, removable);

    postingCell = domPostingContent.setPostingComplexElements(posting, postingCell,
      removable);

    postingCell = domPostingContent.setSharedSimpleElements(postingCell, posting,
      innerPage, modding, removable, language);

    if (!posting.files || !posting.files.length) {
      return postingCell.replace('__panelUploads_location__', '');
    }

    postingCell = postingCell.replace('__panelUploads_location__',
      removable.panelUploads);

    /* KC-CHANGE */
    var multipleUploadCondition = posting.files.length > (posting.postId && posting.threadId ? 1 : 2);
    postingCell = postingCell.replace(' __panelUploads_class__',
      multipleUploadCondition ? ' multipleUploads' : '');
    /* KC-CHANGE */

    return postingCell.replace('__panelUploads_children__', domCommon.setUploadCell(
      posting, modding, language));

  }

  var getThumbnailDimensions = function(width, height, thumb, divisor=1) {
    var thumbSize = settings.thumbSize;

    if (width == null || height == null) {
      var icons = ['/audioGenericThumb.png', '/genericThumb.png'];
      if (icons.indexOf(thumb) >= 0) {
        width = 60;
        height = 65;
      } else {
        width = 200;
        height = 200;
      }
    } else if (width > thumbSize || height > thumbSize) {
      var ratio = width / height;

      if (ratio > 1) {
        width = thumbSize;
        height = thumbSize / ratio;
      } else {
        width = thumbSize * ratio;
        height = thumbSize;
      }
    }

    return [Math.trunc(width/divisor), Math.trunc(height/divisor)];
  };

  var truncateFilename = function(filename, maxWidth, dots) {
    if (filename.length > maxWidth) {
      var truncateAtEnd = true;
      var parts = filename.split('.');
      var numParts = parts.length;
      var extension = parts.pop();
      if (numParts >= 2) {
        filename = filename.substring(
          0,
          maxWidth - dots.length - extension.length - 1
        ) + dots + '.' + extension;
        truncateAtEnd = filename.length > maxWidth;
      }
      if (truncateAtEnd) {
        filename = filename.substring(
          0,
          maxWidth - dots.length
        ) + dots;
      }
    }
    return filename;
  };

  domCommon.setUploadLinks = function(cell, file) {

    cell = cell.replace('__imgLink_href__', file.path);
    cell = cell.replace('__imgLink_mime__', file.mime);

    if (file.width) {
      cell = cell.replace('__imgLink_width__', file.width);
      cell = cell.replace('__imgLink_height__', file.height);
    } else {
      cell = cell.replace('data-filewidth="__imgLink_width__"', '');
      cell = cell.replace('data-fileheight="__imgLink_height__"', '');
    }

    cell = cell.replace('__nameLink_href__', file.path);

    var dimensions = getThumbnailDimensions(
      file.width,
      file.height,
      file.thumb
    );
    var img = '<img alt="" width=' + dimensions[0] + ' height=' + dimensions[1] + ' src="' + file.thumb + '">';

    cell = cell.replace('__imgLink_children__', img);

    var originalName = domCommon.clean(file.originalName);

    var displayedName = truncateFilename(originalName, 25, '[...]');

    var downloadPath = file.path + '/dl/' + originalName;

    cell = cell.replace('__originalNameLink_inner__', displayedName);
    cell = cell.replace('__originalNameLink_download__', originalName);
    cell = cell.replace('__originalNameLink_title__', originalName);
    cell = cell.replace('__originalNameLink_href__', downloadPath);

    return cell;

  };

  exports.setCatalogCellThumbs = function(thread, language, cell, href, linkThumb) {
    domCommon.clean(thread);

    const catalogThumbCell = templateHandler.getTemplates(language).catalogThumbCell.template;

    var hasFiles = thread.files && thread.files.length;

    var thumbGridTopInner = '';
    var thumbGridBottomInner = '';

    var max = Math.min(thread.files ? thread.files.length : 0, 4);

    for (var i = 0; i < max; i++) {

      var linkThumb = catalogThumbCell;

      var divisor = i > 0 ? 3 : 1;

      var dimensions = getThumbnailDimensions(
        thread.files[i].width,
        thread.files[i].height,
        domCommon.clean(thread.files[i].thumb),
        divisor
      );

      linkThumb = linkThumb.replace('__linkThumb_mime__', thread.files[i].mime);
      linkThumb = linkThumb.replace('__linkThumb_path__', thread.files[i].path);
      linkThumb = linkThumb.replace('__linkThumb_href__', href);

      var img = '<img loading="lazy" alt="" width=' + dimensions[0] + ' height=' + dimensions[1] + ' src="' + domCommon.clean(thread.files[i].thumb) + '">';

      linkThumb = linkThumb.replace('__linkThumb_inner__', img);

      if (i === 0) {

        thumbGridTopInner += linkThumb;

      } else {

        thumbGridBottomInner += linkThumb;

      }

    }

    cell = cell.replace('__thumbGridTop_inner__', thumbGridTopInner);
    cell = cell.replace('__thumbGridBottom_inner__', thumbGridBottomInner);

    return cell;

  };

  domCommon.setPostingFlag = function(posting, postingCell, removable) {

    if (posting.flag) {

      postingCell = postingCell
        .replace('__imgFlag_location__', removable.imgFlag);

      postingCell = postingCell.replace('__imgFlag_src__', posting.flag);
      postingCell = postingCell.replace('__imgFlag_title__', posting.flagName);

      if (posting.flagCode) {

        var flagClass = ' flag' + posting.flagCode;

        postingCell = postingCell.replace(' __imgFlag_class__', flagClass + '" alt="' + posting.flagCode.slice(1));
      } else {
        postingCell = postingCell.replace(' __imgFlag_class__', '" alt="??');
      }

    } else {
      postingCell = postingCell.replace('__imgFlag_location__', '');
    }

    return postingCell;

  };

  var setCatalogCellFlag = function (cell, thread, removable) {

    if (thread.flag) {
      cell = cell.replace('__catalogFlag_location__', removable.catalogFlag);
      cell = cell.replace('__catalogFlag_src__', thread.flag);
    } else {
      cell = cell.replace('__catalogFlag_location__', '');
    }

    return cell;

  }

  domStatic.getCatalogCell = function(boardUri, document, thread, language) {

    var cell = templateHandler.getTemplates(language).catalogCell.template;
    var href = '/' + thread.boardUri + '/res/';
    href += thread.threadId + '.html';

    var hasFiles = thread.files && thread.files.length;

    cell = exports.setCatalogCellThumbs(thread, language, cell, href);

    cell = cell.replace('__labelReplies_inner__', thread.postCount || 0);
    cell = cell.replace('__labelImages_inner__', thread.fileCount || 0);
    cell = cell.replace('__labelPage_inner__', thread.page);

    var removable = templateHandler.getTemplates(language).catalogCell.removable;

    if (thread.subject) {

      cell = cell.replace('__labelSubject_location__', removable.labelSubject);
      cell = cell.replace('__labelSubject_inner__', thread.subject);

    } else {
      cell = cell.replace('__labelSubject_location__', removable.labelSubject);
      cell = cell.replace('__labelSubject_inner__', '#' + thread.threadId);
    }

    cell = cell.replace('__labelSubject_href__', href);

    cell = setCatalogCellFlag(cell, thread, removable);

    cell = domStatic.setCatalogCellIndicators(thread, cell, removable);

    cell = cell.replace('__divMessage_inner__', domCommon.clean(domCommon
      .matchCodeTags(thread.markdown)));

    return '<div class="catalogCell">' + cell + '</div>';

  };

  jsonBuilder.catalog = function(boardUri, threads, callback) {

    var threadsArray = [];

    for (var i = 0; i < threads.length; i++) {
      var thread = threads[i];

      var threadToPush = {
        message : thread.message,
        markdown : thread.markdown,
        threadId : thread.threadId,
        postCount : thread.postCount ? thread.postCount : 0,
        fileCount : thread.fileCount ? thread.fileCount : 0,
        page : thread.page,
        subject : thread.subject,
        locked : !!thread.locked,
        pinned : !!thread.pinned,
        cyclic : !!thread.cyclic,
        autoSage : !!thread.autoSage,
        lastBump : thread.lastBump,
        creation: thread.creation,
        flag: thread.flag,
      };

      /* KC-CHANGE */

      threadToPush.files = [];

      if (thread.files && thread.files.length > 0) {

        // compatibility with third party clients
        threadToPush.thumb = thread.files[0].thumb;

        for (var j = 0; j < thread.files.length; j++) {

          var file = {};

          file.thumb = thread.files[j].thumb;
          file.path = thread.files[j].path;
          file.mime = thread.files[j].mime;
          file.width = thread.files[j].width;
          file.height = thread.files[j].height;

          threadToPush.files.push(file);

        }

      }

      /* KC-CHANGE */

      threadsArray.push(threadToPush);
    }

    var path = '/' + boardUri + '/catalog.json';

    cacheHandler.writeData(JSON.stringify(threadsArray), path,
      'application/json', {
        boardUri : boardUri,
        type : 'catalog'
      }, callback);

  };

  domStatic.placeNextLink = function(modPrefix, boardUri, modding, currentPage,
    pageCount, document, removable) {

    if (pageCount === currentPage) {
      document = document.replace('__linkNext_location__', '');
    } else {
      document = document.replace('__linkNext_location__', removable.linkNext);

      if (modding) {
        var href = modPrefix + (currentPage + 1);
      } else {
        href = (currentPage + 1) + '.html';
      }

      document = document.replace('__linkNext_href__', href);
    }

    return domStatic.addPageListing(pageCount, modding, boardUri, document, currentPage);

  };

  domStatic.addPageListing = function(pageCount, modding, boardUri, document, currentPage) {

    var children = '';

    for (var i = 0; i < pageCount; i++) {

      if (i + 1 !== currentPage) {

        if (!modding) {
          var pageLink = i ? (i + 1) + '.html' : 'index.html';
        } else {
          pageLink = '/mod.js?boardUri=' + boardUri + '&page=' + (i + 1);
        }

        children += '<a href="' + pageLink + '">' + (i + 1) + '</a>';

      } else {

        children += '<span class="brackets">' + (i + 1) + '</span>';

      }
    }

    return document.replace('__divPages_children__', children);

  };

  // Ignore report closure
  generatorGlobal.log = function(date, callback, logData) {

    if (!logData) {

      if (!date) {

        if (verbose) {
          console.log('Could not build log page, no data.');
        }

        callback();
        return;
      }

      aggregatedLogs.findOne({
        date : date
      }, function gotLogData(error, data) {

        if (error) {
          callback(error);
        } else if (!data) {

          if (verbose) {
            console.log('Could not find logs for ' + date);
          }

          callback();
        } else {
          generatorGlobal.log(null, callback, data);
        }

      });

      return;
    }

    if (verbose) {
      console.log('Building log page for ' + logData.date);
    }

    var toFind = [];

    for (var i = 0; i < logData.logs.length; i++) {
      toFind.push(logData.logs[i]);
    }

    logs.find({
      _id : {
        $in : toFind
      },
      /* KC-CHANGE */
      type: {
        $ne : 'reportClosure'
      }
      /* KC-CHANGE */
    }, {
      projection : {
        type : 1,
        user : 1,
        cache : 1,
        alternativeCaches : 1,
        time : 1,
        boardUri : 1,
        description : 1,
        global : 1
      }
    }).sort({
      time : 1
    }).toArray(function gotLogs(error, foundLogs) {

      if (error) {
        callback(error);
      } else {
        generatorGlobal.createLogPage(logData, foundLogs, callback);
      }

    });

  };

  postingOpsCommon.addPostToLatestPosts = function(posting, callback) {

    /* KC-CHANGE */
    var addDots = posting.message.length > 128;
    var previewText = miscOps.cleanHTML(posting.message.substring(0, 128));
    if (addDots) {
      previewText = previewText + '...';
    }
    /* KC-CHANGE */

    latestPosts.insertOne({
      boardUri : posting.boardUri,
      threadId : posting.threadId,
      creation : posting.creation,
      postId : posting.postId,
      /* KC-CHANGE */
      previewText : previewText
      /* KC-CHANGE */
    }, function addedPost(error) {

      if (error) {
        callback(error);
      } else {

        // style exception, too simple
        latestPosts.countDocuments({}, function counted(error, count) {
          if (error) {
            callback(error);
          } else if (count > maxGlobalLatestPosts) {
            postingOpsCommon.cleanGlobalLatestPosts(callback);
          } else {
            process.send({
              frontPage : true
            });

            callback();
          }
        });
        // style exception, too simple

      }

    });

  };

  // 2.5 (slightly modified, but it should be ok with future updates)
  modOpsGeneral.getRangeBans = function(userData, parameters, language, callback) {

    /* KC-CHANGE */
    var globalStaff = userData.globalRole < miscOps.getMaxStaffRole();
    var clearIpUnlocked = userData.globalRole <= minClearIpRole;
    /* KC-CHANGE */

    if (parameters.boardUri) {

      parameters.boardUri = parameters.boardUri.toString();

      boards.findOne({
        boardUri : parameters.boardUri
      }, function gotBoard(error, board) {
        if (error) {
          callback(error);
        } else if (!board) {
          callback(lang(language).errBoardNotFound);
        } else if (!modOpsCommon.isInBoardStaff(userData, board, 2)) {
          callback(lang(language).errDeniedBoardRangeBanManagement);
        } else {
          modOpsGeneral.readRangeBans(parameters, callback, board);
        }
      });
    /* KC-CHANGE */
    } else if (!clearIpUnlocked) {
      if (globalStaff) {
        modOpsGeneral.readRangeBans(parameters, callback, {ipSalt: globalSalt});
      } else {
        callback(lang(language).errDeniedGlobalRangeBanManagement);
      }
    /* KC-CHANGE */
    } else {
      modOpsGeneral.readRangeBans(parameters, callback);
    }

  };

  // 2.5 (slightly modified, but it should be ok with future updates)
  modOpsVersatile.liftBan = function(userData, parameters, language, callback) {

    var globalStaff = userData.globalRole < miscOps.getMaxStaffRole();

    /* KC-CHANGE */
    // removed, including one else if down below
    /* KC-CHANGE */

    try {
      parameters.banId = new ObjectID(parameters.banId);
    } catch (error) {
      callback();
      return;
    }

    bans.findOne({
      _id : parameters.banId
    },
      function gotBan(error, ban) {
        if (error) {
          callback(error);
        } else if (!ban) {
          callback();
        } else if (ban.boardUri) {
          modOpsVersatile.checkForBoardBanLiftPermission(ban, userData, language,
            callback);
        } else if (!globalStaff) {
          callback(lang(language).errDeniedGlobalBanManagement);
        } else {
          modOpsVersatile.removeBan(ban, userData, callback);
        }
      });

  };

  // 2.5
  latestOps.fetchPostInfo = function(matchBlock, globalStaff, parameters, cb) {

    var query = {
      boardUri : parameters.boardUri
    };

    var collectionToUse;

    if (parameters.threadId) {
      collectionToUse = threads;
      query.threadId = +parameters.threadId;
    } else {
      collectionToUse = posts;
      query.postId = +parameters.postId;
    }

    collectionToUse.findOne(query, {
      projection : {
        ip : 1,
        bypassId : 1,
        boardUri : 1
      }
    }, function gotPosting(error, posting) {

      if (error) {
        return cb(error);
      }

      if (posting && (posting.ip || posting.bypassId)) {

        var orList = [];

        matchBlock.$or = orList;

        if (posting.ip) {
          orList.push({
            ip : posting.ip
          });
        }

        if (posting.bypassId) {
          orList.push({
            bypassId : posting.bypassId
          });
        }

        if (!globalStaff && !unlockHistory) {
          matchBlock.boardUri = posting.boardUri;
        }
      }

      cb(null, matchBlock);

    });

  };

  // 2.5
  latestOps.canSearchPerBan = function(ban, userData) {

    if (!ban || (!ban.ip && !ban.bypassId)) {
      return false;
    }

    if (userData.globalRole <= miscOps.getMaxStaffRole()) {
      return true;
    }

    if (!ban.boardUri) {
      return false;
    }

    var allowedBoards = userData.ownedBoards || [];

    allowedBoards = allowedBoards.concat(userData.volunteeredBoards || []);

    return allowedBoards.indexOf(ban.boardUri) >= 0;

  };

  // 2.5
  latestOps.setIpAndPostMatch = function(parameters, matchBlock, userData, cb) {

    if (parameters.ip && userData.globalRole <= minClearIpRole) {
      matchBlock.ip = logger.convertIpToArray(parameters.ip);
      delete parameters.boardUri;
    } else if (latestOps.canSearchPerPost(parameters, userData)) {
      return latestOps.fetchPostInfo(matchBlock, userData.globalRole <= miscOps
        .getMaxStaffRole(), parameters, cb);
    }

    cb(null, matchBlock);

  };

  latestOps.getMatchBlock = function(parameters, userData, callback) {

    var matchBlock = parameters.date ? {
      creation : {
        $gt : parameters.date
      }
    } : {};

    var boardsToShow = latestOps.getBoardsToShow(parameters, userData);

    if (boardsToShow) {
      matchBlock.boardUri = {
        $in : boardsToShow
      };
    }

    if (!parameters.banId) {
      return latestOps
        .setIpAndPostMatch(parameters, matchBlock, userData, callback);
    }

    try {
      var banId = new ObjectID(parameters.banId);
    } catch (error) {
      return callback(null, matchBlock);
    }

    bans.findOne({
      _id : banId
    }, function(error, ban) {

      if (error) {
        return callback(error);
      }

      if (latestOps.canSearchPerBan(ban, userData)) {

        var globalStaff = userData.globalRole <= miscOps.getMaxStaffRole();

        if (!globalStaff && !unlockHistory) {
          matchBlock.boardUri = ban.boardUri;
        }

        var orList = [];

        matchBlock.$or = orList;

        if (ban.ip) {
          orList.push({
            ip : ban.ip
          });
        }

        if (ban.bypassId) {
          orList.push({
            bypassId : ban.bypassId
          });
        }

      }

      callback(null, matchBlock);

    });

  };

};

exports.setThreadIds = function() {

  postingOpsPost.getNewPost = function(req, parameters, userData, postId, thread, board,
      wishesToSign) {

    var ip = logger.ip(req);

    var hideId = board.settings.indexOf('disableIds') > -1;

    /* KC-CHANGE */

    var tempId = ip;

    if (!tempId) {
      tempId = req.bypassId;
    }

    var id = hideId ? null : postingOpsCommon
        .createId(thread.salt, parameters.boardUri, tempId);

    /* KC-CHANGE */

    var nameToUse = parameters.name || board.anonymousName;
    nameToUse = nameToUse || postingOpsCommon.defaultAnonymousName;

    var postToAdd = {
      boardUri : parameters.boardUri,
      postId : postId,
      hash : parameters.hash,
      markdown : parameters.markdown,
      ip : ip,
      asn : req.asn,
      threadId : parameters.threadId,
      signedRole : postingOpsCommon.getSignedRole(userData, wishesToSign, board),
      creation : parameters.creationDate,
      subject : parameters.subject,
      name : nameToUse,
      id : id,
      bypassId : req.bypassId,
      message : parameters.message,
      email : parameters.email
    };

    if (parameters.flag) {
      postToAdd.flagName = parameters.flagName;
      postToAdd.flagCode = parameters.flagCode;
      postToAdd.flag = parameters.flag;
    }

    if (parameters.password) {
      postToAdd.password = parameters.password;
    }

    return postToAdd;

  };

  postingOpsThread.getNewThread = function(req, userData, parameters, board, threadId,
    wishesToSign) {

    var salt = crypto.createHash('sha256').update(
        threadId + parameters.toString() + Math.random() + new Date()).digest(
        'hex');

    var ip = logger.ip(req);

    /* KC-CHANGE */

    var tempId = ip;

    if (!tempId) {
      tempId = req.bypassId;
    }

    var id = board.settings.indexOf('disableIds') > -1 ? null : postingOpsCommon.createId(
        salt, parameters.boardUri, tempId);

    /* KC-CHANGE */

    var nameToUse = parameters.name || board.anonymousName;
    nameToUse = nameToUse || postingOpsCommon.defaultAnonymousName;

    var threadToAdd = {
      boardUri : parameters.boardUri,
      threadId : threadId,
      salt : salt,
      hash : parameters.hash,
      ip : ip,
      bypassId : req.bypassId,
      id : id,
      asn : req.asn,
      markdown : parameters.markdown,
      lastBump : new Date(),
      creation : parameters.creationDate,
      subject : parameters.subject,
      pinned : false,
      locked : false,
      signedRole : postingOpsCommon.getSignedRole(userData, wishesToSign, board),
      name : nameToUse,
      message : parameters.message,
      email : parameters.email
    };

    if (board.specialSettings && board.specialSettings.indexOf('sfw') > -1) {
      threadToAdd.sfw = true;
    }

    if (parameters.flag) {
      threadToAdd.flagName = parameters.flagName;
      threadToAdd.flag = parameters.flag;
      threadToAdd.flagCode = parameters.flagCode;
    }

    if (parameters.password) {
      threadToAdd.password = parameters.password;
    }

    return threadToAdd;

  };

}

exports.setKeepFlags = function() {

  transferOps.getPostsOps = function(newPostIdRelation, newBoard, foundPosts,
      updateOps, newThreadId, originalThread) {

    var messageReplaceFunction = function(match, id) {
      return '>>' + newPostIdRelation[id];
    };

    var markdownReplaceFunction = function(match, id) {

      var toRet = '<a class="quoteLink" href="/' + newBoard.boardUri + '/res/';
      toRet += newThreadId + '.html#' + newPostIdRelation[id] + '">>>';
      return toRet + newPostIdRelation[id] + '</a>';

    };

    for (var i = 0; i < foundPosts.length; i++) {

      var post = foundPosts[i];

      var newPostId = newThreadId + 1 + i;

      for ( var key in newPostIdRelation) {

        var matchRegex = '<a class="quoteLink" href="\/';
        matchRegex += originalThread.boardUri;
        matchRegex += '\/res\/' + originalThread.threadId + '\.html#(' + key;
        matchRegex += ')">>>' + key + '<\/a>';

        post.message = post.message.replace(new RegExp('>>(' + key + ')(?!.*\d)',
            'g'), messageReplaceFunction);

        post.markdown = post.markdown.replace(new RegExp(matchRegex, 'g'),
            markdownReplaceFunction);

      }

      newPostIdRelation[post.postId] = newPostId;

      updateOps.push({
        updateOne : {
          filter : {
            _id : post._id
          },
          update : {
            $set : {
              boardUri : newBoard.boardUri,
              threadId : newThreadId,
              message : post.message,
              markdown : post.markdown,
              postId : newPostId,
              files : transferOps.getAdjustedFiles(newBoard, originalThread,
                  post.files)
            },
            $unset : {
              /* KC-Change - Removal of flag and flagName */
              innerCache : 1,
              outerCache : 1,
              alternativeCaches : 1,
              previewCache : 1,
              clearCache : 1,
              hashedCache : 1,
              previewHashedCache : 1,
              outerHashedCache : 1,
              outerClearCache : 1
            }
          }
        }
      });

    }

  };

  transferOps.updateThread = function(userData, parameters, originalThread, newBoard,
      cb) {

    var lastId = newBoard.lastPostId;

    var newThreadId = lastId - originalThread.postCount;

    var newLatestPosts = [];

    var startingPoint = lastId - originalThread.latestPosts.length + 1;

    for (var i = startingPoint; i <= lastId; i++) {
      newLatestPosts.push(i);
    }

    threads.updateOne({
      _id : originalThread._id
    }, {
      $set : {
        boardUri : parameters.boardUriDestination,
        threadId : newThreadId,
        latestPosts : newLatestPosts,
        files : transferOps.getAdjustedFiles(newBoard, originalThread,
            originalThread.files)
      },
      $unset : {
        /* KC-Change - Removal of flag and flagName */
        innerCache : 1,
        outerCache : 1,
        previewCache : 1,
        alternativeCaches : 1,
        clearCache : 1,
        hashedCache : 1,
        previewHashedCache : 1,
        outerHashedCache : 1,
        outerClearCache : 1
      }
    }, function updatedThread(error) {
      if (error) {
        cb(error);
      } else {

        // style exception, too simple
        transferOps.updateThreadCount(newBoard, userData, originalThread,
            newThreadId, function updatedThreadCount(error) {
              if (error) {
                transferOps.revertThread(originalThread, error, cb);
              } else {
                cb(null, newThreadId);
              }
            });
        // style exception, too simple

      }

    });

  };

}

// Add into dont-reload a file called "dnswl" with whitelisted IPs for
// DNSBLs per line.
exports.setDnsblWhitelist = function() {

  exports.dnswl = fs.readFileSync(__dirname + '/dont-reload/dnswl', 'utf8')
          .split('\n');

  spamOps.checkDnsbl = function(ip, callback, index) {

    if (!settings.dnsbl) {
      return spamOps.checkIp(ip, callback);
    }

    index = index || 0;

    if (index >= settings.dnsbl.length) {
      return spamOps.checkIp(ip, callback);
    }

    /* KC-CHANGE */
    var joined = "";
    for (var j = 0; j < exports.dnswl.length; j++) {
      joined = ip.join('.');
      if (exports.dnswl[j].trim() == joined) {
        //return spamOps.checkIp(ip, callback);
        return callback();
      }
    }

    /* KC-CHANGE */

    logger.runDNSBL(ip, settings.dnsbl[index], function(error, matched) {

      if (error || matched) {
        callback(error, matched);
      } else {
        spamOps.checkDnsbl(ip, callback, ++index);
      }

    });

  };

}

// This function creates thumbnails for animated WebPs.
exports.setAnimatedWebP = function() {

  var apngThreshold = 25 * 1024;

  uploadHandler.getImageBounds = function(file, callback) {

    var path = file.pathInDisk;

    if (native && file.mime !== 'image/webp') {
      return native.getImageBounds(path, callback);
    }

    exec('identify ' + path, function(error, results) {
      if (error) {
        callback(error);
      } else {
        var lines = results.split('\n');

        var maxHeight = 0;
        var maxWidth = 0;

        for (var i = 0; i < lines.length; i++) {
          var dimensions = lines[i].match(/\s(\d+)x(\d+)\s/);

          if (dimensions) {

            var currentWidth = +dimensions[1];
            var currentHeight = +dimensions[2];

            maxWidth = currentWidth > maxWidth ? currentWidth : maxWidth;
            maxHeight = currentHeight > maxHeight ? currentHeight : maxHeight;

          }
        }

        callback(null, maxWidth, maxHeight);
      }
    });

  };

  uploadHandler.generateThumb = function(identifier, file, callback) {

    var tooSmall = file.height <= settings.thumbSize && file.width <= settings.thumbSize;

    var gifCondition = settings.thumbExtension || tooSmall;

    var apngCondition = gifCondition && file.size > apngThreshold;
    apngCondition = (apngCondition && file.mime === 'image/png') || (apngCondition && file.mime === 'image/webp');

    var imageCondition = file.mime.indexOf('image/') > -1;
    imageCondition = imageCondition && !tooSmall && file.mime !== 'image/svg+xml';

    if (file.mime === 'image/gif' && gifCondition) {
      uploadHandler.generateGifThumb(identifier, file, callback);
    } else if (imageCondition || apngCondition) {
      uploadHandler.generateImageThumb(identifier, file, callback);
    } else if (uploadHandler.videoMimes.indexOf(file.mime) > -1 && settings.mediaThumb) {
      uploadHandler.generateVideoThumb(identifier, file, tooSmall, callback);
    } else if (uploadHandler.thumbAudioMimes.indexOf(file.mime) > -1 && settings.mediaThumb) {
      uploadHandler.generateAudioThumb(identifier, file, callback);
    } else {
      uploadHandler.decideOnDefaultThumb(file, identifier, callback);
    }

  };

  uploadHandler.generateImageThumb = function(identifier, file, callback) {

    var thumbDestination = file.pathInDisk + '_t';

    var command;

    var thumbCb = function(error) {
      if (error) {
        callback(error);
      } else {

        file.thumbOnDisk = thumbDestination;
        file.thumbMime = settings.thumbExtension ? logger.getMime(thumbDestination)
            : file.mime;
        file.thumbPath = '/.media/t_' + identifier;

        uploadHandler.transferThumbToGfs(identifier, file, callback);
      }
    };

    if (file.mime !== 'image/gif' || !settings.ffmpegGifs) {

      if (settings.thumbExtension) {
        thumbDestination += '.' + settings.thumbExtension;
      }

      if (native && file.mime !== 'image/webp') {
        return native.imageThumb(file.pathInDisk, thumbDestination, settings.thumbSize,
            thumbCb);

      }

      command = 'convert ' + file.pathInDisk + ' -coalesce -resize ';
      command += settings.thumbSize + 'x' + settings.thumbSize + ' ' + thumbDestination;
    } else {

      thumbDestination += '.gif';
      command = uploadHandler.getFfmpegGifCommand(file, thumbDestination);
    }

    exec(command, function(error) {
      if (error) {
        if (String(error).includes("Decoding of an animated WebP file is not supported") ||
        String(error).includes("data encoding scheme is not supported")) {
          var temporaryFrame = file.pathInDisk + '_t' + '.' + 'webp'
          var commandWebP = 'webpmux -get frame 1 ' + file.pathInDisk + ' -o ' + temporaryFrame;

          exec(commandWebP, function(error) {
            if (error) {
              callback(error);
            } else {
              var commandNew = 'convert ' + temporaryFrame + ' -coalesce -resize ';
              commandNew += settings.thumbSize + 'x' + settings.thumbSize + ' ' + thumbDestination;

              exec(commandNew, thumbCb);
            }
          });

        } else {
          callback(error);
        }
      } else {
        thumbCb();
      }
    });
  };

};

// Add into dont-reload a file called "dnswl" with whitelisted IPs for
// DNSBLs per line.
exports.setNewVideoMime = function() {

  uploadHandler.videoMimes = [ 'video/webm', 'video/mp4', 'video/ogg', 'video/x-m4v' ];

}

exports.init = function() {

  exports.globalSalt();

  exports.setAutosage();

  exports.setMisc();

  exports.setFfmpegThumb();

  exports.setNoDefaultBanMessage();

  exports.setThreadActionLog();

  exports.cleanStats();

  exports.increaseThreadLockLimit();

  exports.allowNonAnonPosting();

  exports.setThreadIds();

  exports.setKeepFlags();

  exports.setDnsblWhitelist();

  //exports.setAnimatedWebP();

  exports.setNewVideoMime();

  torFlags.init();

  extensionMime.init();

  customMarkdown.init();

};

exports.formRequest = function(req, res) {

  var args = url.parse(req.url, true).query;

  if (args.feature === 'map') {
    map.output(res);
  } else {
    formOps.outputError('Unknown feature', 500, res, req.language);
  }

};


code:
'use strict';

var db = require('../../db');
var coordinates = db.conn().collection('coordinates');
var miscOps = require('../../engine/miscOps');
var formOps = require('../../engine/formOps');
var logger = require('../../logger');

exports.output = function(res) {

  var now = new Date();

  now.setUTCHours(now.getUTCHours() - 1);

  coordinates.find({
    time : {
      $gte : now
    }
  }, {
    projection : {
      _id : 0,
      c : 1,
      lat : 1,
      lon : 1
    }
  }).toArray(function(error, data) {

    if (error) {
      formOps.outputError(error, 500, res, null, true);
    } else {

      now.setUTCHours(now.getUTCHours() - 23);

      coordinates.find({
        time : {
          $gte : now
        }
      }, {
        projection : {
          _id : 0,
          c : 1,
          lat : 1,
          lon : 1
        }
      }).toArray(function(error, dayData) {

        if (error) {
          formOps.outputError(error, 500, res, null, true);
        } else {

          res.writeHead(200, miscOps.getHeader('application/json'));

          res.end(JSON.stringify({
            H : data,
            D : dayData
          }));

        }

      });

    }

  });

};


lynxchanaddon-kc

description: **KC-Addon**

test: echo "Error: no test specified" && exit 1

type: git

license: ISC


code:
'use strict';

var crypto = require('crypto');
var postingOps = require('../../engine/postingOps');
var db = require('../../db');
var logger = require('../../logger');
var coordinates = db.conn().collection('coordinates');
var threads = db.threads();
var posts = db.posts();
var domCommon = require('../../engine/domManipulator').common;
var postingOpsCommon = postingOps.common;
var postPosting = postingOps.post;
var threadPosting = postingOps.thread;
var locationOps = require('../../engine/locationOps');
var compiledCoordinates = __dirname + '/../../locationData/compiledCoordinates';
var compiledLocations = __dirname + '/../../locationData/compiledLocations';
var locationLineSize = 60;

exports.ipToInt = function(ip) {

  var toReturn = ip[0] << 24 >>> 0;
  toReturn += ip[1] << 16 >>> 0;
  toReturn += ip[2] << 8 >>> 0;
  toReturn += ip[3];

  return toReturn;

};

exports.recordCoordinate = function(req, board, userData) {

  if (board.boardUri !== 'int')
    return;

  var ip = logger.ip(req);

  if (req.bypassId) {

    var bypassId = (userData && userData.login) || req.bypassId;

    var toInsert;

    if (req.isTor) {

      toInsert = {
        time : new Date(),
        lat : 48.9036,
        lon : -125.2932,
        c : 'onion',
        ip : bypassId,
        board : board.boardUri
      };

    } else {

      toInsert = {
        time : new Date(),
        lat : 30.3835697,
        lon : -91.0227705,
        c : 'proxy',
        ip : bypassId,
        board : board.boardUri
      };

    }

    coordinates.insertOne(toInsert, function inserted(error) {

      if (error && error.code !== 11000) {
        console.log(error);
      }

    });

    return;
  }

  var ipArray = logger.convertIpToArray(logger.getRawIp(req));

  if (locationOps.isLocal(ipArray)) {
    return;
  }

  var convertedIp = exports.ipToInt(ipArray);

  logger.binarySearch({
    ip : convertedIp
  }, compiledCoordinates, 26, function compare(a, b) {
    return a.ip - b.ip;
  }, function parse(buffer) {
    return {
      ip : buffer.readUInt32BE(0),
      lat : buffer.readDoubleBE(4),
      lon : buffer.readDoubleBE(12),
      geoId : buffer.readIntBE(20, 6),
    };
  }, function gotIp(error, data) {

    if (error) {
      console.log(error);
    } else if (data) {

      logger.binarySearch(data, compiledLocations, locationLineSize,
        function compare(a, b) {
          return a.geoId - b.geoId;
        }, function parse(buffer) {

          return {
            geoId : buffer.readUIntBE(0, 6),
            country : buffer.toString('utf8', 6, 8),
            region : buffer.toString('utf8', 8, 11).replace(/\u0000.*/, ''),
            city : buffer.toString('utf8', 11).replace(/\u0000.*/, '')
          };

        }, function gotData(error, geoData) {

          if (error) {
            console.log(error);
          } else if (geoData) {

            coordinates.replaceOne({
              ip : convertedIp
            }, {
              time : new Date(),
              lat : data.lat,
              lon : data.lon,
              c : geoData.country,
              ip : convertedIp,
              board : board.boardUri
            }, {
              upsert : true
            });

          }

        });

    }

  }, true);

};

exports.init = function() {

  coordinates.createIndexes([ {
    key : {
      time : 1
    },
    expireAfterSeconds : 60 * 60 * 24
  }, {
    key : {
      ip : 1
    },
    unique : true
  } ], function setIndex(error, index) {
    if (error) {
      console.log(error);
    }
  });

  var oldCheckForTripcode = postingOpsCommon.checkForTripcode;

  postingOpsCommon.checkForTripcode = function(parameters, callback) {

    if (parameters.name) {
      var roleSignatureRequestIndex = parameters.name.toLowerCase().indexOf('#rs');
      parameters.nameSigned = roleSignatureRequestIndex > -1;
    }

    oldCheckForTripcode(parameters, callback);

  }

  var originalGetPostFlag = postPosting.getPostFlag;

  postPosting.getPostFlag = function(req, parameters, userData, postId, thread,
    board, wishesToSign, enabledCaptcha, cb) {
    exports.recordCoordinate(req, board, userData);

    var locationFlagMode = board.locationFlagMode || 0;

    var flagCondition = locationFlagMode > 1 || !parameters.noFlag;

    var isTor = req.isTor;
    var isBypass = req.bypassId || isTor;
    var isUser = userData && userData.globalRole <= 3 &&
      (postingOpsCommon.doesUserWishesToSign(userData, parameters) || parameters.nameSigned);

    if ((isBypass || isUser) && locationFlagMode && flagCondition) {

      if (isUser) {
        parameters.flagName = '010011010110111101100100';
        parameters.flag = '/.static/flags/kohl.png';
        delete parameters.flagCode;
      } else if (isTor) {
        parameters.flagName = 'Onion';
        parameters.flag = '/.static/flags/onion.png';
        delete parameters.flagCode;
      } else if (req.flag && req.flagName && req.flagCode) {
        parameters.flagName = req.flagName;
        parameters.flag = req.flag;
        parameters.flagCode = req.flagCode;
      } else {
        parameters.flagName = 'Proxy';
        parameters.flag = '/.static/flags/proxy.png';
        delete parameters.flagCode;
      }

      postPosting.handleFiles(req, parameters, userData, postId, thread, board,
        wishesToSign, enabledCaptcha, cb);

    } else {

      originalGetPostFlag(req, parameters, userData, postId, thread,
        board, wishesToSign, enabledCaptcha, cb);
    }

  };

  var originalCreateThread = threadPosting.createThread;

  threadPosting.createThread = function(req, userData, parameters, newFiles, board,
    threadId, wishesToSign, enabledCaptcha, callback) {

    var locationFlagMode = board.locationFlagMode || 0;

    var flagCondition = locationFlagMode > 1 || !parameters.noFlag;

    if (locationFlagMode && flagCondition) {

      var isTor = req.isTor;
      var isBypass = req.bypassId || isTor;
      var isUser = userData && userData.globalRole <= 3 &&
        (postingOpsCommon.doesUserWishesToSign(userData, parameters) || parameters.nameSigned);

      if (isUser) {
        parameters.flagName = '010011010110111101100100';
        parameters.flag = '/.static/flags/kohl.png';
        delete parameters.flagCode;
      } else if (isTor) {
        parameters.flagName = 'Onion';
        parameters.flag = '/.static/flags/onion.png';
        delete parameters.flagCode;
      } else if (isBypass) {
        parameters.flagName = 'Proxy';
        parameters.flag = '/.static/flags/proxy.png';
        delete parameters.flagCode;
      }

    }

    originalCreateThread(req, userData, parameters, newFiles, board,
      threadId, wishesToSign, enabledCaptcha, function(error, threadId) {

        if (error) {
          return callback(error);
        }

        exports.recordCoordinate(req, board, userData);

        callback(null, threadId);

      });

  };

};