Bitcoin Forum
March 29, 2024, 01:10:15 AM *
News: Latest Bitcoin Core release: 26.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: [ХАК] Отчет модератору в один клик  (Read 399 times)
madnessteat (OP)
Legendary
*
Offline Offline

Activity: 2198
Merit: 1937



View Profile
January 29, 2019, 06:51:25 PM
Last edit: January 29, 2019, 08:33:39 PM by madnessteat
Merited by xandry (2)
 #1



    Недавно пользователь eternalgloom задался вопросом можно ли расширить функции отчетов модератору в виде выпадающего списка с предложенными вариантами. На следующий день пользователь suchmoon опубликовал тему в которой предложил свой метод.


    Предупреждение

    1. Это расширение для браузера. Никогда не устанавливайте расширения для браузеров, полученные от незнакомцев в интернете, если вы точно не знаете, что это. Я не несу ответственности, если это расширение украдет все ваши деньги.

    2. В основе этого - непроверенный код. В лучшем случае это альфа версия. Я выдернул его из большего расширения, которое я использую для других целей. Рассматривайте это как пример или идею. Не стесняйтесь использовать любые кусочки, которые вам нравятся, также можете делать с ними все, что хотите.

    3. Это работает в Firefox/Tor браузерах и может работать в Chrome, но это еще не проверенно. Скорее всего это не будет работать в мобильных браузерах.

    4. Вы должны знать, как работают WebExtensions и Promises, чтобы понять смысл кода. Я попытаюсь объяснить ниже, но полное обучение выходит за рамки этого потока.

    Как это работает

    Это расширение для браузера, построенное с использованием WebExtensions API и состоит из двух основных частей:

    • bct-content.js: скрипт содержимого, копия которого выполняется на каждой странице форума Bitcointalk после установки расширения. В зависимости от страницы скрипт содержимого будет выполнять различные действия:
      • Внутри потоков, на странице патрулирования и в истории сообщений/потоков пользователя он создает кнопки, чтобы можно было отправить отчет модератору с помощью одного щелчка мыши. Нажав одну из кнопок скрипт отправляет сообщение другой части расширения, например, которая обрабатывает очереди.

      Loading...

      • На странице "отчет модератору" значительно расширяется поле комментария для лучшей видимости. Если страница была открыта автоматически с помощью одной из вышеуказанных кнопок, она вставляет предварительно выбранный комментарий, ожидает указанное количество времени для соблюдения регулирования и нажимает кнопку "Отправить".
      • При просмотре досок он закрывает вкладку, если он получен из автоматического пост-отчета, иначе он ничего не делает.
    • bct-background.js: фоновый скрипт, который получает сообщения из скрипта содержимого и для каждого сообщения открывает "отчет модератору" в новой вкладке, которая затем обрабатывается скриптом содержимого, как описано выше. Это делается, не отвлекая вас от того, чем вы занимаетесь, чтобы вы могли продолжать просматривать и отправлять отчеты о других нарушениях.

    Код, который запускается между нажатием вами кнопки и нажатием кнопки «Отправить» расширением, оборачивается в Promise, поэтому он должен быть успешным или неуспешным в целом. Обратите внимание, что фактическое представление отчета не подтверждено, то есть если произошел сбой при нажатии кнопки «Отправить», вам придется дважды проверить историю отчета, чтобы убедиться, что отчет был успешно отправлен.

    Если код завершится успешно, он добавит желтую рамку в левой части сообщения, а в случае неудачи - красную. Стиль настраивается в bct-content.css. В случае сбоя проверьте журнал консоли и, пожалуйста, дайте мне знать, если есть ошибка, которая ее вызывает.

    Установка

    Сохраните файлы, размещенные ниже, в одну папку. В Firefox/Tor наберите в адресной строке "about:debugging", нажмите загрузить временное дополнение и выберете любой файл из папки. Если вы достаточно храбры, чтобы использовать Chrome - вам придется разобраться в этом самостоятельно.

    Нажатие кнопки "Отправить" комментируется в строке 18 в bct-content.js. Я хотел бы избежать спама модераторам с кучей неправильных отчетов, если что-то пойдет не так. Если вы решите использовать расширение, вы можете сначала протестировать его, вручную нажав "Отправить", а затем измените строку, так как вам нужно.

    В строке 19 задается время ожидания до нажатия кнопки "Отправить". Аккаунты с высокими рангами имеют задержку 4 секунды между сообщениями, поэтому задержка по умолчанию 5000ms хорошо работает с этим. С более низкими рангами учетных записей вы можете столкнуться с подстройкой времени ожидания - отрегулируйте задержку под себя.

    Вы можете изменить названия кнопок, комментарии к отчетам, добавить новые кнопки и т. д. со строки 120 в bct-content.js.

    Файлы


    manifest.json

    [/list]
    Code:
    {

      "manifest_version": 2,
      "name": "BCT Helper",
      "version": "0.1b",

      "description": "Adds some automation for bitcointalk.org.",

      "content_scripts": [
        {
          "matches": [ "*://bitcointalk.org/*" ],
          "js": [ "bct-content.js" ],
          "css": [ "bct-content.css" ],
          "run_at": "document_idle"
        }
      ],

      "background":
      {
        "scripts": ["bct-background.js"]
      },
      
      "permissions": [
        "tabs"
      ]
    }

    bct-content.css

    Code:
    div.post {
        border-left: 4px transparent solid;
    }

    div.post.post-wait {
        opacity: 0.5;
    }

    div.post.post-error {
        border-left: 4px red solid;
    }

    div.post.post-success {
        border-left: 4px yellow solid;
    }

    .bct-report-button-container {
        margin-top: 10px;
        background-color: #bbddbb;
    }

    .bct-report-input {
        margin-left: 5px;
        height: 12px;
    }

    .bct-report-button, .bct-report-button:hover {
        display: inline-block;
        border: 1px solid black;
        margin-left: 5px;
        padding: 1px 5px 1px 5px;
        transform: none;
    }

    .bct-report-button:hover {
        cursor: pointer;
    }

    bct-content.js

    Code:
    console.log("BCT-CONTENT initialized");
    console.log("Page: " + window.location.href);
    console.log("Referrer: " + document.referrer);

    function process_background_message(message, sender, send_response) {
        browser.runtime.onMessage.removeListener(process_background_message);
        console.log("Content script received background message: " + JSON.stringify(message));
        if (message.action == "bct-tab-open-report" || message.action == "bct-tab-submit-report") {
            if (message.comment !== undefined) {
                document.getElementsByName("comment")[0].value = message.comment;
            }
            document.getElementsByName("comment")[0].focus();
            message.result = "OK";
        }
        if (message.action == "bct-tab-submit-report") {
            // mod report counts as post/PM for throttling - add a delay
            setTimeout(() => {
                send_response(message);
                // Uncomment the next line to allow reports to be submitted automatically
                //document.querySelector("input[type=submit][value=Submit]").click();
            }, 5000);
        } else {
            send_response(message);
        }
        // this is needed to make the sender wait for a response
        return true;
    }

    function report_post(post_container, thread_id, post_id, report_comment, auto_submit) {
        post_container.classList.add("post-wait");

        let event_detail = {
            event_id: (Math.random().toString(36) + '000000000000000000').slice(2, 18),
            action_name: "bct-report",
            action_url: "https://bitcointalk.org/index.php?action=reporttm;topic=" + thread_id + ";msg=" + post_id,
            action_payload: { post_id: post_id, comment: report_comment, auto: auto_submit }
        };

        browser.runtime.sendMessage(event_detail)
            .then((message_response) => {
                //console.log("message_response: " + JSON.stringify(message_response));
                console.log("message_response size: " + JSON.stringify(message_response).length);
                post_container.classList.remove("post-wait", "post-error", "post-success");
                post_container.classList.add("post-success");
            })
            .catch((error) => {
                console.log("Data request failed:");
                console.log(error);
                post_container.classList.remove("post-wait", "post-error", "post-success");
                post_container.classList.add("post-error");
            })
        ;
        
    }

    function extract_ids_from_url(post_url) {
        let url_parts = post_url.split("#msg");
        let post_id = url_parts[1];
        let thread_id = url_parts[0].split(".msg")[0].split("?topic=")[1];
        return [thread_id, post_id];
    }

    function create_button(post_container, button_title, report_comment, text_field, auto_submit) {
        let button = document.createElement("button");
        button.className = "bct-report-button";
        button.innerText = button_title;
        button.title = report_comment;
        button.addEventListener("click", (e) => {
            e.preventDefault();
            if (text_field) {
                if (text_field.value.trim()) {
                    report_comment += " " + text_field.value.trim();
                } else {
                    alert("Required value missing");
                    return;
                }
            }
            report_post(post_container, post_container.thread_id, post_container.post_id, report_comment, auto_submit);
        });
        return button;
    }

    function create_span(text) {
        let span = document.createElement("span");
        span.innerText = text;
        return span;
    }

    function create_text_field(hint) {
        let text_field = document.createElement("input");
        text_field.className = "bct-report-input";
        text_field.type = "text";
        text_field.placeholder = hint;
        return text_field;
    }

    // inject the buttons into each message
    document.querySelectorAll("div.post").forEach(post_container => {
        // Try to determine thread ID and post ID
        let link_object = null;
        if (post_container.parentNode.classList.contains("td_headerandpost")) {
            // Thread view
            // post -> td.td_headerandpost -> table ... -> div#subject_123456
            link_object = post_container.parentNode.firstElementChild.querySelector("div[id^='subject_'] a");
        } else {
            // Other views: patrol, user's post history, user's thread history
            let post_url_start = "https://bitcointalk.org/index.php?topic=";
            // post -> td -> tr -> tbody -> tr ... -> a[href contains #msg123456]
            link_object = post_container.parentNode.parentNode.parentNode.firstElementChild.querySelector("a[href^='" + post_url_start + "'][href*='#msg']");
        }
        if (link_object) {
            [post_container.thread_id, post_container.post_id] = extract_ids_from_url(link_object.getAttribute("href"));
            if (post_container.thread_id && post_container.post_id) {
                let button_container = document.createElement("div");
                button_container.className = "bct-report-button-container";
                post_container.appendChild(button_container);
                button_container.appendChild(create_span("Report as: "));
                button_container.appendChild(create_button(post_container, "zero value", "zero-value shitpost", null, true));
                button_container.appendChild(create_button(post_container, "multi post", "two or more consecutive posts in 24h", null, true));
                button_container.appendChild(create_button(post_container, "cross spam", "spamming their service across multiple threads - please check post history", null, true));
                button_container.appendChild(create_button(post_container, "non-english", "non-English post on English board", null, true));
                let url_field = create_text_field("URL of the original");
                button_container.appendChild(create_button(post_container, "copy from:", "copy-paste from:", url_field, true));
                button_container.appendChild(url_field);
                let board_field = create_text_field("correct board name");
                button_container.appendChild(create_button(post_container, "move to:", "wrong board, should be in", board_field, true));
                button_container.appendChild(board_field);
            } else {
                console.log("Found div.post and post URL but couldn't determine thread/post ID.");
            }
        } else {
            console.log("Found div.post but couldn't find post URL.");        
        }
    });

    if (window.location.href.startsWith("https://bitcointalk.org/index.php?action=reporttm")) {
        document.getElementsByName("comment")[0].style.width = "80%";
        browser.runtime.onMessage.addListener(process_background_message);
    }
    if (window.location.href.startsWith("https://bitcointalk.org/index.php?board=")) {
        if (document.referrer &&
            document.referrer.startsWith("https://bitcointalk.org/index.php?action=reporttm") &&
            document.referrer.endsWith(";a") // after automatic submission
        ) {
            console.log("Attempting to close this tab...");
            browser.runtime.sendMessage({ action_name: "close-this-tab" });
        }
    }

    bct-background.js

    Code:
    // This is an array of Promise.resolve functions that will be called sequentially with delay
    let throttled_resolvers = [];
    // Number of milliseconds to wait before resolving the next queued promise
    let PROMISE_INTERVAL = 1300;
    // Number of milliseconds to wait before rejecting the queued promise
    let PROMISE_TIMEOUT = 120000;
    // Number of milliseconds to wait for a tab to load
    let TAB_TIMEOUT = 60000;

    function handle_next_resolver() {
        let p = throttled_resolvers.shift();
        if (p === undefined) {
            setTimeout(handle_next_resolver, PROMISE_INTERVAL);
        }
        else {
            p.resolve();
        }
    }

    setTimeout(handle_next_resolver, PROMISE_INTERVAL);

    function queue_promise() {
        return new Promise((resolve, reject) => {
            throttled_resolvers.push({ resolve: resolve });
            setTimeout(function () { reject(new Error("Queued promise has timed out.")); }, PROMISE_TIMEOUT);
        });
    }

    function check_if_tab_fully_loaded(tab) {

        function is_tab_complete(tab) {
            return tab.status === "complete" && tab.url !== "about:blank";
        }

        if (is_tab_complete(tab)) {
            return tab;
        } else {
            return new Promise((resolve, reject) => {

                const timer = setTimeout(
                    function () {
                        browser.tabs.onUpdated.removeListener(on_updated);
                        if (is_tab_complete(tab)) {
                            resolve(tab);
                        } else {
                            reject(new Error("Tab status " + tab.status + ": " + tab.url));
                        }
                    },
                    TAB_TIMEOUT
                );
                
                function on_updated(tab_id, change_info, updated_tab) {
                    if (tab_id == tab.id && is_tab_complete(updated_tab)) {
                        clearTimeout(timer);
                        browser.tabs.onUpdated.removeListener(on_updated);
                        resolve(updated_tab);
                    }
                }

                browser.tabs.onUpdated.addListener(on_updated);

            });
        }
    }

    browser.runtime.onMessage.addListener(function(message, sender) {
        if (message.action_name === "close-this-tab") {
            //console.log("Background script closing tab:");
            //console.log(sender.tab);
            browser.tabs.remove(sender.tab.id);
        }
        else if (message.action_name === "bct-report") {
            /*
            Expected message format:
            {
                action_name: "bct-auto-report",
                action_url: "https://...",
                action_payload: { post_id: N, comment: "...", auto: true }
            }
            */
            let tab_url = message.action_url;
            let tab_action = "bct-tab-open-report";
            if (message.action_payload.auto) {
                tab_action = "bct-tab-submit-report";
                tab_url += ";a";
            }
            console.log(message);
            return queue_promise()
                .then(() =>
                    browser.tabs.create({
                        url: tab_url,
                        windowId: sender.tab.windowId,
                        active: false
                    })
                )
                .then((created_tab) => check_if_tab_fully_loaded(created_tab))
                .catch((error) => {
                    error_message = "Tab load/check failed: " + error.message;
                    console.log(error_message);
                    throw new Error(error_message);
                })
                .then((loaded_tab) => browser.tabs.sendMessage(loaded_tab.id, { id: loaded_tab.id, action: tab_action, comment: message.action_payload.comment }))
                .then((tab_response) => {
                    //console.log("Tab result: " + tab_response.result);
                    message.action_result = tab_response.result;
                    return message;
                })
                .catch((error) => {
                    console.log("Request failed in the background:");
                    console.log(error);
                    throw new Error(error.message);
                })
                .finally(() => {
                    setTimeout(handle_next_resolver, PROMISE_INTERVAL);
                })
            ;
        }
    });




    ███████████████████████████
    ███████▄████████████▄██████
    ████████▄████████▄████████
    ███▀█████▀▄███▄▀█████▀███
    █████▀█▀▄██▀▀▀██▄▀█▀█████
    ███████▄███████████▄███████
    ███████████████████████████
    ███████▀███████████▀███████
    ████▄██▄▀██▄▄▄██▀▄██▄████
    ████▄████▄▀███▀▄████▄████
    ██▄███▀▀█▀██████▀█▀███▄███
    ██▀█▀████████████████▀█▀███
    ███████████████████████████
    .
    .Duelbits.
    ▄▄█▄▄░░▄▄█▄▄░░▄▄█▄▄
    ███░░░░███░░░░███
    ░░░░░░░░░░░░░
    ░░░░░░░░░░░░
    ▀██████████
    ░░░░░███░░░░
    ░░░░░███▄█░░░
    ░░██▌░░███░▀░░██▌
    █░██░░███░░░██
    █▀▀▀█▌░███░░█▀▀▀█▌
    ▄█▄░░░██▄███▄█▄░░▄██▄
    ▄███▄
    ░░░░▀██▄▀
    .
    REGIONAL
    SPONSOR
    ███▀██▀███▀█▀▀▀▀██▀▀▀██
    ██░▀░██░█░███░▀██░███▄█
    █▄███▄██▄████▄████▄▄▄██
    ██▀ ▀███▀▀░▀██▀▀▀██████
    ███▄███░▄▀██████▀█▀█▀▀█
    ████▀▀██▄▀█████▄█▀███▄█
    ███▄▄▄████████▄█▄▀█████
    ███▀▀▀████████████▄▀███
    ███▄░▄█▀▀▀██████▀▀▀▄███
    ███████▄██▄▌████▀▀█████
    ▀██▄█████▄█▄▄▄██▄████▀
    ▀▀██████████▄▄███▀▀
    ▀▀▀▀█▀▀▀▀
    .
    EUROPEAN
    BETTING
    PARTNER
    Unlike traditional banking where clients have only a few account numbers, with Bitcoin people can create an unlimited number of accounts (addresses). This can be used to easily track payments, and it improves anonymity.
    Advertised sites are not endorsed by the Bitcoin Forum. They may be unsafe, untrustworthy, or illegal in your jurisdiction.
    1711674615
    Hero Member
    *
    Offline Offline

    Posts: 1711674615

    View Profile Personal Message (Offline)

    Ignore
    1711674615
    Reply with quote  #2

    1711674615
    Report to moderator
    1711674615
    Hero Member
    *
    Offline Offline

    Posts: 1711674615

    View Profile Personal Message (Offline)

    Ignore
    1711674615
    Reply with quote  #2

    1711674615
    Report to moderator
    madnessteat (OP)
    Legendary
    *
    Offline Offline

    Activity: 2198
    Merit: 1937



    View Profile
    January 29, 2019, 06:53:12 PM
     #2

    резерв

    ███████████████████████████
    ███████▄████████████▄██████
    ████████▄████████▄████████
    ███▀█████▀▄███▄▀█████▀███
    █████▀█▀▄██▀▀▀██▄▀█▀█████
    ███████▄███████████▄███████
    ███████████████████████████
    ███████▀███████████▀███████
    ████▄██▄▀██▄▄▄██▀▄██▄████
    ████▄████▄▀███▀▄████▄████
    ██▄███▀▀█▀██████▀█▀███▄███
    ██▀█▀████████████████▀█▀███
    ███████████████████████████
    .
    .Duelbits.
    ▄▄█▄▄░░▄▄█▄▄░░▄▄█▄▄
    ███░░░░███░░░░███
    ░░░░░░░░░░░░░
    ░░░░░░░░░░░░
    ▀██████████
    ░░░░░███░░░░
    ░░░░░███▄█░░░
    ░░██▌░░███░▀░░██▌
    █░██░░███░░░██
    █▀▀▀█▌░███░░█▀▀▀█▌
    ▄█▄░░░██▄███▄█▄░░▄██▄
    ▄███▄
    ░░░░▀██▄▀
    .
    REGIONAL
    SPONSOR
    ███▀██▀███▀█▀▀▀▀██▀▀▀██
    ██░▀░██░█░███░▀██░███▄█
    █▄███▄██▄████▄████▄▄▄██
    ██▀ ▀███▀▀░▀██▀▀▀██████
    ███▄███░▄▀██████▀█▀█▀▀█
    ████▀▀██▄▀█████▄█▀███▄█
    ███▄▄▄████████▄█▄▀█████
    ███▀▀▀████████████▄▀███
    ███▄░▄█▀▀▀██████▀▀▀▄███
    ███████▄██▄▌████▀▀█████
    ▀██▄█████▄█▄▄▄██▄████▀
    ▀▀██████████▄▄███▀▀
    ▀▀▀▀█▀▀▀▀
    .
    EUROPEAN
    BETTING
    PARTNER
    abdeldayemwebas
    Jr. Member
    *
    Offline Offline

    Activity: 199
    Merit: 3


    View Profile
    August 07, 2019, 10:45:06 AM
    Merited by xandry (1), Tobikov (1)
     #3

    Адаптация под Google Chrome от пользователя HCP. Оригинальный пост автора на английском языке: https://bitcointalk.org/index.php?topic=5103488.msg51378732#msg51378732

    Скрипт будет работать с расширением для браузера Mozilla webextension-polyfill library. Не пугайтесь названия, всё будет работать и на Google Chrome, но с несколькими модификациями.

    Итак, вам необходимо:

    1. Загрузить самую свежую версию файла browser-polyfill.js,
    Скачать расширение целиком: https://github.com/mozilla/webextension-polyfill/releases (оттуда нужен только файл browser-polyfill.js, его переместить в директорую с файлами из первого поста)
    По данной ссылке можно получить нужный файл отдельно (на момент написания поста актуальная версия 0.4.0): https://unpkg.com/webextension-polyfill@0.4.0/dist/


    2. Поместить browser-polyfill.js в одну директорию с файлами из первого поста данного топика

    По итогу будет 5 файлов:
     
    • manifest.json
    • browser-polyfill.js
    • bct-content.css
    • bct-content.js
    • bct-background.js

    3. Замените содержимое файла (с помощью текстового редактора) manifest.json на нижеуказанное, для того чтобы он ссылался на browser-polyfill.js ПЕРЕД остальными .js файлами, а также для фикса бага "некорректная версия" (Google Chrome ругается на 'b' в "version": "0.1b")

    Code:
    {

      "manifest_version": 2,
      "name": "BCT Helper",
      "version": "0.1.1",

      "description": "Adds some automation for bitcointalk.org.",

      "content_scripts": [
        {
          "matches": [ "*://bitcointalk.org/*" ],
          "js": [ "browser-polyfill.js","bct-content.js" ],
          "css": [ "bct-content.css" ],
          "run_at": "document_idle"
        }
      ],

      "background":
      {
        "scripts": ["browser-polyfill.js","bct-background.js"]
      },
      
      "permissions": [
        "tabs"
      ]
    }

    4. Проверяем список файлов. Напоминаю, в одной директории должно быть всего 5 файлов:

    Отредактированный Вами ранее manifest.json;
    Последняя версия browser-polyfill.js;
    Оригинальные bct-content.css, bct-content.js, bct-background.js из первого поста данного топика.

    После этого переходим к следующему шагу.

    5. Включаем режим разработчика в Google Chrome

    Как это сделать?

    Открываем расширения Google Chrome через настройки и управление Google Chrome в правом верхнем углу экрана (три вертикальные точки) > дополнительные инструменты > расширения. Вам откроется страница со списком установленных в Вашем браузере расширений. В правой верхней части страницы переводим выключатель в правое положение (вкл.) для включения Режима разработчика.

    После данного действия чуть ниже появятся кнопки управления, которые доступны только в этом режиме.

    6. Нажимаем самую первую кнопку "загрузить распакованное расширение", выбираем директорию где находятся наши 5 файлов и кликаем на кнопку "Выбрать".

    Если Вы все сделали правильно, то в списке Ваших расширений появится BCT Helper 0.1.1, а под каждым постом на форуме будет доступна небольшая панель управления репортами. В случае если скрипт открывает новую вкладку, но при этом отчет автоматически не отправляется, пречитайте следующие строки более внимательно:

    Нажатие кнопки "Отправить" комментируется в строке 18 в bct-content.js. Я хотел бы избежать спама модераторам с кучей неправильных отчетов, если что-то пойдет не так. Если вы решите использовать расширение, вы можете сначала протестировать его, вручную нажав "Отправить", а затем измените строку, так как вам нужно.

    В строке 19 задается время ожидания до нажатия кнопки "Отправить". Аккаунты с высокими рангами имеют задержку 4 секунды между сообщениями, поэтому задержка по умолчанию 5000ms хорошо работает с этим. С более низкими рангами учетных записей вы можете столкнуться с подстройкой времени ожидания - отрегулируйте задержку под себя
    Vadi2323
    Legendary
    *
    Offline Offline

    Activity: 2044
    Merit: 1231


    View Profile
    September 17, 2019, 08:41:18 PM
     #4

    ...На следующий день пользователь suchmoon опубликовал тему в которой предложил свой метод.

    ...

    1. Это расширение для браузера. Никогда не устанавливайте расширения для браузеров, полученные от незнакомцев в интернете, если вы точно не знаете, что это. Я не несу ответственности, если это расширение украдет все ваши деньги.

    Никаких расширений даже от "знакомцев", сколько раз уже написано было. suchmoon дебил
    suchmoon
    Legendary
    *
    Offline Offline

    Activity: 3640
    Merit: 8906


    https://bpip.org


    View Profile WWW
    September 17, 2019, 09:55:21 PM
     #5

    Никаких расширений даже от "знакомцев", сколько раз уже написано было. suchmoon дебил

    Это открытый исходный код. Достаточно безопасен для тех, кто хорошо знает JavaScript.
    Pages: [1]
      Print  
     
    Jump to:  

    Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!