diff --git a/create.php b/create.php index 59134bd8..309ac9eb 100644 --- a/create.php +++ b/create.php @@ -1,35 +1,17 @@ '._('Game').'' . $ayah->getPublisherHTML() . ''; -} - -if (!$cbRecaptcha){ - $recapcha_html = ''; -} else { - $recapcha_html = 'reCaptcha' . recaptcha_get_html($config['recaptcha_public'], NULL, TRUE) . ''; -} - +include '8chan-captcha/functions.php'; $password = base64_encode(openssl_random_pseudo_bytes(9)); -$body = Element("8chan/create.html", array("config" => $config, "password" => $password, "game_html" => $game_html, "recapcha_html" => $recapcha_html)); +$captcha = generate_captcha($config['captcha']['extra']); + +$body = Element("8chan/create.html", array("config" => $config, "password" => $password, "captcha" => $captcha)); echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Create your board"), "subtitle" => _("before someone else does"))); } @@ -41,26 +23,13 @@ $subtitle = $_POST['subtitle']; $username = $_POST['username']; $password = $_POST['password']; - $resp = ($cbRecaptcha) ? recaptcha_check_answer ($config['recaptcha_private'], - $_SERVER["REMOTE_ADDR"], - $_POST["recaptcha_challenge_field"], - $_POST["recaptcha_response_field"]):false; +$resp = file_get_contents($config['captcha']['provider_check'] . "?" . http_build_query([ + 'mode' => 'check', + 'text' => $_POST['captcha_text'], + 'extra' => $config['captcha']['extra'], + 'cookie' => $_POST['captcha_cookie'] +])); -if ($resp != false){ -$passedCaptcha = $resp->is_valid; -} else { -$passedCaptcha = true; -} - -if (!$ayah){ -$score = true; -} else { -$score = $ayah->scoreResult(); -} -if (!$score) - error(_('You failed the game')); -if (!$passedCaptcha) - error(_('You failed to enter the reCaptcha correctly')); if (!preg_match('/^[a-z0-9]{1,30}$/', $uri)) error(_('Invalid URI')); if (!(strlen($title) < 40)) @@ -69,6 +38,8 @@ if (!(strlen($subtitle) < 200)) error(_('Invalid subtitle')); if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $username)) error(_('Invalid username')); +if ($resp !== '1') + error($config['error']['captcha']); foreach (listBoards() as $i => $board) { if ($board['uri'] == $uri) diff --git a/dnsbls_bypass.php b/dnsbls_bypass.php index a37f1ca0..cf289b67 100644 --- a/dnsbls_bypass.php +++ b/dnsbls_bypass.php @@ -1,31 +1,26 @@ array('8.8.8.8'))); -$result = $dns->query(RECAPTCHA_VERIFY_SERVER, "A"); -if ($result and $result->answer[0]) { - $RECAPTCHA_VERIFY_SERVER_IP = $result->answer[0]->address; -} else { - $RECAPTCHA_VERIFY_SERVER_IP = RECAPTCHA_VERIFY_SERVER; -} +include '8chan-captcha/functions.php'; if ($_SERVER['REQUEST_METHOD'] === 'GET') { - $ayah_html = recaptcha_get_html($config['recaptcha_public'], NULL, TRUE); - $body = Element("8chan/dnsbls.html", array("config" => $config, "ayah_html" => $ayah_html)); + $captcha = generate_captcha($config['captcha']['extra']); + + $html = "{$captcha['html']}
+ +
"; + + $body = Element("8chan/dnsbls.html", array("config" => $config, "ayah_html" => $html)); echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Bypass DNSBL"), "subtitle" => _("Post even if blocked"))); } else { - $score = recaptcha_check_answer($config['recaptcha_private'], - $_SERVER["REMOTE_ADDR"], - $_POST["recaptcha_challenge_field"], - $_POST["recaptcha_response_field"], - array(), - $RECAPTCHA_VERIFY_SERVER_IP); + $resp = file_get_contents($config['captcha']['provider_check'] . "?" . http_build_query([ + 'mode' => 'check', + 'text' => $_POST['captcha_text'], + 'extra' => $config['captcha']['extra'], + 'cookie' => $_POST['captcha_cookie'] + ])); - if ($score->is_valid) { + if ($resp === '1') { $tor = checkDNSBL($_SERVER['REMOTE_ADDR']); if (!$tor) { $query = prepare('INSERT INTO ``dnsbl_bypass`` VALUES(:ip, NOW()) ON DUPLICATE KEY UPDATE `created`=NOW()'); diff --git a/faq.php b/faq.php index 646a9e6c..5da901db 100644 --- a/faq.php +++ b/faq.php @@ -143,6 +143,13 @@ Assuming the /b/ board, they are as follows:

Just read the data to get an idea of what is exposed and under what attribute names. It should be self explanatory.

Endpoints not listed here, like post.php, catalog.json or boards-top20.json are subject to change or removal at any time!

+

I would like to contribute a translation in my language.

+ +

Great! See this page for more information.

+ +

Are there any publicly available statistics?

+

Yes, take a look at http://stats.4ch.net/8chan/. +

I got an email from an @8chan.co email address, is that you?

8chan.co uses cock.li to manage our domain's email. cock.li allows anyone to create an email account @8chan.co.

That said, we have quite a few official 8chan.co email addresses. They are:

diff --git a/inc/config.php b/inc/config.php index 88984911..64b2836b 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1018,7 +1018,8 @@ // Width and height (and more?) of post flags. Can be overridden with the Tinyboard post modifier: // . - $config['flag_style'] = 'width:16px;height:11px;'; + // $config['flag_style'] = 'width:16px;height:11px;'; + $config['flag_style'] = ''; /* * ==================== diff --git a/inc/instance-config.php b/inc/instance-config.php index ef456aab..cfcb62de 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -142,7 +142,7 @@ $config['additional_javascript'][] = 'js/image-hover.js'; $config['additional_javascript'][] = 'js/auto-scroll.js'; $config['additional_javascript'][] = 'js/twemoji/twemoji.js'; - $config['additional_javascript'][] = 'js/multi-image.js'; + $config['additional_javascript'][] = 'js/file-selector.js'; // Oekaki (now depends on config.oekaki so can be in all scripts) $config['additional_javascript'][] = 'js/jquery-ui.custom.min.js'; $config['additional_javascript'][] = 'js/wPaint/lib/wColorPicker.min.js'; diff --git a/js/auto-reload.js b/js/auto-reload.js index a976a019..7340bf25 100644 --- a/js/auto-reload.js +++ b/js/auto-reload.js @@ -287,8 +287,6 @@ $(document).ready(function(){ }; $(window).scroll(function() { - recheck_activated(); - // if the newest post is not visible if($(this).scrollTop() + $(this).height() < $('div.post:last').position().top + $('div.post:last').height()) { diff --git a/js/file-selector.js b/js/file-selector.js new file mode 100644 index 00000000..6a7e6773 --- /dev/null +++ b/js/file-selector.js @@ -0,0 +1,183 @@ +/* + * file-selector.js - Add support for drag and drop file selection, and paste from clipbboard on supported browsers. + * + * Usage: + * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/file-selector.js'; + */ +function init_file_selector(max_images) { + +$(document).ready(function () { + // add options panel item + if (window.Options && Options.get_tab('general')) { + Options.extend_tab('general', ''); + + $('#file-drag-drop>input').on('click', function() { + if ($('#file-drag-drop>input').is(':checked')) { + localStorage.file_dragdrop = 'true'; + } else { + localStorage.file_dragdrop = 'false'; + } + }); + + if (typeof localStorage.file_dragdrop === 'undefined') localStorage.file_dragdrop = 'true'; + if (localStorage.file_dragdrop === 'true') $('#file-drag-drop>input').prop('checked', true); + } +}); + +// disabled by user, or incompatible browser. +if (localStorage.file_dragdrop == 'false' || !(window.URL.createObjectURL && window.File)) + return; + +// multipost not enabled +if (typeof max_images == 'undefined') { + var max_images = 1; +} + +var files = []; +$('#upload_file').hide(); // hide the original file selector +$('.dropzone-wrap').css('user-select', 'none').show(); // let jquery add browser specific prefix + +function addFile(file) { + if (files.length == max_images) + return; + + files.push(file); + addThumb(file); +} + +function removeFile(file) { + files.splice(files.indexOf(file), 1); +} + +function getThumbElement(file) { + return $('.tmb-container').filter(function(){return($(this).data('file-ref')==file);}); +} + +function addThumb(file) { + + var fileName = (file.name.length < 24) ? file.name : file.name.substr(0, 22) + '…'; + var fileType = file.type.split('/')[0]; + var fileExt = file.type.split('/')[1]; + var $container = $('
'); + + $container + .addClass('tmb-container') + .data('file-ref', file) + .append( + $('
').addClass('remove-btn').html('✖'), + $('
').addClass('file-tmb'), + $('
').addClass('tmb-filename').html(fileName) + ) + .appendTo($('.file-thumbs')); + + var $fileThumb = $container.find('.file-tmb'); + if (fileType == 'image') { + // if image file, generate thumbnail + var objURL = window.URL.createObjectURL(file); + $fileThumb.css('background-image', 'url('+ objURL +')'); + } else { + $fileThumb.html('' + fileExt.toUpperCase() + ''); + } +} + +$(document).on('ajax_before_post', function (e, formData) { + for (var i=0; i 0) key += i + 1; + formData.append(key, files[i]); + } +}); + +// clear file queue and UI on success +$(document).on('ajax_after_post', function () { + files = []; + $('.file-thumbs').empty(); +}); + +var dragCounter = 0; +var dropHandlers = { + dragenter: function (e) { + e.stopPropagation(); + e.preventDefault(); + + if (dragCounter === 0) $(this).addClass('dragover'); + dragCounter++; + }, + dragover: function (e) { + // needed for webkit to work + e.stopPropagation(); + e.preventDefault(); + }, + dragleave: function (e) { + e.stopPropagation(); + e.preventDefault(); + + dragCounter--; + if (dragCounter === 0) $(this).removeClass('dragover'); + }, + drop: function (e) { + e.stopPropagation(); + e.preventDefault(); + + $(this).removeClass('dragover'); + dragCounter = 0; + + var fileList = e.originalEvent.dataTransfer.files; + for (var i=0; i'); + + $fileSelector.on('change', function (e) { + if (this.files.length > 0) { + for (var i=0; i'); + if (getSetting("imageHoverFollowCursor")) { + var size = $this.parents('.file').find('.unimportant').text().match(/\b(\d+)x(\d+)\b/), + maxWidth = $(window).width(), + maxHeight = $(window).height(); + + var scale = Math.min(1, maxWidth / size[1], maxHeight / size[2]); hoverImage.css({ "position" : "absolute", "z-index" : 101, "pointer-events": "none", - "max-width" : $(window).width(), - "max-height" : $(window).height(), + "width" : size[1] + "px", + "height" : size[2] + "px", + "max-width" : (size[1] * scale) + "px", + "max-height" : (size[2] * scale) + "px", 'left' : e.pageX, 'top' : imgTop, }); diff --git a/js/post-filter.js b/js/post-filter.js index 518e38bb..8eb3ab7d 100644 --- a/js/post-filter.js +++ b/js/post-filter.js @@ -425,7 +425,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata array = $post.find('.body').contents().filter(function () {if ($(this).text() !== '') return true;}).toArray(); array = $.map(array, function (ele) { - return $(ele).text(); + return $(ele).text().trim(); }); comment = array.join(' '); diff --git a/static/flags/ec.png b/static/flags/ec.png index 4ca13865..6a779870 100755 Binary files a/static/flags/ec.png and b/static/flags/ec.png differ diff --git a/static/flags/flags.png b/static/flags/flags.png index 96a723f2..3c593eeb 100644 Binary files a/static/flags/flags.png and b/static/flags/flags.png differ diff --git a/stylesheets/style.css b/stylesheets/style.css index 8410754d..e067cc10 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -1181,3 +1181,81 @@ div.mix { #youtube-size input { width: 50px; } + +/* File selector */ +.dropzone { + color: #000; + cursor: default; + margin: auto; + padding: 0px 4px; + text-align: center; + min-height: 50px; + max-height: 140px; + transition: 0.2s; + background-color: rgba(200, 200, 200, 0.5); + overflow-y: auto; +} +.dropzone-wrap { + width: 100%; +} +.dropzone .file-hint { + color: rgba(0, 0, 0, 0.5); + cursor: pointer; + position: relative; + margin-bottom: 5px; + padding: 10px 0px; + top: 5px; + transition: 0.2s; + border: 2px dashed rgba(125, 125, 125, 0.4); +} +.file-hint:hover, .dropzone.dragover .file-hint { + color: rgba(0, 0, 0, 1); + border-color: rgba(125, 125, 125, 0.8); +} +.dropzone.dragover { + background-color: rgba(200, 200, 200, 1); +} +.dropzone .file-thumbs { + text-align: left; + width: 100%; +} +.dropzone .tmb-container { + padding: 3px; + overflow-x: hidden; + white-space: nowrap; +} +.dropzone .file-tmb { + height: 40px; + width: 70px; + cursor: pointer; + display: inline-block; + text-align: center; + background-color: rgba(187, 187, 187, 0.5); + background-size: cover; + background-position: center; +} +.dropzone .file-tmb span { + font-weight: 600; + position: relative; + top: 13px; +} +.dropzone .tmb-filename { + display: inline-block; + vertical-align: bottom; + bottom: 12px; + position: relative; + margin-left: 5px; +} +.dropzone .remove-btn { + cursor: pointer; + color: rgba(125, 125, 125, 0.5); + display: inline-block; + vertical-align: bottom; + bottom: 10px; + position: relative; + margin-right: 5px; + font-size: 20px +} +.dropzone .remove-btn:hover { + color: rgba(125, 125, 125, 1); +} diff --git a/templates/8chan/create.html b/templates/8chan/create.html index b6470709..be0cf4b7 100644 --- a/templates/8chan/create.html +++ b/templates/8chan/create.html @@ -7,8 +7,9 @@ Subtitle {% trans %}(must be < 200 chars){% endtrans %} {% trans %}Username{% endtrans %} {% trans %}(must contain only alphanumeric, periods and underscores){% endtrans %} {% trans %}Password{% endtrans %} {% trans %}(write this down){% endtrans %} -{{ game_html }} -{{ recapcha_html }} +{% trans %}CAPTCHA{% endtrans %}{{ captcha['html'] }}
+ +
diff --git a/templates/8chan/dnsbls.html b/templates/8chan/dnsbls.html index 49c54efc..0e346a00 100644 --- a/templates/8chan/dnsbls.html +++ b/templates/8chan/dnsbls.html @@ -1,5 +1,6 @@

{% trans %}Your IP is listed in our DNSBL. To stop attackers, we require users who use certain IP ranges to pass a test which proves they are human every 24 hours.{% endtrans %}

+

{% trans %}Tor users need to fill out the CAPTCHA every 3 hours or 5 posts.{% endtrans %}

{% trans %}It is also possible that the site is currently under attack and we are requiring everyone to pass the test right now. Sorry for the inconvenience.{% endtrans %}

diff --git a/templates/main.js b/templates/main.js index f4be2c8c..77f5b9f4 100644 --- a/templates/main.js +++ b/templates/main.js @@ -336,7 +336,7 @@ function init() { {% endraw %} {% if config.allow_delete %} - if (document.forms.postcontrols) { + if (document.forms.postcontrols && document.forms.postcontrols.password) { document.forms.postcontrols.password.value = localStorage.password; } {% endif %} diff --git a/templates/mod/settings.html b/templates/mod/settings.html index 4c4a5cfa..c0f43ced 100644 --- a/templates/mod/settings.html +++ b/templates/mod/settings.html @@ -53,7 +53,7 @@ {% trans %}Public action log{% endtrans %}
{% trans %}Displays all actions to the public{% endtrans %}{% for i in range(2, 25) %}{% endfor %} {% trans %}Bump limit{% endtrans %} - {% trans %}Language{% endtrans %}
{% trans %}To contribute translations, register at Transifex{% endtrans %} + {% trans %}Language{% endtrans %}
{% trans %}Read this page for more information about contributing translations:
Translation tutorial or use Transifex{% endtrans %}
{% endif %} {% if mod %}{% endif %} - +
{% if not config.field_disable_name or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %} -
{% trans %}Name{% endtrans %} @@ -91,6 +91,13 @@ + + {% if config.allow_upload_by_url %}
: @@ -129,7 +136,8 @@
Confused? See the FAQ.
+
+ {% if not config.force_flag %} {{ flag_tr }} @@ -163,17 +171,23 @@ -
+ {% trans %}Do not bump{% endtrans %} +
+ {% trans %}(you can also write sage in the email field){% endtrans %}
{% if config.spoiler_images %}
+ {% trans %}Spoiler images{% endtrans %} +
+ {% trans %}(this replaces the thumbnails of your images with question marks){% endtrans %}
{% endif %} {% if config.allow_no_country and config.country_flags and not config.force_flag %}
+ {% trans %}Hide country{% endtrans %} +
+ {% trans %}(this board displays your country when you post if this is unchecked){% endtrans %}
{% endif %} {% if mod %} @@ -225,7 +239,7 @@

+
diff --git a/translation.php b/translation.php new file mode 100644 index 00000000..381f72fd --- /dev/null +++ b/translation.php @@ -0,0 +1,37 @@ + +

Thank you for your interest in contributing a translation to infinity. This page will teach you how.

+ +

Historical note: infinity is based on a project called vichan (pronunced 6chan) which is in turn based on an older, abandoned project called Tinyboard. Vichan uses a service called Transifex to translate their files. In earlier versions of infinity, I decided to just keep using the vichan files because the only substantial source of new strings was the homepage and the Board configuration page, neither of which were displayed to board users so the existing set of translations worked. However, as time went on and more scripts and features were contributed, strings became out of sync. I originally intended to create 8chan a Transifex account, but Transifex charges for something as simple as bulk imports, so we will use this slightly more complicated process instead. Further, despite how much their charismatic CEO tried to sugarcoat it, the Transifex company abandoned their open source repository and became proprietary software, and then immediately put limits on imports/exports. Please see this page from the Free Software Foundation for more about the philosophy behind this and the dangers of trusting SaaS with your data. Who does that server really serve?

+ +

Some of my criticism of Transifex was not accurate, I apologize. You are free to either use Transifex or follow the steps below. Here's our Transifex team page

+ +

infinity uses gettext files for translation. This is what allows us to have boards in many languages on the same site, such as /argentina/ in Spanish, /deutsch/ in German and /japan2/ in Japanese. gettext files have the .po file extension. You can edit PO files by hand, but I highly recommend using POEdit. It is very easy to make syntax errors without POEdit or similar software.

+
    +
  1. Install POEdit. POEdit is free software available for Mac, Linux and Windows.
  2. +
  3. Find your translation file. Go to our Github project, and in the files list click "inc", then "locale". You will see a list of languages. It's usually self explanatory which code is for which language, but if you're not sure you can check the GNU project's list of usual language codes and search for your language.
  4. +
+

If your language is listed and you want to update the translation:

+
    +
  1. Download the .po files. tinyboard.po contains the strings generated by PHP, like "Comment", "Name", et cetera. javascript.po contains the strings generated by JavaScript, like all the fields under [Options]. You can translate one or both. For example, here is the French translation of tinyboard.po. To download it, click "Raw" and then save the file to your computer.
  2. +
  3. Click "Edit a translation" in POEdit. Navigate to the file you downloaded, fill in the translation boxes and save your file.
  4. +
+

If your language is not listed and you want to add a translation for it:

+
    +
  1. Download the en English .po file. (Tip: If, for example, you want to create a new Spanish dialect translation when Spanish (Spain) already exists, download es_ES and use that as the template.)
  2. +
  3. Click "Create a new translation" in POEdit and select the po file you downloaded as the template file.
  4. +
  5. Select your language from the dropdown when prompted.
  6. +
+ +

Tip: If you would like to attribute your translation to you, you can change your Name and Email in Preferences.

+

Another tip: You might find that a string you want to translate is not in the files. Don't panic, I accidentally forget to put strings in {% trans %} tags and _() gettext function all the time so gettext doesn't catch them. Just email me and tell me where I forgot and I'll add it and update tinyboard.po/javascript.po. Some strings I don't want to add for legal reasons. Those are the ones at the bottom including the copyright notice.

+ +

Once you are done translating, save your .po file in POEdit and send it to admin@8chan.co, or, if you know how, open a pull request on Github with your translated file. Make sure to put the language you translated to in the subject of your email. Thanks in advance for your contribution!

+
+EOT; + +echo Element("page.html", array("config" => $config, "body" => $body, "title" => "Translation tutorial"));