Merge remote-tracking branch 'upstream/master'
7
404.php
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
include "inc/functions.php";
|
||||
require_once "inc/functions.php";
|
||||
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
|
||||
|
||||
$dir = "static/404/";
|
||||
|
||||
@ -35,8 +36,8 @@ $page = <<<EOT
|
||||
var faves = JSON.parse(localStorage.favorites);
|
||||
|
||||
$.each(faves, function(k, v) {
|
||||
if (window.location.pathname === '/' + v + '/') {
|
||||
faves.pop(v);
|
||||
if ((window.location.pathname === '/' + v + '/') || (window.location.pathname === '/' + v)) {
|
||||
faves.splice(k, 1);
|
||||
localStorage.favorites = JSON.stringify(faves);
|
||||
|
||||
alert('As /' + v + '/ no longer exists, it has been removed from your favorites.');
|
||||
|
140
README.md
@ -3,145 +3,23 @@
|
||||
|
||||
About
|
||||
------------
|
||||
8chan is a free light-weight, fast, highly configurable and user-friendly
|
||||
imageboard software package. It is written in PHP and has few dependencies.
|
||||
8chan is a fork of vichan, with the difference that 8chan is geared towards allowing users to create their own boards.
|
||||
|
||||
8chan is a fork of [vichan](https://github.com/vichan-devel/vichan), which is a fork of [Tinyboard](http://tinyboard.org/), a great imageboard package, actively
|
||||
building on it and adding a lot of features and other improvements.
|
||||
Most things (other than installation) that apply to upstream vichan also apply to 8chan. See their readme for a detailed FAQ: https://github.com/vichan-devel/vichan/blob/master/README.md
|
||||
|
||||
Support and announcements: https://int.vichan.net/devel/
|
||||
If you are not interested in letting your users make their own boards, install vichan instead of 8chan.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
1. PHP >= 5.3
|
||||
2. MySQL/MariaDB server
|
||||
3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
|
||||
4. [PHP GD](http://www.php.net/manual/en/intro.image.php)
|
||||
5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php)
|
||||
|
||||
We try to make sure vichan is compatible with all major web servers and
|
||||
operating systems. vichan does not include an Apache ```.htaccess``` file nor does
|
||||
it need one.
|
||||
|
||||
### Recommended
|
||||
1. MySQL/MariaDB server >= 5.5.3
|
||||
2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
|
||||
3. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php),
|
||||
[XCache](http://xcache.lighttpd.net/) or
|
||||
[Memcached](http://www.php.net/manual/en/intro.memcached.php)
|
||||
|
||||
Contributing
|
||||
------------
|
||||
You can contribute to vichan by:
|
||||
* Developing patches/improvements/translations and using GitHub to submit pull requests
|
||||
* Providing feedback and suggestions
|
||||
* Writing/editing documentation
|
||||
|
||||
If you need help developing a patch, please join our IRC channel.
|
||||
|
||||
Installation
|
||||
-------------
|
||||
1. Download and extract Tinyboard to your web directory or get the latest
|
||||
development version with:
|
||||
|
||||
git clone git://github.com/ctrlcctrlv/8chan.git
|
||||
|
||||
2. Copy secrets.example.php to secrets.php and edit it.
|
||||
|
||||
3. Navigate to ```install.php``` in your web browser and follow the
|
||||
prompts.
|
||||
4. vichan should now be installed. Log in to ```mod.php``` with the
|
||||
default username and password combination: **admin / password**.
|
||||
|
||||
Please remember to change the administrator account password.
|
||||
|
||||
See also: [Configuration Basics](http://tinyboard.org/docs/?p=Config).
|
||||
|
||||
Upgrade
|
||||
-------
|
||||
To upgrade from any version of Tinyboard or vichan:
|
||||
|
||||
Either run ```git pull``` to update your files, if you used git, or
|
||||
backup your ```inc/instance-config.php```, replace all your files in place
|
||||
(don't remove boards etc.), then put ```inc/instance-config.php``` back and
|
||||
finally run ```install.php```.
|
||||
|
||||
Support
|
||||
--------
|
||||
vichan is still beta software -- there are bound to be bugs. If you find a
|
||||
bug, please report it.
|
||||
|
||||
If you need assistance with installing, configuring, or using vichan, you may
|
||||
find support from a variety of sources:
|
||||
|
||||
* If you're unsure about how to enable or configure certain features, make
|
||||
sure you have read the comments in ```inc/config.php```.
|
||||
* Check out an [official vichan board](http://int.vichan.net/devel/).
|
||||
* You can join vichan's IRC channel for support
|
||||
[irc.6irc.net #vichan-devel](irc://irc.6irc.net/vichan-devel)
|
||||
|
||||
### Tinyboard support
|
||||
8chan is based on a Tinyboard, so both engines have very much in common. These
|
||||
links may be helpful for you as well:
|
||||
|
||||
* Tinyboard documentation can be found [here](http://tinyboard.org/docs/).
|
||||
* You can join Tinyboard's IRC channel for support and general queries:
|
||||
[irc.datnode.net #tinyboard](irc://irc.datnode.net/tinyboard).
|
||||
* You may find help at [tinyboard.org](http://tinyboard.org/#help).
|
||||
|
||||
Donations
|
||||
---------
|
||||
Do you like our work? You can motivate us financially to do better ;)
|
||||
* Bitcoin: [](http://tip4commit.com/projects/708)
|
||||
|
||||
You can also ask us to develop some feature specially for you <3. Join our IRC
|
||||
channel and ask for a quote (there are a few of us, who work with the codebase
|
||||
and are skilled enough to develop such features pretty quickly).
|
||||
|
||||
CLI tools
|
||||
-----------------
|
||||
There are a few command line interface tools, based on Tinyboard-Tools. These need
|
||||
to be launched from a Unix shell account (SSH, or something). They are located in a ```tools/```
|
||||
directory.
|
||||
|
||||
You actually don't need these tools for your imageboard functioning, they are aimed
|
||||
at the power users. You won't be able to run these from shared hosting accounts
|
||||
(i.e. all free web servers).
|
||||
|
||||
Localisation
|
||||
------------
|
||||
Want to have vichan/8chan in your language? You can contribute your translations at this URL:
|
||||
|
||||
https://www.transifex.com/projects/p/tinyboard-vichan-devel/
|
||||
|
||||
Oekaki
|
||||
------
|
||||
vichan makes use of [wPaint](https://github.com/websanova/wPaint) for oekaki. After you pull the repository, however, you will need to download wPaint separately using git's `submodule` feature. Use the following commands:
|
||||
Because I cannot be bothered to maintain `install.php`, the install process is as such:
|
||||
|
||||
```
|
||||
git submodule init
|
||||
git submodule update
|
||||
mysql -uroot 8chan < install.sql
|
||||
echo '8chan' > .installed
|
||||
```
|
||||
|
||||
To enable oekaki, add all the scripts listed in `js/wpaint.js` to your `instance-config.php`.
|
||||
Here's my install script as of 11/14/2014 for the 8chan servers which run Ubuntu 14.04:
|
||||
|
||||
WebM support
|
||||
------------
|
||||
Read `inc/lib/webm/README.md` for information about enabling webm.
|
||||
|
||||
Static Pages
|
||||
------------
|
||||
Some pages like `/faq.html` need to be pre-generated:
|
||||
```
|
||||
$ php faq.php > faq.html
|
||||
apt-get install graphicsmagick gifsicle php5-fpm mysql-client php5-mysql php5-cli php-pear php5-apcu; add-apt-repository ppa:jon-severinsson/ffmpeg; add-apt-repository ppa:nginx/stable; apt-get update; apt-get install nginx ffmpeg; pear install Net_DNS2
|
||||
```
|
||||
|
||||
vichan API
|
||||
----------
|
||||
vichan provides by default a 4chan-compatible JSON API. For documentation on this, see:
|
||||
https://github.com/vichan-devel/vichan-API/ .
|
||||
|
||||
License
|
||||
--------
|
||||
See [LICENSE.md](http://github.com/vichan-devel/vichan/blob/master/LICENSE.md).
|
||||
|
||||
Have fun!
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
require_once 'inc/functions.php';
|
||||
checkBan();
|
||||
print "<!doctype html><html><head><meta charset='utf-8'><title>"._("Banned?")."</title></head><body>";
|
||||
print "<h1>"._("You are not banned.")."</h1>";
|
||||
print "</body></html>";
|
||||
?>
|
@ -8,7 +8,7 @@ header("Expires: 0");
|
||||
|
||||
function get_custom_banner(&$b) {
|
||||
# Validate the board name
|
||||
if (!(isset($b) && preg_match('/^[a-z0-9]{1,10}$/', $b)))
|
||||
if (!(isset($b) && preg_match('/^[a-z0-9+]{1,30}$/', $b)))
|
||||
return null;
|
||||
|
||||
# Check if directory exists
|
||||
|
33
boards.php
@ -10,10 +10,17 @@ if (php_sapi_name() == 'fpm-fcgi' && !$admin) {
|
||||
error('Cannot be run directly.');
|
||||
}
|
||||
$boards = listBoards();
|
||||
|
||||
$all_tags = array();
|
||||
$total_posts_hour = 0;
|
||||
$total_posts = 0;
|
||||
|
||||
function to_tag($str) {
|
||||
$str = trim($str);
|
||||
$str = strtolower($str);
|
||||
$str = str_replace(['_', ' '], '-', $str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
foreach ($boards as $i => $board) {
|
||||
|
||||
//$query = prepare(sprintf("SELECT (SELECT MAX(id) from ``posts_%s``) AS max, (SELECT MAX(id) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) < DATE_SUB(NOW(), INTERVAL 1 HOUR)) AS oldmax, (SELECT MAX(id) from ``posts_%s``) AS max_d, (SELECT MAX(id) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) < DATE_SUB(NOW(), INTERVAL 1 DAY)) AS oldmax_d, (SELECT count(id) FROM ``posts_%s``) AS count;", $board['uri'], $board['uri'], $board['uri'], $board['uri'], $board['uri']));
|
||||
@ -28,6 +35,23 @@ SELECT MAX(id) max, (SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time)
|
||||
$query->execute() or error(db_error($query));
|
||||
$r = $query->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$tquery = prepare("SELECT `tag` FROM ``board_tags`` WHERE `uri` = :uri");
|
||||
$tquery->execute([":uri" => $board['uri']]) or error(db_error($tquery));
|
||||
$r2 = $tquery->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$tags = array();
|
||||
if ($r2) {
|
||||
foreach ($r2 as $ii => $t) {
|
||||
$tag=to_tag($t['tag']);
|
||||
$tags[] = $tag;
|
||||
if (!isset($all_tags[$tag])) {
|
||||
$all_tags[$tag] = (int)$r['uniq_ip'];
|
||||
} else {
|
||||
$all_tags[$tag] += $r['uniq_ip'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pph = $r['pph'];
|
||||
$ppd = $r['ppd'];
|
||||
|
||||
@ -38,6 +62,7 @@ SELECT MAX(id) max, (SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time)
|
||||
$boards[$i]['ppd'] = $ppd;
|
||||
$boards[$i]['max'] = $r['max'];
|
||||
$boards[$i]['uniq_ip'] = $r['uniq_ip'];
|
||||
$boards[$i]['tags'] = $tags;
|
||||
}
|
||||
|
||||
usort($boards,
|
||||
@ -86,14 +111,18 @@ foreach ($boards as $i => &$board) {
|
||||
$n_boards = sizeof($boards);
|
||||
$t_boards = $hidden_boards_total + $n_boards;
|
||||
|
||||
$boards = array_values($boards);
|
||||
arsort($all_tags);
|
||||
|
||||
$config['additional_javascript'] = array('js/jquery.min.js', 'js/jquery.tablesorter.min.js');
|
||||
$body = Element("8chan/boards.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p')));
|
||||
$body = Element("8chan/boards-tags.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p'), 'tags' => $all_tags));
|
||||
|
||||
$html = Element("page.html", array("config" => $config, "body" => $body, "title" => "Boards on ∞chan"));
|
||||
if ($admin) {
|
||||
echo $html;
|
||||
} else {
|
||||
file_write("boards.json", json_encode($boards));
|
||||
file_write("tags.json", json_encode($all_tags));
|
||||
foreach ($boards as $i => $b) {
|
||||
if (in_array($b['uri'], $config['no_top_bar_boards'])) {
|
||||
unset($boards[$i]);
|
||||
|
60
claim.php
@ -1,5 +1,61 @@
|
||||
<?php
|
||||
|
||||
include 'inc/functions.php';
|
||||
if (php_sapi_name() == 'fpm-fcgi') {
|
||||
error('Cannot be run directly.');
|
||||
}
|
||||
function last_activity($board) {
|
||||
// last post
|
||||
$query = prepare(sprintf("SELECT MAX(time) AS time FROM posts_%s", $board));
|
||||
$query->execute();
|
||||
$row = $query->fetch();
|
||||
$ago = (new DateTime)->sub(new DateInterval('P1W'));
|
||||
$mod_ago = (new DateTime)->sub(new DateInterval('P2W'));
|
||||
|
||||
error('Automatic claiming is no longer available. To claim a board, send your request to admin@8chan.co along with the IP you used to post on that board.');
|
||||
$last_activity_date = new DateTime();
|
||||
$last_mod_date = new DateTime();
|
||||
|
||||
$last_activity_date->setTimestamp($row['time']);
|
||||
|
||||
$query = query("SELECT id, username FROM mods WHERE boards = '$board' AND type = 20");
|
||||
$mods = $query->fetchAll();
|
||||
|
||||
if ($mods) {
|
||||
$mod = $mods[0]['id'];
|
||||
$query = query("SELECT MAX(time) AS time FROM modlogs WHERE `mod` = $mod");
|
||||
$a = $query->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if ($a[0]) {
|
||||
$last_mod_date->setTimestamp($a[0]);
|
||||
if (!$row['time'])
|
||||
$last_activity_date->setTimestamp($a[0]);
|
||||
} else {// no one ever logged in, try board creation time
|
||||
$query = query("SELECT UNIX_TIMESTAMP(time) AS time FROM board_create WHERE uri = '$board'");
|
||||
$crt = $query->fetchAll(PDO::FETCH_COLUMN);
|
||||
$last_activity_date->setTimestamp($crt[0]);
|
||||
$last_mod_date = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mods and ($last_activity_date < $ago or ($last_mod_date and $last_mod_date < $mod_ago))) {
|
||||
return array($last_activity_date, $last_mod_date, $mods);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$q = query("SELECT uri FROM boards");
|
||||
$boards = $q->fetchAll(PDO::FETCH_COLUMN);
|
||||
$delete = array();
|
||||
foreach($boards as $board) {
|
||||
$last_activity = last_activity($board);
|
||||
|
||||
if ($last_activity) {
|
||||
list($last_activity_date, $last_mod_date, $mods) = $last_activity;
|
||||
|
||||
$last_mod_f = $last_mod_date ? $last_mod_date->format('Y-m-d H:i:s') : '<em>never</em>';
|
||||
$last_activity_f = $last_activity_date ? $last_activity_date->format('Y-m-d H:i:s') : '<em>never</em>';
|
||||
$delete[] = array('board' => $board, 'last_activity_date' => $last_activity_f, 'last_mod' => $last_mod_date, 'last_mod_f' => $last_mod_f);
|
||||
}
|
||||
}
|
||||
$body = Element("8chan/claim.html", array("config" => $config, "delete" => $delete));
|
||||
echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Claim"), "subtitle" => _("Take deserted boards back from their owners")));
|
||||
|
@ -8,8 +8,8 @@ $protected = array('burgers', 'cow', 'wilno', 'cute', 'yoga');
|
||||
$q = query("SELECT uri FROM boards");
|
||||
$boards = $q->fetchAll(PDO::FETCH_COLUMN);
|
||||
$now = new DateTime();
|
||||
$ago = (new DateTime)->sub(new DateInterval('P3D'));
|
||||
$mod_ago = (new DateTime)->sub(new DateInterval('P7D'));
|
||||
$ago = (new DateTime)->sub(new DateInterval('P7D'));
|
||||
$mod_ago = (new DateTime)->sub(new DateInterval('P14D'));
|
||||
|
||||
// Find out the last activity for our board
|
||||
$delete = array();
|
||||
|
27
faq.php
@ -26,6 +26,7 @@ $body = <<<EOT
|
||||
<li>Do not post, request, or link to any content illegal in the United States of America. Do not create boards with the sole purpose of posting or spreading such content.</li>
|
||||
</ul>
|
||||
<p>Other than that, you are free to institute whatever rules you want on your board.</p>
|
||||
<p><a href="https://8chan.co/obscenity.html">More information about US obscenity laws and how they relate to 8chan boards</a></p>
|
||||
<h2>How do I add more volunteers?</h2>
|
||||
<p>You may do this in your board settings, click on "Edit board volunteers".
|
||||
<h2>How do I manage my board?</h2>
|
||||
@ -34,9 +35,9 @@ $body = <<<EOT
|
||||
<p>The admin can be reached at <tt>admin at 8chan dot co</tt>.</p>
|
||||
|
||||
<h2>Help! My board has been deleted!</h2>
|
||||
<p>Were you inactive for longer than one week? Were there no posts on the board for 72 hours?</p>
|
||||
<p>As of November 13th, 2014, board expiration no longer occurs.</p>
|
||||
|
||||
<p>If either of those is true, the board was deleted automatically. You are free to recreate it. I cannot restore it, so don't bother emailing me about it.</p>
|
||||
<p>You still may lose access to your board, however, if you fail to log in for two weeks or it receives no posts for a week. See <a href="/claim.html">here</a> for a list of boards that are available for reclaiming.</p>
|
||||
|
||||
<h2>How do I post as a volunteer on my board?</h2>
|
||||
<p>Make sure you are using the volunteer interface to view your board. The URL of your browser should be <a href="https://8chan.co/mod.php?/yourboard"><tt>https://8chan.co/mod.php?/yourboard</tt></a>.</p>
|
||||
@ -47,7 +48,7 @@ $body = <<<EOT
|
||||
<p>If they are doing something illegal, email me.</p>
|
||||
|
||||
<h2>Can you give me X board?</h2>
|
||||
<p>If the owner of the board is inactive or the board is broken due to bad CSS, sure. Send me an email.</p>
|
||||
<p>If the owner of the board is inactive or the board is broken due to bad CSS, sure. Send me an email. You can see a list of boards that qualify for being taken over <a href="/claim.html">here</a>.</p>
|
||||
|
||||
<h2>Can you add some new feature?</h2>
|
||||
<p>Open a <a href="https://github.com/ctrlcctrlv/8chan/issues">Github issue</a>. Better yet, write it yourself and open a pull request.
|
||||
@ -65,9 +66,9 @@ $body = <<<EOT
|
||||
</ul>
|
||||
|
||||
<h2>How are featured boards chosen?</h2>
|
||||
<p>Top fifteen boards excluding /meta/, /b/ and /int/.</p>
|
||||
<p>Top fifteen boards excluding /meta/, /b/ and /news+/.</p>
|
||||
|
||||
<h2>Who owns /meta/, /b/, and /int/?</h2>
|
||||
<h2>Who owns /meta/ and /b/?</h2>
|
||||
<p>No one, so they are <em>de facto</em> property of the administration.</p>
|
||||
|
||||
<h2>Where's the mobile app?</h2>
|
||||
@ -76,12 +77,22 @@ $body = <<<EOT
|
||||
<p>I don't provide support for this app, ask the developer of it if you have a problem with it.</p>
|
||||
|
||||
<h2>Where's the archive?</h2>
|
||||
<p>There isn't one yet and there will never be an official archive.</p>
|
||||
<p><s>There isn't one yet and there will never be an official archive.</s></p>
|
||||
<p>Given that archives are inevitable and will be created anyway via <a href="https://archive.today">archive.today</a>, Google cache, and anyone who installs Asagi, I'm softening my stance on this. Currently, 8archive.moe provides our archive, and I may set up an official one. <strong>All archives officially partnered with us will be opt-in by our board owners, not opt-out. Archives who archive boards that have not opted in will be considered pirate archives, and legal action may be taken.</strong></p>
|
||||
|
||||
<h2>I got an email from an @8chan.co email address, is that you?</h2>
|
||||
<p>8chan.co uses <a href="https://cock.li">cock.li</a> to manage our domain's email. cock.li allows anyone to create an email account @8chan.co.</p>
|
||||
<p>That said, we have quite a few official 8chan.co email addresses. They are:</p>
|
||||
<ul>
|
||||
<li>admin at 8chan dot co</li>
|
||||
<li>dmca at 8chan dot co</li>
|
||||
<li>claim at 8chan dot co</li>
|
||||
</ul>
|
||||
|
||||
<h2>How do I donate?</h2>
|
||||
<p>Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin). PayPal is also accepted @ fredrick.brennan1@gmail.com .</p>
|
||||
<p>Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin).</p>
|
||||
<p>I am also a big fan of Monero (XMR). You can send XMR to our <a href="http://openalias.org">OpenAlias</a> in the simplewallet client, or simply send to 49dBJhGhYFxJEfydS6hH6GRyg1W4cDgupdNVtw7j1WtcUY7xPXwNLw6fUVay644viaCcEhMFG1Z7SjjxRXEFDdNWJdvH9kS.</p>
|
||||
<p>You may also donate monthly via Patreon at <a href="http://www.patreon.com/user?u=162165">http://www.patreon.com/user?u=162165</a>.
|
||||
|
||||
<h2>Are you really a cripple?</h2>
|
||||
<p>Yes.</p>
|
||||
|
||||
|
@ -20,6 +20,9 @@
|
||||
$config['mod']['noticeboard_post'] = GLOBALVOLUNTEER;
|
||||
$config['mod']['search'] = GLOBALVOLUNTEER;
|
||||
$config['mod']['clean_global'] = GLOBALVOLUNTEER;
|
||||
$config['mod']['view_notes'] = DISABLED;
|
||||
$config['mod']['create_notes'] = DISABLED;
|
||||
$config['mod']['edit_config'] = DISABLED;
|
||||
$config['mod']['debug_recent'] = ADMIN;
|
||||
$config['mod']['debug_antispam'] = ADMIN;
|
||||
$config['mod']['noticeboard_post'] = ADMIN;
|
||||
@ -29,6 +32,7 @@
|
||||
$config['mod']['edit_flags'] = MOD;
|
||||
$config['mod']['edit_settings'] = MOD;
|
||||
$config['mod']['edit_volunteers'] = MOD;
|
||||
$config['mod']['edit_tags'] = MOD;
|
||||
$config['mod']['clean'] = BOARDVOLUNTEER;
|
||||
// new perms
|
||||
|
||||
@ -49,6 +53,87 @@
|
||||
$config['mod']['ban_appeals'] = BOARDVOLUNTEER;
|
||||
$config['mod']['view_ban_appeals'] = BOARDVOLUNTEER;
|
||||
$config['mod']['view_ban'] = BOARDVOLUNTEER;
|
||||
$config['mod']['reassign_board'] = ADMIN;
|
||||
|
||||
$config['mod']['custom_pages']['/tags/(\%b)'] = function ($b) {
|
||||
global $board, $config;
|
||||
|
||||
if (!openBoard($b))
|
||||
error("Could not open board!");
|
||||
|
||||
if (!hasPermission($config['mod']['edit_tags'], $b))
|
||||
error($config['error']['noaccess']);
|
||||
|
||||
if (isset($_POST['tags'])) {
|
||||
if (sizeof($_POST['tags']) > 5)
|
||||
error(_('Too many tags.'));
|
||||
|
||||
$delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri');
|
||||
$delete->bindValue(':uri', $b);
|
||||
$delete->execute();
|
||||
|
||||
foreach ($_POST['tags'] as $i => $tag) {
|
||||
if ($tag) {
|
||||
if (strlen($tag) > 255)
|
||||
continue;
|
||||
|
||||
$insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)');
|
||||
$insert->bindValue(':uri', $b);
|
||||
$insert->bindValue(':tag', utf8tohtml($tag));
|
||||
$insert->execute();
|
||||
}
|
||||
}
|
||||
|
||||
$update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri');
|
||||
$update->bindValue(':uri', $b);
|
||||
$update->bindValue(':sfw', isset($_POST['sfw']));
|
||||
$update->execute();
|
||||
}
|
||||
$query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri');
|
||||
$query->bindValue(':uri', $b);
|
||||
$query->execute();
|
||||
|
||||
$tags = $query->fetchAll();
|
||||
|
||||
$query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri');
|
||||
$query->bindValue(':uri', $b);
|
||||
$query->execute();
|
||||
|
||||
$sfw = $query->fetchColumn();
|
||||
|
||||
mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw));
|
||||
};
|
||||
|
||||
$config['mod']['custom_pages']['/reassign/(\%b)'] = function($b) {
|
||||
global $board, $config;
|
||||
|
||||
if (!openBoard($b))
|
||||
error("Could not open board!");
|
||||
|
||||
if (!hasPermission($config['mod']['reassign_board'], $b))
|
||||
error($config['error']['noaccess']);
|
||||
|
||||
$query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20");
|
||||
$mods = $query->fetchAll();
|
||||
|
||||
if (!$mods) {
|
||||
error('No mods?');
|
||||
}
|
||||
|
||||
$password = base64_encode(openssl_random_pseudo_bytes(9));
|
||||
$salt = generate_salt();
|
||||
$hashed = hash('sha256', $salt . sha1($password));
|
||||
|
||||
$query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod');
|
||||
$query->bindValue(':hashed', $hashed);
|
||||
$query->bindValue(':salt', $salt);
|
||||
$query->bindValue(':mod', $mods[0]['username']);
|
||||
$query->execute();
|
||||
|
||||
$body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at 8chan.co/mod.php.<br>Username: {$mods[0]['username']}<br>Password: {$password}<br>Thanks for using 8chan.co!";
|
||||
|
||||
mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body));
|
||||
};
|
||||
|
||||
$config['mod']['custom_pages']['/volunteers/(\%b)'] = function($b) {
|
||||
global $board, $config, $pdo;
|
||||
@ -71,14 +156,16 @@
|
||||
error(sprintf($config['error']['required'], 'username'));
|
||||
if ($_POST['password'] == '')
|
||||
error(sprintf($config['error']['required'], 'password'));
|
||||
if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username']))
|
||||
error(_('Invalid username'));
|
||||
|
||||
if ($count > 10) {
|
||||
error(_('Too many board volunteers!'));
|
||||
}
|
||||
|
||||
foreach ($volunteers as $i => $v) {
|
||||
if ($_POST['username'] == $v['username']) {
|
||||
error(_('Refusing to create a volunteer with the same username as an existing one.'));
|
||||
if (strtolower($_POST['username']) == strtolower($v['username'])) {
|
||||
error(_('Refusing to create a volunteer with the same username as an existing one.'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +262,7 @@
|
||||
error($config['error']['invalidimg']);
|
||||
}
|
||||
|
||||
if ($size[0] > 20 or $size[0] < 11 or $size[1] != 11){
|
||||
if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){
|
||||
error(_('Image wrong size!'));
|
||||
}
|
||||
if (sizeof($banners) > 256) {
|
||||
@ -317,6 +404,9 @@ FLAGS;
|
||||
$code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "<code><pre class=\'prettyprint\' style=\'display:inline-block\'>\$1</pre></code>");' : '';
|
||||
$katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "<span class=\'tex\'>\$1</span>"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : '';
|
||||
$user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : '';
|
||||
$captcha = isset($_POST['captcha']) ? 'true' : 'false';
|
||||
$force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false';
|
||||
|
||||
|
||||
$oekaki_js = <<<OEKAKI
|
||||
\$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
|
||||
@ -392,6 +482,8 @@ OEKAKI;
|
||||
\$config['blotter'] = base64_decode('$blotter');
|
||||
\$config['stylesheets']['Custom'] = 'board/$b.css';
|
||||
\$config['default_stylesheet'] = array('Custom', \$config['stylesheets']['Custom']);
|
||||
\$config['captcha']['enabled'] = $captcha;
|
||||
\$config['force_subject_op'] = $force_subject_op;
|
||||
$code_tags $katex $oekaki $replace $multiimage $allow_flash $allow_pdf $user_flags
|
||||
if (\$config['disable_images'])
|
||||
\$config['max_pages'] = 10000;
|
||||
@ -400,19 +492,54 @@ $locale
|
||||
$add_to_config
|
||||
EOT;
|
||||
|
||||
// Clean up our CSS...no more expression() or off-site URLs.
|
||||
$clean_css = preg_replace('/expression\s*\(/', '', $_POST['css']);
|
||||
|
||||
// URL matcher from SO:
|
||||
$match_urls = '(?xi)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))';
|
||||
|
||||
$matched = array();
|
||||
|
||||
preg_match_all("#$match_urls#im", $clean_css, $matched);
|
||||
|
||||
$allowed_urls = array('https://i.imgur.com/', 'https://media.8chan.co/', 'https://a.pomf.se/', 'https://fonts.googleapis.com/', 'http://8ch.net/');
|
||||
$error = false;
|
||||
|
||||
if (isset($matched[0])) {
|
||||
foreach ($matched[0] as $i => $v) {
|
||||
$error = true;
|
||||
foreach ($allowed_urls as $ii => $url) {
|
||||
if (strpos($v, $url) === 0) {
|
||||
$error = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
error(_('Off-site links are not allowed in board stylesheets!'));
|
||||
}
|
||||
|
||||
$query = query('SELECT `uri`, `title`, `subtitle` FROM ``boards`` WHERE `8archive` = TRUE');
|
||||
file_write('8archive.json', json_encode($query->fetchAll(PDO::FETCH_ASSOC)));
|
||||
file_write($b.'/config.php', $config_file);
|
||||
file_write('stylesheets/board/'.$b.'.css', $_POST['css']);
|
||||
file_write('stylesheets/board/'.$b.'.css', $clean_css);
|
||||
file_write($b.'/rules.html', Element('page.html', array('title'=>'Rules', 'subtitle'=>'', 'config'=>$config, 'body'=>'<div class="ban">'.purify($_POST['rules']).'</div>')));
|
||||
file_write($b.'/rules.txt', $_POST['rules']);
|
||||
|
||||
$_config = $config;
|
||||
|
||||
openBoard($b);
|
||||
// Faster than openBoard and bypasses cache...we're trusting the PHP output
|
||||
// to be safe enough to run with every request, we can eval it here.
|
||||
eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file)));
|
||||
|
||||
// be smarter about rebuilds...only some changes really require us to rebuild all threads
|
||||
if ($_config['blotter'] != $config['blotter'] || $_config['field_disable_name'] != $config['field_disable_name'] || $_config['show_sages'] != $config['show_sages']) {
|
||||
if ($_config['captcha']['enabled'] != $config['captcha']['enabled']
|
||||
|| $_config['captcha']['extra'] != $config['captcha']['extra']
|
||||
|| $_config['blotter'] != $config['blotter']
|
||||
|| $_config['field_disable_name'] != $config['field_disable_name']
|
||||
|| $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) {
|
||||
buildIndex();
|
||||
$query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error());
|
||||
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
10
inc/bans.php
@ -218,14 +218,8 @@ class Bans {
|
||||
}
|
||||
unset($ban['type']);
|
||||
if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) {
|
||||
@list($ban['mask'], $subnet) = explode("/", $ban['mask']);
|
||||
$ban['mask'] = preg_split("/[\.:]/", $ban['mask']);
|
||||
$ban['mask'] = array_slice($ban['mask'], 0, 2);
|
||||
$ban['mask'] = implode(".", $ban['mask']);
|
||||
$ban['mask'] .= ".x.x";
|
||||
if (isset ($subnet)) {
|
||||
$ban['mask'] .= "/$subnet";
|
||||
}
|
||||
$ban['mask'] = @less_ip($ban['mask']);
|
||||
|
||||
$ban['masked'] = true;
|
||||
}
|
||||
|
||||
|
@ -272,6 +272,8 @@
|
||||
'embed',
|
||||
'recaptcha_challenge_field',
|
||||
'recaptcha_response_field',
|
||||
'captcha_cookie',
|
||||
'captcha_text',
|
||||
'spoiler',
|
||||
'page',
|
||||
'file_url',
|
||||
@ -300,6 +302,19 @@
|
||||
$config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f';
|
||||
$config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_';
|
||||
|
||||
$config['captcha'] = array();
|
||||
|
||||
// Enable custom captcha provider
|
||||
$config['captcha']['enabled'] = false;
|
||||
|
||||
// Custom captcha provider path
|
||||
$config['captcha']['provider_get'] = 'http://8chan.vichan.net/captcha/entrypoint.php';
|
||||
$config['captcha']['provider_check'] = 'http://8chan.vichan.net/captcha/entrypoint.php';
|
||||
|
||||
// Custom captcha extra field (eg. charset)
|
||||
$config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
|
||||
/*
|
||||
* Custom filters detect certain posts and reject/ban accordingly. They are made up of a condition and an
|
||||
* action (for when ALL conditions are met). As every single post has to be put through each filter,
|
||||
@ -602,6 +617,17 @@
|
||||
|
||||
// How many ban appeals can be made for a single ban?
|
||||
$config['ban_appeals_max'] = 1;
|
||||
|
||||
// Blacklisted board names. Default values to protect existing folders in the core codebase.
|
||||
$config['banned_boards'] = array(
|
||||
'.git',
|
||||
'inc',
|
||||
'js',
|
||||
'static',
|
||||
'stylesheets',
|
||||
'templates',
|
||||
'tools'
|
||||
);
|
||||
|
||||
// Show moderator name on ban page.
|
||||
$config['show_modname'] = false;
|
||||
@ -727,7 +753,6 @@
|
||||
// Allowed image file extensions.
|
||||
$config['allowed_ext'][] = 'jpg';
|
||||
$config['allowed_ext'][] = 'jpeg';
|
||||
$config['allowed_ext'][] = 'bmp';
|
||||
$config['allowed_ext'][] = 'gif';
|
||||
$config['allowed_ext'][] = 'png';
|
||||
// $config['allowed_ext'][] = 'svg';
|
||||
@ -745,6 +770,7 @@
|
||||
$config['file_icons']['default'] = 'file.png';
|
||||
$config['file_icons']['zip'] = 'zip.png';
|
||||
$config['file_icons']['webm'] = 'video.png';
|
||||
$config['file_icons']['mp4'] = 'video.png';
|
||||
// Example: Custom thumbnail for certain file extension.
|
||||
// $config['file_icons']['extension'] = 'some_file.png';
|
||||
|
||||
@ -858,7 +884,7 @@
|
||||
$config['thread_subject_in_title'] = false;
|
||||
|
||||
// Additional lines added to the footer of all pages.
|
||||
$config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
|
||||
// $config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
|
||||
|
||||
// Characters used to generate a random password (with Javascript).
|
||||
$config['genpassword_chars'] = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
|
||||
@ -1057,6 +1083,7 @@
|
||||
$config['error']['youaremuted'] = _('You are muted! Expires in %d seconds.');
|
||||
$config['error']['dnsbl'] = _('Your IP address is listed in %s.');
|
||||
$config['error']['toomanylinks'] = _('Too many links; flood detected.');
|
||||
$config['error']['notenoughlinks'] = _('OPs are required to have at least %d links on this board.');
|
||||
$config['error']['toomanycites'] = _('Too many cites; post discarded.');
|
||||
$config['error']['toomanycross'] = _('Too many cross-board links; post discarded.');
|
||||
$config['error']['nodelete'] = _('You didn\'t select anything to delete.');
|
||||
@ -1326,8 +1353,8 @@
|
||||
// Capcode permissions.
|
||||
$config['mod']['capcode'] = array(
|
||||
// JANITOR => array('Janitor'),
|
||||
MOD => array('Mod'),
|
||||
ADMIN => true
|
||||
MOD => array('Mod'),
|
||||
ADMIN => true
|
||||
);
|
||||
|
||||
// Example: Allow mods to post with "## Moderator" as well
|
||||
@ -1410,7 +1437,7 @@
|
||||
$config['mod']['view_banlist'] = MOD;
|
||||
// View the username of the mod who made a ban
|
||||
$config['mod']['view_banstaff'] = MOD;
|
||||
// If the moderator doesn't fit the $config['mod']['view_banstaff''] (previous) permission, show him just
|
||||
// If the moderator doesn't fit the $config['mod']['view_banstaff'] (previous) permission, show him just
|
||||
// a "?" instead. Otherwise, it will be "Mod" or "Admin".
|
||||
$config['mod']['view_banquestionmark'] = false;
|
||||
// Show expired bans in the ban list (they are kept in cache until the culprit returns)
|
||||
@ -1654,10 +1681,17 @@
|
||||
|
||||
// Regex for board URIs. Don't add "`" character or any Unicode that MySQL can't handle. 58 characters
|
||||
// is the absolute maximum, because MySQL cannot handle table names greater than 64 characters.
|
||||
$config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}';
|
||||
$config['board_regex'] = '[0-9a-zA-Z\+$_\x{0080}-\x{FFFF}]{1,58}';
|
||||
|
||||
// Youtube.js embed HTML code
|
||||
$config['youtube_js_html'] = '<div class="video-container" data-video="$1" data-params="&$2&$3">'.
|
||||
'<a href="$0" target="_blank" class="file">'.
|
||||
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$1/0.jpg" class="post-image"/>'.
|
||||
'</a></div>';
|
||||
|
||||
// Use read.php?
|
||||
// read.php is a file that dynamically displays pages to users instead of the build on demand system in use in Tinyboard since 2010.
|
||||
//
|
||||
// read.php is basically a watered down mod.php -- if coupled with caching, it improves performance and allows for easier replication
|
||||
// across machines.
|
||||
$config['use_read_php'] = false;
|
||||
|
@ -406,9 +406,13 @@ class Post {
|
||||
}
|
||||
|
||||
public function getClean( ) {
|
||||
global $board;
|
||||
global $board, $config;
|
||||
|
||||
if( !isset( $this->clean ) ) {
|
||||
if ($config['cache']['enabled'] && $this->clean = cache::get("post_clean_{$board['uri']}_{$this->id}")) {
|
||||
return $this->clean;
|
||||
}
|
||||
|
||||
$query = prepare("SELECT * FROM `post_clean` WHERE `post_id` = :post AND `board_id` = :board");
|
||||
$query->bindValue( ':board', $board['uri'] );
|
||||
$query->bindValue( ':post', $this->id );
|
||||
@ -424,6 +428,8 @@ class Post {
|
||||
'clean_local_mod_id' => null,
|
||||
'clean_global_mod_id' => null,
|
||||
);
|
||||
|
||||
cache::set("post_clean_{$board['uri']}_{$this->id}", $this->clean);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,14 +29,16 @@ mb_internal_encoding('UTF-8');
|
||||
loadConfig();
|
||||
|
||||
function init_locale($locale, $error='error') {
|
||||
if (_setlocale(LC_ALL, $locale) === false) {
|
||||
$error('The specified locale (' . $locale . ') does not exist on your platform!');
|
||||
}
|
||||
if ($locale === 'en')
|
||||
$locale = 'en_US.utf8';
|
||||
|
||||
if (extension_loaded('gettext')) {
|
||||
setlocale(LC_ALL, $locale);
|
||||
bindtextdomain('tinyboard', './inc/locale');
|
||||
bind_textdomain_codeset('tinyboard', 'UTF-8');
|
||||
textdomain('tinyboard');
|
||||
} else {
|
||||
_setlocale(LC_ALL, $locale);
|
||||
_bindtextdomain('tinyboard', './inc/locale');
|
||||
_bind_textdomain_codeset('tinyboard', 'UTF-8');
|
||||
_textdomain('tinyboard');
|
||||
@ -430,7 +432,8 @@ function setupBoard($array) {
|
||||
$board = array(
|
||||
'uri' => $array['uri'],
|
||||
'title' => $array['title'],
|
||||
'subtitle' => $array['subtitle']
|
||||
'subtitle' => $array['subtitle'],
|
||||
'indexed' => $array['indexed']
|
||||
);
|
||||
|
||||
// older versions
|
||||
@ -680,7 +683,7 @@ function listBoards($just_uri = false, $indexed_only = false) {
|
||||
return $boards;
|
||||
|
||||
if (!$just_uri) {
|
||||
$query = query("SELECT ``boards``.`uri` uri, ``boards``.`title` title, ``boards``.`subtitle` subtitle, ``board_create``.`time` time, ``boards``.`indexed` indexed FROM ``boards``" . ( $indexed_only ? " WHERE `indexed` = 1 " : "" ) . "LEFT JOIN ``board_create`` ON ``boards``.`uri` = ``board_create``.`uri` ORDER BY ``boards``.`uri`") or error(db_error());
|
||||
$query = query("SELECT ``boards``.`uri` uri, ``boards``.`title` title, ``boards``.`subtitle` subtitle, ``board_create``.`time` time, ``boards``.`indexed` indexed, ``boards``.`sfw` sfw FROM ``boards``" . ( $indexed_only ? " WHERE `indexed` = 1 " : "" ) . "LEFT JOIN ``board_create`` ON ``boards``.`uri` = ``board_create``.`uri` ORDER BY ``boards``.`uri`") or error(db_error());
|
||||
$boards = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
$boards = array();
|
||||
@ -1091,9 +1094,9 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
|
||||
|
||||
if (!$post['thread']) {
|
||||
// Delete thread HTML page
|
||||
file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['id']));
|
||||
file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $post['id']));
|
||||
file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
|
||||
@file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['id']));
|
||||
@file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $post['id']));
|
||||
@file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
|
||||
|
||||
$antispam_query = prepare('DELETE FROM ``antispam`` WHERE `board` = :board AND `thread` = :thread');
|
||||
$antispam_query->bindValue(':board', $board['uri']);
|
||||
@ -1106,9 +1109,9 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
|
||||
if ($post['files']) {
|
||||
// Delete file
|
||||
foreach (json_decode($post['files']) as $i => $f) {
|
||||
if ($f->file !== 'deleted') {
|
||||
file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['img'] . $f->file);
|
||||
file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['thumb'] . $f->thumb);
|
||||
if (isset($f->file, $f->thumb) && $f->file !== 'deleted') {
|
||||
@file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['img'] . $f->file);
|
||||
@file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['thumb'] . $f->thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1329,23 +1332,26 @@ function getPages($mod=false) {
|
||||
|
||||
// Stolen with permission from PlainIB (by Frank Usrs)
|
||||
function make_comment_hex($str) {
|
||||
global $config;
|
||||
// remove cross-board citations
|
||||
// the numbers don't matter
|
||||
$str = preg_replace('!>>>/[A-Za-z0-9]+/!', '', $str);
|
||||
$str = preg_replace("!>>>/[A-Za-z0-9]+/!", '', $str);
|
||||
|
||||
if (function_exists('iconv')) {
|
||||
// remove diacritics and other noise
|
||||
// FIXME: this removes cyrillic entirely
|
||||
$oldstr = $str;
|
||||
$str = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
||||
if (!$str) $str = $oldstr;
|
||||
if ($config['robot_enable']) {
|
||||
if (function_exists('iconv')) {
|
||||
// remove diacritics and other noise
|
||||
// FIXME: this removes cyrillic entirely
|
||||
$oldstr = $str;
|
||||
$str = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
||||
if (!$str) $str = $oldstr;
|
||||
}
|
||||
|
||||
$str = strtolower($str);
|
||||
|
||||
// strip all non-alphabet characters
|
||||
$str = preg_replace('/[^a-z]/', '', $str);
|
||||
}
|
||||
|
||||
$str = strtolower($str);
|
||||
|
||||
// strip all non-alphabet characters
|
||||
$str = preg_replace('/[^a-z]/', '', $str);
|
||||
|
||||
return md5($str);
|
||||
}
|
||||
|
||||
@ -1701,7 +1707,7 @@ function extract_modifiers($body) {
|
||||
return $modifiers;
|
||||
}
|
||||
|
||||
function markup(&$body, $track_cites = false) {
|
||||
function markup(&$body, $track_cites = false, $op = false) {
|
||||
global $board, $config, $markup_urls;
|
||||
|
||||
$modifiers = extract_modifiers($body);
|
||||
@ -1739,6 +1745,9 @@ function markup(&$body, $track_cites = false) {
|
||||
|
||||
if ($num_links > $config['max_links'])
|
||||
error($config['error']['toomanylinks']);
|
||||
|
||||
if ($num_links < $config['min_links'] && $op)
|
||||
error(sprintf($config['error']['notenoughlinks'], $config['min_links']));
|
||||
}
|
||||
|
||||
if ($config['markup_repair_tidy'])
|
||||
@ -2427,6 +2436,8 @@ function diceRoller($post) {
|
||||
}
|
||||
|
||||
function less_ip($ip) {
|
||||
global $config;
|
||||
|
||||
$ipv6 = (strstr($ip, ':') !== false);
|
||||
$has_range = (strstr($ip, '/') !== false);
|
||||
|
||||
@ -2446,7 +2457,15 @@ function less_ip($ip) {
|
||||
}
|
||||
|
||||
$final = inet_ntop($in_addr & $mask);
|
||||
return str_replace(array(':0', '.0'), array(':x', '.x'), $final) . (isset($range) ? '/'.$range : '');
|
||||
$masked = str_replace(array(':0', '.0'), array(':x', '.x'), $final);
|
||||
|
||||
if ($config['hash_masked_ip']) {
|
||||
$masked = substr(sha1(sha1($masked) . $config['secure_trip_salt']), 0, 10);
|
||||
}
|
||||
|
||||
$masked .= (isset($range) ? '/'.$range : '');
|
||||
|
||||
return $masked;
|
||||
}
|
||||
|
||||
function less_hostmask($hostmask) {
|
||||
|
@ -61,6 +61,7 @@
|
||||
$config['spoiler_images'] = true;
|
||||
$config['image_reject_repost'] = true;
|
||||
$config['allowed_ext_files'][] = 'webm';
|
||||
$config['allowed_ext_files'][] = 'mp4';
|
||||
$config['webm']['use_ffmpeg'] = true;
|
||||
$config['webm']['allow_audio'] = true;
|
||||
$config['webm']['max_length'] = 60 * 15;
|
||||
@ -73,7 +74,7 @@
|
||||
$config['mod']['capcode'][MOD] = array('Board Owner');
|
||||
$config['mod']['capcode'][GLOBALVOLUNTEER] = array('Global Volunteer');
|
||||
$config['custom_capcode']['Admin'] = array(
|
||||
'<span class="capcode" style="color:blue;font-weight:bold"> <i class="fa fa-wheelchair"></i> %s</span>',
|
||||
'<span class="capcode" title="This post is written by the global 8chan.co administrator."> <i class="fa fa-wheelchair" style="color:blue;"></i> <span style="color:red">8chan.co Administrator</span></span>',
|
||||
);
|
||||
//$config['mod']['view_banlist'] = GLOBALVOLUNTEER;
|
||||
$config['mod']['recent_reports'] = 65535;
|
||||
@ -90,7 +91,9 @@
|
||||
//$config['default_stylesheet'] = array('Notsuba', 'notsuba.css');
|
||||
$config['additional_javascript'][] = 'js/jquery.min.js';
|
||||
$config['additional_javascript'][] = 'js/jquery.mixitup.min.js';
|
||||
$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
|
||||
$config['additional_javascript'][] = 'js/catalog.js';
|
||||
$config['additional_javascript'][] = 'js/captcha.js';
|
||||
$config['additional_javascript'][] = 'js/jquery.tablesorter.min.js';
|
||||
$config['additional_javascript'][] = 'js/options.js';
|
||||
$config['additional_javascript'][] = 'js/style-select.js';
|
||||
@ -135,6 +138,9 @@
|
||||
$config['additional_javascript'][] = 'js/youtube.js';
|
||||
$config['additional_javascript'][] = 'js/comment-toolbar.js';
|
||||
$config['additional_javascript'][] = 'js/catalog-search.js';
|
||||
$config['additional_javascript'][] = 'js/thread-stats.js';
|
||||
$config['additional_javascript'][] = 'js/quote-selection.js';
|
||||
$config['additional_javascript'][] = 'js/twemoji/twemoji.js';
|
||||
|
||||
//$config['font_awesome_css'] = '/netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css';
|
||||
|
||||
@ -146,16 +152,18 @@
|
||||
$config['markup'][] = array("/\[spoiler\](.+?)\[\/spoiler\]/", "<span class=\"spoiler\">\$1</span>");
|
||||
$config['markup'][] = array("/~~(.+?)~~/", "<s>\$1</s>");
|
||||
$config['markup'][] = array("/__(.+?)__/", "<u>\$1</u>");
|
||||
$config['markup'][] = array("/###([^\s']+)###/", "<a href='/boards.html#\$1'>###\$1###</a>");
|
||||
|
||||
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
|
||||
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'news+'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
|
||||
//$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'int'), array('v', 'a', 'tg', 'fit', 'pol', 'tech', 'mu', 'co', 'sp', 'boards'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
|
||||
|
||||
$config['footer'][] = 'All posts on 8chan.co are the responsibility of the individual poster and not the administration of 8chan.co, pursuant to 47 U.S.C. § 230.';
|
||||
$config['footer'][] = 'We have not been served any secret court orders and are not under any gag orders.';
|
||||
$config['footer'][] = 'Contribute to 8chan.co development at <a href="https://github.com/ctrlcctrlv/8chan">github</a>';
|
||||
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a> or use the "Global Report" functionality on every page.';
|
||||
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a>.';
|
||||
|
||||
$config['search']['enable'] = true;
|
||||
|
||||
//$config['debug'] = true;
|
||||
$config['syslog'] = true;
|
||||
|
||||
$config['wordfilters'][] = array('\rule', ''); // 'true' means it's a regular expression
|
||||
@ -189,6 +197,10 @@
|
||||
);
|
||||
|
||||
$config['gzip_static'] = false;
|
||||
$config['hash_masked_ip'] = true;
|
||||
$config['force_subject_op'] = false;
|
||||
$config['min_links'] = 0;
|
||||
$config['min_body'] = 0;
|
||||
// 8chan specific mod pages
|
||||
require '8chan-mod-pages.php';
|
||||
|
||||
|
@ -30,8 +30,17 @@ function is_valid_webm($ffprobe_out) {
|
||||
if (empty($ffprobe_out))
|
||||
return array('code' => 1, 'msg' => $config['error']['genwebmerror']);
|
||||
|
||||
if ($ffprobe_out['format']['format_name'] != 'matroska,webm')
|
||||
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
|
||||
$extension = pathinfo($ffprobe_out['format']['filename'], PATHINFO_EXTENSION);
|
||||
|
||||
if ($extension === 'webm') {
|
||||
if ($ffprobe_out['format']['format_name'] != 'matroska,webm')
|
||||
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
|
||||
} elseif ($extension === 'mp4') {
|
||||
if ($ffprobe_out['streams'][0]['codec_name'] != 'h264' && $ffprobe_out['streams'][1]['codec_name'] != 'aac')
|
||||
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
|
||||
} else {
|
||||
return array('code' => 1, 'msg' => $config['error']['genwebmerror']);
|
||||
}
|
||||
|
||||
if ((count($ffprobe_out['streams']) > 1) && (!$config['webm']['allow_audio']))
|
||||
return array('code' => 3, 'msg' => $config['error']['webmhasaudio']);
|
||||
@ -49,11 +58,11 @@ function make_webm_thumbnail($filename, $thumbnail, $width, $height) {
|
||||
$filename = escapeshellarg($filename);
|
||||
$thumbnail = escapeshellarg($thumbnail); // Should be safe by default but you
|
||||
// can never be too safe.
|
||||
|
||||
$ffmpeg = $config['webm']['ffmpeg_path'];
|
||||
|
||||
$ret = 0;
|
||||
$ffmpeg_out = array();
|
||||
exec("$ffmpeg -strict -2 -i $filename -v quiet -ss 00:00:00 -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnail 2>&1", $ffmpeg_out, $ret);
|
||||
|
||||
exec("$ffmpeg -strict -2 -i $filename -v quiet -ss 00:00:00 -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnail 2>&1");
|
||||
|
||||
return count($ffmpeg_out);
|
||||
return $ret;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
function postHandler($post) {
|
||||
global $board, $config;
|
||||
|
||||
if ($post->has_file) foreach ($post->files as &$file) if ($file->extension == 'webm') {
|
||||
if ($post->has_file) foreach ($post->files as &$file) if ($file->extension == 'webm' || $file->extension == 'mp4') {
|
||||
if ($config['webm']['use_ffmpeg']) {
|
||||
require_once dirname(__FILE__) . '/ffmpeg.php';
|
||||
$webminfo = get_webm_info($file->file_path);
|
||||
@ -21,7 +21,7 @@ function postHandler($post) {
|
||||
$file = set_thumbnail_dimensions($post, $file);
|
||||
$tn_path = $board['dir'] . $config['dir']['thumb'] . $file->file_id . '.jpg';
|
||||
|
||||
if(false == make_webm_thumbnail($file->file_path, $tn_path, $file->thumbwidth, $file->thumbheight)) {
|
||||
if(0 == make_webm_thumbnail($file->file_path, $tn_path, $file->thumbwidth, $file->thumbheight)) {
|
||||
$file->thumb = $file->file_id . '.jpg';
|
||||
}
|
||||
else {
|
||||
|
@ -689,7 +689,7 @@ msgstr ""
|
||||
msgid "You seem to have mistyped the verification."
|
||||
msgstr ""
|
||||
|
||||
#. Moderator errors
|
||||
#. Volunteer errors
|
||||
#: ../../../../inc/config.php:906 ../../../../inc/config.php:1011
|
||||
#: ../../../../inc/config.php:1002 ../../../../inc/config.php:1004
|
||||
#: ../../../../inc/config.php:1006 ../../../../inc/config.php:1022
|
||||
@ -879,7 +879,7 @@ msgstr ""
|
||||
#: ../../../../inc/mod/pages.php:671 ../../../../inc/mod/pages.php:698
|
||||
#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:300
|
||||
#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:75
|
||||
msgid "Moderation log"
|
||||
msgid "Volunteer log"
|
||||
msgstr ""
|
||||
|
||||
#. line 104
|
||||
@ -3489,7 +3489,7 @@ msgid "There are no active posts."
|
||||
msgstr ""
|
||||
|
||||
#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:49
|
||||
msgid "Show only bans from boards I moderate"
|
||||
msgid "Show only bans from boards I volunteer on"
|
||||
msgstr ""
|
||||
|
||||
#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:55
|
||||
|
@ -495,7 +495,15 @@ function mod_new_board() {
|
||||
if (openBoard($_POST['uri'])) {
|
||||
error(sprintf($config['error']['boardexists'], $board['url']));
|
||||
}
|
||||
|
||||
foreach ($config['banned_boards'] as $i => $w) {
|
||||
if ($w[0] !== '/') {
|
||||
if (strpos($_POST['uri'],$w) !== false)
|
||||
error(_("Cannot create board with banned word $w"));
|
||||
} else {
|
||||
if (preg_match($w,$_POST['uri']))
|
||||
error(_("Cannot create board matching banned pattern $w"));
|
||||
}
|
||||
}
|
||||
$query = prepare('INSERT INTO ``boards`` (``uri``, ``title``, ``subtitle``) VALUES (:uri, :title, :subtitle)');
|
||||
$query->bindValue(':uri', $_POST['uri']);
|
||||
$query->bindValue(':title', $_POST['title']);
|
||||
@ -625,7 +633,7 @@ function mod_news($page_no = 1) {
|
||||
|
||||
rebuildThemes('news');
|
||||
|
||||
header('Location: ?/news#' . $pdo->lastInsertId(), true, $config['redirect_http']);
|
||||
header('Location: ?/edit_news#' . $pdo->lastInsertId(), true, $config['redirect_http']);
|
||||
}
|
||||
|
||||
$query = prepare("SELECT * FROM ``news`` ORDER BY `id` DESC LIMIT :offset, :limit");
|
||||
@ -638,14 +646,14 @@ function mod_news($page_no = 1) {
|
||||
error($config['error']['404']);
|
||||
|
||||
foreach ($news as &$entry) {
|
||||
$entry['delete_token'] = make_secure_link_token('news/delete/' . $entry['id']);
|
||||
$entry['delete_token'] = make_secure_link_token('edit_news/delete/' . $entry['id']);
|
||||
}
|
||||
|
||||
$query = prepare("SELECT COUNT(*) FROM ``news``");
|
||||
$query->execute() or error(db_error($query));
|
||||
$count = $query->fetchColumn();
|
||||
|
||||
mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('news')));
|
||||
mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('edit_news')));
|
||||
}
|
||||
|
||||
function mod_news_delete($id) {
|
||||
@ -660,7 +668,7 @@ function mod_news_delete($id) {
|
||||
|
||||
modLog('Deleted a news entry');
|
||||
|
||||
header('Location: ?/news', true, $config['redirect_http']);
|
||||
header('Location: ?/edit_news', true, $config['redirect_http']);
|
||||
}
|
||||
|
||||
function mod_log($page_no = 1) {
|
||||
@ -1591,12 +1599,13 @@ function mod_edit_post($board, $edit_raw_html, $postID) {
|
||||
error($config['error']['404']);
|
||||
|
||||
if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) {
|
||||
$trip = isset($_POST['remove_trip']) ? ' `trip` = NULL,' : '';
|
||||
if ($edit_raw_html)
|
||||
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup, `edited_at` = NOW() WHERE `id` = :id', $board));
|
||||
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name,'. $trip .' `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup, `edited_at` = NOW() WHERE `id` = :id', $board));
|
||||
else
|
||||
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body_nomarkup` = :body, `edited_at` = NOW() WHERE `id` = :id', $board));
|
||||
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name,'. $trip .' `email` = :email, `subject` = :subject, `body_nomarkup` = :body, `edited_at` = NOW() WHERE `id` = :id', $board));
|
||||
$query->bindValue(':id', $postID);
|
||||
$query->bindValue('name', $_POST['name']);
|
||||
$query->bindValue(':name', $_POST['name'] ? $_POST['name'] : $config['anonymous']);
|
||||
$query->bindValue(':email', $_POST['email']);
|
||||
$query->bindValue(':subject', $_POST['subject']);
|
||||
$query->bindValue(':body', $_POST['body']);
|
||||
@ -2987,6 +2996,9 @@ function mod_report_clean( $global_reports, $board, $unclean, $post, $global, $l
|
||||
$log_action = ($unclean ? "Closed" : "Re-opened" );
|
||||
$log_scope = ($local && $global ? "local and global" : ($local ? "local" : "global" ) );
|
||||
modLog( "{$log_action} reports for post #{$post} in {$log_scope}.", $board);
|
||||
if ($config['cache']['enabled']) {
|
||||
cache::delete("post_clean_{$board}_{$post}");
|
||||
}
|
||||
|
||||
rebuildPost( $post );
|
||||
}
|
||||
|
23
install.sql
@ -67,6 +67,8 @@ CREATE TABLE IF NOT EXISTS `boards` (
|
||||
`subtitle` tinytext,
|
||||
`indexed` boolean default true,
|
||||
`public_bans` boolean default true,
|
||||
`8archive` boolean default false,
|
||||
`sfw` boolean default false,
|
||||
PRIMARY KEY (`uri`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
@ -75,13 +77,6 @@ CREATE TABLE IF NOT EXISTS `board_create` (
|
||||
`uri` text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
--
|
||||
-- Dumping data for table `boards`
|
||||
--
|
||||
|
||||
INSERT INTO `boards` VALUES
|
||||
('b', 'Random', NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
@ -227,6 +222,7 @@ CREATE TABLE IF NOT EXISTS `reports` (
|
||||
`board` varchar(58) CHARACTER SET utf8 DEFAULT NULL,
|
||||
`post` int(11) NOT NULL,
|
||||
`reason` text NOT NULL,
|
||||
`local` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`global` tinyint(1) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
|
||||
@ -322,6 +318,19 @@ CREATE TABLE `post_clean` (
|
||||
UNIQUE KEY `clean_id_UNIQUE` (`clean_id`)
|
||||
);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `board_tags`
|
||||
--
|
||||
|
||||
CREATE TABLE `board_tags` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`uri` varchar(30) DEFAULT NULL,
|
||||
`tag` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
|
16
js/ajax.js
@ -55,7 +55,7 @@ $(window).ready(function() {
|
||||
}
|
||||
return xhr;
|
||||
},
|
||||
success: function(post_response) {
|
||||
success: function(post_response, textStatus, xhr) {
|
||||
if (post_response.error) {
|
||||
if (post_response.banned) {
|
||||
// You are banned. Must post the form normally so the user can see the ban message.
|
||||
@ -109,23 +109,15 @@ $(window).ready(function() {
|
||||
$(form).find('input[type="submit"]').val(_('Posted...'));
|
||||
$(document).trigger("ajax_after_post", post_response);
|
||||
} else {
|
||||
console.log(xhr);
|
||||
alert(_('An unknown error occured when posting!'));
|
||||
$(form).find('input[type="submit"]').val(submit_txt);
|
||||
$(form).find('input[type="submit"]').removeAttr('disabled');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, er) {
|
||||
// An error occured
|
||||
do_not_ajax = true;
|
||||
$(form).find('input[type="submit"]').each(function() {
|
||||
var $replacement = $('<input type="hidden">');
|
||||
$replacement.attr('name', $(this).attr('name'));
|
||||
$replacement.val(submit_txt);
|
||||
$(this)
|
||||
.after($replacement)
|
||||
.replaceWith($('<input type="button">').val(submit_txt));
|
||||
});
|
||||
$(form).submit();
|
||||
console.log(xhr);
|
||||
alert(_('The server returned an error or truncated response -- your post was probably still submitted. If it wasn\'t, 8chan.co might be experiencing issues right now -- please try your post again later.'));
|
||||
},
|
||||
data: formData,
|
||||
cache: false,
|
||||
|
44
js/captcha.js
Normal file
@ -0,0 +1,44 @@
|
||||
var tout;
|
||||
|
||||
function redo_events(provider, extra) {
|
||||
$('.captcha .captcha_text, textarea[id="body"]').off("focus").one("focus", function() { actually_load_captcha(provider, extra); });
|
||||
}
|
||||
|
||||
function actually_load_captcha(provider, extra) {
|
||||
$('.captcha .captcha_text, textarea[id="body"]').off("focus");
|
||||
|
||||
if (tout !== undefined) {
|
||||
clearTimeout(tout);
|
||||
}
|
||||
|
||||
$.getJSON(provider, {mode: 'get', extra: extra}, function(json) {
|
||||
$(".captcha .captcha_cookie").val(json.cookie);
|
||||
$(".captcha .captcha_html").html(json.captchahtml);
|
||||
|
||||
setTimeout(function() {
|
||||
redo_events(provider, extra);
|
||||
}, json.expires_in * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function load_captcha(provider, extra) {
|
||||
$(function() {
|
||||
$(".captcha>td").html("<input class='captcha_text' type='text' name='captcha_text' size='25' maxlength='6' autocomplete='off'>"+
|
||||
"<input class='captcha_cookie' name='captcha_cookie' type='hidden'>"+
|
||||
"<div class='captcha_html'></div>");
|
||||
|
||||
$("#quick-reply .captcha .captcha_text").prop("placeholder", _("Verification"));
|
||||
|
||||
$(".captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); });
|
||||
$(document).on("ajax_after_post", function() { actually_load_captcha(provider, extra); });
|
||||
redo_events(provider, extra);
|
||||
|
||||
$(window).on("quick-reply", function() {
|
||||
redo_events(provider, extra);
|
||||
$("#quick-reply .captcha .captcha_html").html($("form:not(#quick-reply) .captcha .captcha_html").html());
|
||||
$("#quick-reply .captcha .captcha_cookie").val($("form:not(#quick-reply) .captcha .captcha_cookie").html());
|
||||
$("#quick-reply .captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
35
js/disable-styles.js
Normal file
@ -0,0 +1,35 @@
|
||||
/* Adds a checkbox in the General options tab to disable and enable board style sheets. */
|
||||
|
||||
$(document).ready(function () {
|
||||
var disableStyles = localStorage['disablestylesheet'] ? true : false;
|
||||
/* only search for and disable board stylesheets if the user is on a page that uses them */
|
||||
if(active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || active_page == 'catalog')
|
||||
{
|
||||
var i = 0
|
||||
while(i<document.styleSheets.length) {
|
||||
var protAndHost = window.location.protocol + '//' + window.location.host
|
||||
if(document.styleSheets[i].href == protAndHost + $('link[id="stylesheet"]').attr('href'))
|
||||
{
|
||||
var sheet = i
|
||||
document.styleSheets[sheet].disabled = disableStyles
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
/* add the option on all pages so that the user doesn't need to goto a board to toggle it */
|
||||
if (window.Options && Options.get_tab('general')){
|
||||
Options.extend_tab('general','<label id=\'disablestyle\'><input type=\'checkbox\' />' + ' Disable board specific style sheets' + '</label>')
|
||||
$('#disablestyle').find('input').prop('checked', disableStyles)
|
||||
}
|
||||
|
||||
$('#disablestyle').on('change', function() {
|
||||
if(disableStyles) {
|
||||
delete localStorage.disablestylesheet
|
||||
} else {
|
||||
localStorage.disablestylesheet = true
|
||||
}
|
||||
disableStyles =! disableStyles
|
||||
if(active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || active_page == 'catalog') document.styleSheets[sheet].disabled = disableStyles
|
||||
})
|
||||
})
|
@ -26,6 +26,9 @@ $(function() {
|
||||
var content = $(data).find('#'+url.split('#')[1]).parent().parent().find(".body").first().html();
|
||||
|
||||
body.html(content);
|
||||
|
||||
var post = $(body).parents('.post');
|
||||
$(document).trigger('new_post', post);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -204,13 +204,13 @@ function setupVideo(thumb, url) {
|
||||
function setupVideosIn(element) {
|
||||
var thumbs = element.querySelectorAll("a.file");
|
||||
for (var i = 0; i < thumbs.length; i++) {
|
||||
if (/\.webm$/.test(thumbs[i].pathname)) {
|
||||
if (/(\.webm)|(\.mp4)$/.test(thumbs[i].pathname)) {
|
||||
setupVideo(thumbs[i], thumbs[i].href);
|
||||
} else {
|
||||
var m = thumbs[i].search.match(/\bv=([^&]*)/);
|
||||
if (m != null) {
|
||||
var url = decodeURIComponent(m[1]);
|
||||
if (/\.webm$/.test(url)) setupVideo(thumbs[i], url);
|
||||
if (/(\.webm)|(\.mp4)$/.test(url)) setupVideo(thumbs[i], url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,6 @@ if (active_page == 'thread' || active_page == 'index' || active_page == 'catalog
|
||||
$(document).ready(function(){
|
||||
var favorites = JSON.parse(localStorage.favorites);
|
||||
var is_board_favorite = ~$.inArray(board_name, favorites);
|
||||
console.log(is_board_favorite);
|
||||
|
||||
$('header>h1').append('<a id="favorite-star" href="#" data-active="'+(is_board_favorite ? 'true' : 'false')+'" style="color: '+(is_board_favorite ? 'yellow' : 'grey')+'; text-decoration:none">\u2605</span>');
|
||||
add_favorites();
|
||||
|
@ -19,7 +19,7 @@ onready(function(){
|
||||
for (var i = 0; i < link.length; i++) {
|
||||
if (typeof link[i] == "object" && link[i].childNodes && typeof link[i].childNodes[0] !== 'undefined' && link[i].childNodes[0].src && link[i].childNodes[0].className.match(/post-image/) && !link[i].className.match(/file/)) {
|
||||
link[i].onclick = function(e) {
|
||||
var img, post_body, still_open;
|
||||
var img, post_body, still_open, canvas;
|
||||
var thumb = this.childNodes[0];
|
||||
var padding = 5;
|
||||
var boardlist = $('.boardlist')[0];
|
||||
@ -43,8 +43,10 @@ onready(function(){
|
||||
this.dataset.expanded = 'true';
|
||||
|
||||
if (thumb.tagName === 'CANVAS') {
|
||||
this.removeChild(thumb);
|
||||
thumb.style.display = 'block';
|
||||
canvas = thumb;
|
||||
thumb = thumb.nextSibling;
|
||||
this.removeChild(canvas);
|
||||
canvas.style.display = 'block';
|
||||
}
|
||||
|
||||
thumb.style.opacity = '0.4';
|
||||
@ -72,10 +74,10 @@ onready(function(){
|
||||
|
||||
if (still_open > 1) {
|
||||
if (e.target.getBoundingClientRect().top - padding < 0)
|
||||
$('body').scrollTop($(e.target).parent().parent().offset().top - padding);
|
||||
$(document).scrollTop($(e.target).parent().parent().offset().top - padding);
|
||||
} else {
|
||||
if (post_body[0].getBoundingClientRect().top - padding < 0)
|
||||
$('body').scrollTop(post_body.offset().top - padding);
|
||||
$(document).scrollTop(post_body.offset().top - padding);
|
||||
}
|
||||
}
|
||||
|
||||
|
4
js/jquery.tablesorter.min.js
vendored
Normal file
@ -13,6 +13,8 @@
|
||||
*/
|
||||
|
||||
function unanimate_gif(e) {
|
||||
if ($(e).closest('.thread').children('.thread-hidden').length > 0) return;
|
||||
|
||||
if (active_page === "catalog")
|
||||
var c = $('<canvas class="thread-image"></canvas>');
|
||||
else
|
||||
|
@ -73,6 +73,7 @@ onready(function(){
|
||||
.css('font-style', 'normal')
|
||||
.css('z-index', '100')
|
||||
.css('left', '0')
|
||||
.css('margin-left', '')
|
||||
.addClass('reply').addClass('post')
|
||||
.appendTo(link.closest('div.post'))
|
||||
|
||||
|
@ -87,7 +87,7 @@
|
||||
-webkit-box-sizing:border-box;\
|
||||
-moz-box-sizing: border-box;\
|
||||
font-size: 10pt;\
|
||||
resize: vertical;\
|
||||
resize: both;\
|
||||
}\
|
||||
#quick-reply input, #quick-reply select, #quick-reply textarea {\
|
||||
margin: 0 0 1px 0;\
|
||||
@ -149,14 +149,14 @@
|
||||
.removeAttr('size')
|
||||
.attr('placeholder', $th.clone().children().remove().end().text());
|
||||
}
|
||||
|
||||
|
||||
// Move anti-spam nonsense and remove <th>
|
||||
$th.contents().filter(function() {
|
||||
return this.nodeType == 3; // Node.TEXT_NODE
|
||||
}).remove();
|
||||
$th.contents().appendTo($dummyStuff);
|
||||
$th.remove();
|
||||
|
||||
|
||||
if ($td.find('input[name="password"]').length) {
|
||||
// Hide password field
|
||||
$(this).hide();
|
||||
@ -280,7 +280,7 @@
|
||||
|
||||
$postForm.find('textarea[name="body"]').removeAttr('id').removeAttr('cols').attr('placeholder', _('Comment'));
|
||||
|
||||
$postForm.find('textarea:not([name="body"]),input[type="hidden"]').removeAttr('id').appendTo($dummyStuff);
|
||||
$postForm.find('textarea:not([name="body"]),input[type="hidden"]:not(.captcha_cookie)').removeAttr('id').appendTo($dummyStuff);
|
||||
|
||||
$postForm.find('br').remove();
|
||||
$postForm.find('table').prepend('<tr><th colspan="2">\
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
onready(function(){
|
||||
var showBackLinks = function() {
|
||||
var reply_id = $(this).attr('id').replace(/^reply_/, '');
|
||||
var reply_id = $(this).attr('id').replace(/^(reply|op)_/, '');
|
||||
|
||||
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
|
||||
var id, post, $mentioned;
|
||||
@ -26,6 +26,9 @@ onready(function(){
|
||||
return;
|
||||
|
||||
$post = $('#reply_' + id);
|
||||
if($post.length == 0)
|
||||
$post = $('#op_' + id);
|
||||
|
||||
if($post.length == 0)
|
||||
return;
|
||||
|
||||
@ -38,7 +41,8 @@ onready(function(){
|
||||
|
||||
var $link = $('<a class="mentioned-' + reply_id + '" onclick="highlightReply(\'' + reply_id + '\');" href="#' + reply_id + '">>>' +
|
||||
reply_id + '</a>');
|
||||
$link.appendTo($mentioned)
|
||||
$link.appendTo($mentioned);
|
||||
$link.after(" ");
|
||||
|
||||
if (window.init_hover) {
|
||||
$link.each(init_hover);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* thread-stats.js
|
||||
* - Adds statistics of the thread below the posts area
|
||||
* - Shows ID post count beside each postID on hover
|
||||
@ -8,8 +8,9 @@
|
||||
* $config['additional_javascript'][] = 'js/thread-stats.js';
|
||||
*/
|
||||
if (active_page == 'thread') {
|
||||
$(document).ready(function(){
|
||||
//check if page uses unique ID
|
||||
var IDsupport = ($('.poster_id').length > 0);
|
||||
var IDsupport = ($('.poster_id').length > 0);
|
||||
var thread_id = (document.location.pathname + document.location.search).split('/');
|
||||
thread_id = thread_id[thread_id.length -1].split('+')[0].split('.')[0];
|
||||
|
||||
@ -49,9 +50,12 @@ if (active_page == 'thread') {
|
||||
ids[opID] = 0;
|
||||
}
|
||||
ids[opID]++;
|
||||
var cur = op.find('>.intro >.poster_id');
|
||||
cur.find('+.posts_by_id').remove();
|
||||
cur.after('<span class="posts_by_id"> ('+ ids[cur.text()] +')</span>');
|
||||
replies.each(function(){
|
||||
var cur = $(this).find('> .intro > .poster_id');
|
||||
cur.find('+ .posts_by_id').remove();
|
||||
cur = $(this).find('>.intro >.poster_id');
|
||||
cur.find('+.posts_by_id').remove();
|
||||
cur.after('<span class="posts_by_id"> ('+ ids[cur.text()] +')</span>');
|
||||
});
|
||||
var size = function(obj) {
|
||||
@ -64,7 +68,7 @@ if (active_page == 'thread') {
|
||||
$('#thread_stats_uids').text(size(ids));
|
||||
}
|
||||
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
|
||||
var found, page = 'Pruned or Deleted';
|
||||
var found, page = '???';
|
||||
for (var i=0;data[i];i++){
|
||||
var threads = data[i].threads;
|
||||
for (var j=0; threads[j]; j++){
|
||||
@ -78,13 +82,14 @@ if (active_page == 'thread') {
|
||||
}
|
||||
$('#thread_stats_page').text(page);
|
||||
if (!found) $('#thread_stats_page').css('color','red');
|
||||
else $('#thread_stats_page').css('color','');
|
||||
});
|
||||
}
|
||||
// load the current page the thread is on.
|
||||
// uses ajax call so it gets loaded on a delay (depending on network resources available)
|
||||
var thread_stats_page_timer = setInterval(function(){
|
||||
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
|
||||
var found, page = 'Pruned or Deleted';
|
||||
var found, page = '???';
|
||||
for (var i=0;data[i];i++){
|
||||
var threads = data[i].threads;
|
||||
for (var j=0; threads[j]; j++){
|
||||
@ -98,12 +103,12 @@ if (active_page == 'thread') {
|
||||
}
|
||||
$('#thread_stats_page').text(page);
|
||||
if (!found) $('#thread_stats_page').css('color','red');
|
||||
else $('#thread_stats_page').css('color','');
|
||||
});
|
||||
},30000);
|
||||
$(document).ready(function(){
|
||||
$('body').append('<style>.posts_by_id{display:none;}.poster_id:hover+.posts_by_id{display:initial}</style>');
|
||||
update_thread_stats();
|
||||
$('#update_thread').click(update_thread_stats);
|
||||
$(document).on('new_post',update_thread_stats);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -147,15 +147,16 @@ $(document).ready(function(){
|
||||
}
|
||||
|
||||
//Append the watchlist toggle button.
|
||||
$('.boardlist').append('<span>[ <a id="watchlist-toggle">watchlist</a> ]</span>');
|
||||
$('.boardlist').append('<span>[ <a class="watchlist-toggle" href="#">watchlist</a> ]</span>');
|
||||
//Append a watch thread button after every OP.
|
||||
$('.op>.intro').append('<a class="watchThread">[Watch Thread]</a>');
|
||||
$('.op>.intro').append('<a class="watchThread" href="#">[Watch Thread]</a>');
|
||||
|
||||
//Draw the watchlist, hidden.
|
||||
watchlist.render();
|
||||
|
||||
//Show or hide the watchlist.
|
||||
$('#watchlist-toggle').on('click', function(e) {
|
||||
$('.watchlist-toggle').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
//if ctrl+click, reset the watchlist.
|
||||
if (e.ctrlKey) {
|
||||
watchlist.render(true);
|
||||
@ -169,7 +170,8 @@ $(document).ready(function(){
|
||||
|
||||
//Trigger the watchlist add function.
|
||||
//The selector is passed as an argument in case the page is not a thread.
|
||||
$('.watchThread').on('click', function() {
|
||||
$('.watchThread').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
watchlist.add(this).render();
|
||||
});
|
||||
|
||||
|
@ -11,14 +11,28 @@
|
||||
*
|
||||
*/
|
||||
|
||||
if (active_page == 'thread' || active_page == 'ukko' || active_page == 'index')
|
||||
$(function() {
|
||||
if (window.Options && Options.get_tab('general')) {
|
||||
var selector = '#treeview-global>input';
|
||||
Options.extend_tab("general", "<label id='treeview-global'><input type='checkbox' /> "+_('Use tree view by default')+"</label>");
|
||||
$(selector).on('change', function() {
|
||||
if (localStorage.treeview === 'true') {
|
||||
localStorage.treeview = 'false';
|
||||
} else {
|
||||
localStorage.treeview = 'true';
|
||||
}
|
||||
});
|
||||
if (localStorage.treeview === 'true') {
|
||||
$(selector).attr('checked', 'checked');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (active_page == 'thread')
|
||||
$(function() {
|
||||
$('hr:first').before('<div id="treeview" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>');
|
||||
$('div#treeview a')
|
||||
.text(_('Tree view'))
|
||||
.click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var treeview = function(enable) {
|
||||
if (enable === true) {
|
||||
$('.post.reply').each(function(){
|
||||
var references = [];
|
||||
$(this).find('.body a').each(function(){
|
||||
@ -26,7 +40,6 @@ $(function() {
|
||||
references.push(parseInt($(this).html().replace('>>', '')));
|
||||
}
|
||||
});
|
||||
|
||||
var maxref = references.reduce(function(a,b) { return a > b ? a : b; }, 0);
|
||||
|
||||
var parent_post = $("#reply_"+maxref);
|
||||
@ -39,7 +52,24 @@ $(function() {
|
||||
|
||||
post.detach().css("margin-left", margin).insertAfter(parent_post.next());
|
||||
br.detach().insertAfter(post);
|
||||
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$('.post.reply').sort(function(a,b) {
|
||||
return parseInt(a.id.replace('reply_', '')) - parseInt(b.id.replace('reply_', ''));
|
||||
}).each(function () {
|
||||
var post = $(this);
|
||||
var br = post.next();
|
||||
post.detach().css('margin-left', '').appendTo('.thread');
|
||||
br.detach().insertAfter(post);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('hr:first').before('<div class="unimportant" style="text-align:right"><label for="treeview"><input type="checkbox" id="treeview"> '+_('Tree view')+'</label></div>');
|
||||
$('input#treeview').on('change', function(e) { treeview($(this).is(':checked')); });
|
||||
|
||||
if (localStorage.treeview === 'true') {
|
||||
treeview(true);
|
||||
$('input#treeview').attr('checked', true);
|
||||
}
|
||||
});
|
||||
|
BIN
js/twemoji/16x16/1f004.png
Normal file
After Width: | Height: | Size: 233 B |
BIN
js/twemoji/16x16/1f0cf.png
Normal file
After Width: | Height: | Size: 396 B |
BIN
js/twemoji/16x16/1f170.png
Normal file
After Width: | Height: | Size: 208 B |
BIN
js/twemoji/16x16/1f171.png
Normal file
After Width: | Height: | Size: 170 B |
BIN
js/twemoji/16x16/1f17e.png
Normal file
After Width: | Height: | Size: 196 B |
BIN
js/twemoji/16x16/1f17f.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
js/twemoji/16x16/1f18e.png
Normal file
After Width: | Height: | Size: 220 B |
BIN
js/twemoji/16x16/1f191.png
Normal file
After Width: | Height: | Size: 220 B |
BIN
js/twemoji/16x16/1f192.png
Normal file
After Width: | Height: | Size: 137 B |
BIN
js/twemoji/16x16/1f193.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
js/twemoji/16x16/1f194.png
Normal file
After Width: | Height: | Size: 182 B |
BIN
js/twemoji/16x16/1f195.png
Normal file
After Width: | Height: | Size: 197 B |
BIN
js/twemoji/16x16/1f196.png
Normal file
After Width: | Height: | Size: 221 B |
BIN
js/twemoji/16x16/1f197.png
Normal file
After Width: | Height: | Size: 203 B |
BIN
js/twemoji/16x16/1f198.png
Normal file
After Width: | Height: | Size: 452 B |
BIN
js/twemoji/16x16/1f199.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
js/twemoji/16x16/1f19a.png
Normal file
After Width: | Height: | Size: 231 B |
BIN
js/twemoji/16x16/1f1e6.png
Normal file
After Width: | Height: | Size: 208 B |
BIN
js/twemoji/16x16/1f1e7.png
Normal file
After Width: | Height: | Size: 170 B |
BIN
js/twemoji/16x16/1f1e8-1f1f3.png
Normal file
After Width: | Height: | Size: 257 B |
BIN
js/twemoji/16x16/1f1e8.png
Normal file
After Width: | Height: | Size: 194 B |
BIN
js/twemoji/16x16/1f1e9-1f1ea.png
Normal file
After Width: | Height: | Size: 120 B |
BIN
js/twemoji/16x16/1f1e9.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
js/twemoji/16x16/1f1ea-1f1f8.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
js/twemoji/16x16/1f1ea.png
Normal file
After Width: | Height: | Size: 147 B |
BIN
js/twemoji/16x16/1f1eb-1f1f7.png
Normal file
After Width: | Height: | Size: 110 B |
BIN
js/twemoji/16x16/1f1eb.png
Normal file
After Width: | Height: | Size: 148 B |
BIN
js/twemoji/16x16/1f1ec-1f1e7.png
Normal file
After Width: | Height: | Size: 217 B |
BIN
js/twemoji/16x16/1f1ec.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
js/twemoji/16x16/1f1ed.png
Normal file
After Width: | Height: | Size: 125 B |
BIN
js/twemoji/16x16/1f1ee-1f1f9.png
Normal file
After Width: | Height: | Size: 114 B |
BIN
js/twemoji/16x16/1f1ee.png
Normal file
After Width: | Height: | Size: 113 B |
BIN
js/twemoji/16x16/1f1ef-1f1f5.png
Normal file
After Width: | Height: | Size: 140 B |
BIN
js/twemoji/16x16/1f1ef.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
js/twemoji/16x16/1f1f0-1f1f7.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
js/twemoji/16x16/1f1f0.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
js/twemoji/16x16/1f1f1.png
Normal file
After Width: | Height: | Size: 131 B |
BIN
js/twemoji/16x16/1f1f2.png
Normal file
After Width: | Height: | Size: 242 B |
BIN
js/twemoji/16x16/1f1f3.png
Normal file
After Width: | Height: | Size: 188 B |
BIN
js/twemoji/16x16/1f1f4.png
Normal file
After Width: | Height: | Size: 196 B |
BIN
js/twemoji/16x16/1f1f5.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
js/twemoji/16x16/1f1f6.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
js/twemoji/16x16/1f1f7-1f1fa.png
Normal file
After Width: | Height: | Size: 119 B |
BIN
js/twemoji/16x16/1f1f7.png
Normal file
After Width: | Height: | Size: 190 B |
BIN
js/twemoji/16x16/1f1f8.png
Normal file
After Width: | Height: | Size: 217 B |
BIN
js/twemoji/16x16/1f1f9.png
Normal file
After Width: | Height: | Size: 133 B |
BIN
js/twemoji/16x16/1f1fa-1f1f8.png
Normal file
After Width: | Height: | Size: 134 B |
BIN
js/twemoji/16x16/1f1fa.png
Normal file
After Width: | Height: | Size: 169 B |
BIN
js/twemoji/16x16/1f1fb.png
Normal file
After Width: | Height: | Size: 200 B |
BIN
js/twemoji/16x16/1f1fc.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
js/twemoji/16x16/1f1fd.png
Normal file
After Width: | Height: | Size: 209 B |
BIN
js/twemoji/16x16/1f1fe.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
js/twemoji/16x16/1f1ff.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
js/twemoji/16x16/1f201.png
Normal file
After Width: | Height: | Size: 113 B |
BIN
js/twemoji/16x16/1f202.png
Normal file
After Width: | Height: | Size: 199 B |
BIN
js/twemoji/16x16/1f21a.png
Normal file
After Width: | Height: | Size: 248 B |
BIN
js/twemoji/16x16/1f22f.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
js/twemoji/16x16/1f232.png
Normal file
After Width: | Height: | Size: 224 B |
BIN
js/twemoji/16x16/1f233.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
js/twemoji/16x16/1f234.png
Normal file
After Width: | Height: | Size: 203 B |
BIN
js/twemoji/16x16/1f235.png
Normal file
After Width: | Height: | Size: 292 B |
BIN
js/twemoji/16x16/1f236.png
Normal file
After Width: | Height: | Size: 194 B |
BIN
js/twemoji/16x16/1f237.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
js/twemoji/16x16/1f238.png
Normal file
After Width: | Height: | Size: 138 B |
BIN
js/twemoji/16x16/1f239.png
Normal file
After Width: | Height: | Size: 214 B |
BIN
js/twemoji/16x16/1f23a.png
Normal file
After Width: | Height: | Size: 221 B |