Скрипт для КФ (биографии)

by David_Goggins

// ==UserScript==
// @name         Скрипт для КФ (биографии)
// @namespace    https://forum.blackrussia.online/
// @version      1.4.5
// @description  by David_Goggins 
// @author       David_Goggins  
// @match        https://forum.blackrussia.online/threads/*
// @include      https://forum.blackrussia.online/threads/*
// @match        https://forum.blackrussia.online/forums*
// @include      https://forum.blackrussia.online/forums
// @grant        none
// @license      MIT            
// @collaborator Kuk
// @icon         https://avatars.mds.yandex.net/i?id=e7371f38fb4d7fe174b4362d628c7f74-4988204-images-thumbs&n=13
// @copyright    2021, Kuk (https://openuserjs.org/users/Kuk)
// ==/UserScript==

// ==UserScript==
// @name Goggins_Goggins (RP-Биографии) ьесть
// @namespace https://forum.blackrussia.online/
// @version 12.1
// @description ФИНАЛЬНЫЙ СБОРНИК
// @author David_Goggins / Artem_Gogol (Финальное Объединение)
// @match https://forum.blackrussia.online/threads/*
// @grant none
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @require https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js
// ==/UserScript==

(function() {
'use strict';

// --- ПРЕФИКСЫ ---
const NARASSSMOTRENII_PREFIX = 2;
const OTKAZANO_PREFIX = 4;
const ODOBRENO_PREFIX = 8;

// --- ID разделов ---
const MOVE_NODE_REJECTED = 792;
const MOVE_NODE_ARCHIVE = 768;
const MOVE_NODE_APPROVED = 790;

// --- БАННЕР И ПОДПИСЬ ---
const APPROVED_BANNER_URL = 'https://i.postimg.cc/sgkL5vvb/1618083711121.png';
const NEW_BANNER_BBCODE = '[B][CENTER][url=https://postimages.org/][img]' + APPROVED_BANNER_URL + '[/img][/url][/CENTER][/B]';
const FOOTER_LINKS = '';


// --- ГЕНЕРАТОРЫ ---
function generateRejectionContent(reasonText) {
return (
NEW_BANNER_BBCODE + "\n\n" +
"[B][CENTER][COLOR=#ff0000]Доброго времени суток, {{ user.name }}[/COLOR][/CENTER][/B]\n\n" +
"[CENTER][SIZE=5][COLOR=#000000]Ваша RolePlay биография была проверена, но есть моменты для доработки![/COLOR][/SIZE][/CENTER]\n\n" +
"[CENTER][SIZE=5][COLOR=#000000]Статус: [COLOR=#FF0000]❌ Отказано[/COLOR][/COLOR][/SIZE][/CENTER]\n\n" +
"[CENTER][SIZE=4][COLOR=#000000]Причина: [COLOR=#FF0000]" + reasonText + "[/COLOR][/COLOR][/SIZE][/CENTER]\n\n" +
"[CENTER][SIZE=4][COLOR=#000000]Просьба ознакомиться с [URL='https://forum.blackrussia.online/threads/13425782/']правилами написания биографий[/URL].[/COLOR][/SIZE][/CENTER]\n\n" +
NEW_BANNER_BBCODE);
}

const approvalContent =
NEW_BANNER_BBCODE + "\n\n" +
"[B][CENTER][COLOR=#ff0000]Доброго времени суток, {{ user.name }}[/COLOR][/CENTER][/B]\n\n" +
"[CENTER][SIZE=5][COLOR=#000000]Ваша биография была проверена и получает статус: [COLOR=#00FF00]✔️ Одобрено[/COLOR][/COLOR][/SIZE][/CENTER]\n\n" +
"[CENTER][SIZE=4][COLOR=#000000]Приятной игры![/COLOR][/SIZE][/CENTER]\n\n" +
NEW_BANNER_BBCODE + "\n\n" + FOOTER_LINKS;

const reworkContent =
NEW_BANNER_BBCODE + "\n\n" +
"[B][CENTER][COLOR=#ff0000]Доброго времени суток, {{ user.name }}[/COLOR][/CENTER][/B]\n\n" +
"[CENTER][SIZE=5][COLOR=#000000]Биография проверена, но есть моменты для доработки.[/COLOR][/SIZE][/CENTER]\n\n" +
"[CENTER][SIZE=4][COLOR=#000000]Статус: [COLOR=#FFC000]🟡 На доработке (24ч)[/COLOR][/COLOR][/SIZE][/CENTER]\n\n" +
"[CENTER][SIZE=4][COLOR=#000000]Вам даётся 24 часа на исправление биографии.[/COLOR][/SIZE][/CENTER]\n\n" +
NEW_BANNER_BBCODE;

// --- КНОПКИ ---
const buttons = [
{ title: '____________________________________RP-БИОГРАФИИ____________________________________' },
{ title: '✔️ Одобрено', content: approvalContent, prefix: ODOBRENO_PREFIX, status: true, grid_col: 2 },
{ title: '🟡 На доработке', content: reworkContent, prefix: NARASSSMOTRENII_PREFIX, status: true, grid_col: 2 },
{ title: '❌ Не доработал', content: generateRejectionContent("Вы не доработали RP биографию за 24 часа."), prefix: OTKAZANO_PREFIX, status: true, grid_col: 5 },
{ title: '❌ Не по форме', content: generateRejectionContent("Биография составлена не по форме."), prefix: OTKAZANO_PREFIX, status: true, grid_col: 5 },

{ title: '❌ Недостаточный объём', content: generateRejectionContent("Объём биографии менее 200 слов."), prefix: OTKAZANO_PREFIX, status: true, grid_col: 5 },
{ title: '❌ Избыточный объём', content: generateRejectionContent("Объём биографии превышает 600 слов."), prefix: OTKAZANO_PREFIX, status: true, grid_col: 5 },
{ title: '❌ Нет фото или видео', content: generateRejectionContent("В биографии отсутствует хотя бы одно релевантное фото/видео."), prefix: OTKAZANO_PREFIX, status: true, grid_col: 5 },
{ title: '❌ Общий отказ (свой вариант)', content: generateRejectionContent("..."), prefix: OTKAZANO_PREFIX, status: false, grid_col: 5 },
{ title: '❌ Заголовок не по форме', content: generateRejectionContent("Заголовок темы составлен не по форме.<br>Формат должен быть: Биография | Имя_Фамилия"), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Копипаст', content: generateRejectionContent("Биография содержит скопированный текст.<br>Создайте оригинальную историю."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Орфографические ошибки', content: generateRejectionContent("Слишком много орфографических ошибок.<br>Исправьте текст и проверьте правописание."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Пунктуация', content: generateRejectionContent("Неверная расстановка знаков препинания.<br>Проверьте грамматику."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нереалистичный возраст', content: generateRejectionContent("Возраст персонажа указан нереалистично.<br>Используйте правдоподобные значения."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нет структуры', content: generateRejectionContent("Отсутствует структурное деление на Детство / Настоящее время / Итог."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Несостыковки', content: generateRejectionContent("В биографии обнаружены логические или фактологические несостыковки."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Сверхспособности', content: generateRejectionContent("Запрещено придавать персонажу нереалистичные свойства или сверхспособности."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нереалистичное описание', content: generateRejectionContent("Описание событий персонажа выходит за рамки реализма проекта."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нет логики сюжета', content: generateRejectionContent("События не связаны между собой логически."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нет хронологии', content: generateRejectionContent("События биографии расположены без временной последовательности."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нарушение правил проекта', content: generateRejectionContent("В тексте присутствуют упоминания запрещённого контента (экстремизм, наркотики, политика)."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нарушен формат шрифта', content: generateRejectionContent("Использован неправильный шрифт или размер текста.<br>Допустимо только Verdana или Times New Roman, 15pt+."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Нет фото персонажа', content: generateRejectionContent("Не прикреплено фото персонажа, требуемое правилами."), prefix: OTKAZANO_PREFIX, status: true },
{ title: '❌ Несовпадение ника', content: generateRejectionContent("Никнейм в заголовке темы отличается от никнейма, указанного в пункте Nick_Name. Исправьте, чтобы оба совпадали."), prefix: OTKAZANO_PREFIX, status: true },
];
// --- СЛУЖЕБНЫЕ ФУНКЦИИ ---

function getFormData(data) {
    const formData = new FormData();
    Object.entries(data).forEach(([k, v]) => formData.append(k, v));
    return formData;
}

// --- СМЕНА ПРЕФИКСА ---
function editThreadData(prefix, pin = false, shouldClose = true) {
    const threadTitle = $('.p-title-value')[0].lastChild.textContent.trim();
    if (typeof XF === 'undefined' || !XF.config || !XF.config.csrf) return;

    const data = {
        prefix_id: prefix,
        title: threadTitle,
        discussion_open: shouldClose ? 0 : 1,
        _xfToken: XF.config.csrf,
        _xfRequestUri: document.URL.split(XF.config.url.fullBase)[1],
        _xfWithData: 1,
        _xfResponseType: 'json',
    };
    if (pin) data.sticky = 1;

    fetch(`${document.URL}edit`, {
        method: 'POST',
        body: getFormData(data),
    }).then(() => location.reload())
    .catch(err => console.error('Ошибка при смене префикса:', err));
}

// --- ПОЛУЧЕНИЕ ДАННЫХ ТЕМЫ ---
function getThreadData() {
    const usernameElement = $('a.username')[0];
    if (!usernameElement)
        return { user: { id: 'Unknown', name: 'Уважаемый пользователь', mention: 'Уважаемый пользователь' } };

    const authorID = usernameElement.attributes['data-user-id']?.nodeValue || 'UnknownID';
    const authorName = $(usernameElement).text().trim() || 'Уважаемый пользователь';
    return {
        user: {
            id: authorID,
            name: authorName,
            mention: `[USER=${authorID}]${authorName}[/USER]`,
        },
    };
}

// --- ДОБАВЛЕНИЕ КНОПКИ ВНИЗУ ---
function addButton(name, id) {
    $('.button--icon--reply').before(
        `<button type="button" class="button rippleButton" id="${id}" style="background:transparent!important;margin:10px;border:none;border-radius:10px;color:white!important;">${name}</button>`
    );
}

// --- ВЕРСТКА СПИСКА КНОПОК ---
function buttonsMarkup(buttons) {
    return `<div class="select_answer">
        ${buttons.map((btn, i) => {
            if (!btn.content || btn.title.includes('______') || btn.title.includes(' - - '))
                return `<div class="separator-title">${btn.title.replace(/_/g,'').replace(/-/g,'').trim()}</div>`;
            const extraClass = (i === 1 || i === 2) ? 'col-2' : '';
            return `<button id="answers-${i}" class="button--primary button rippleButton answer-button ${extraClass}" data-id="${i}">
                        <span class="button-text">${btn.title}</span>
                    </button>`;
        }).join('')}
    </div>`;
}

// --- ВСТАВКА ТЕКСТА В ОТВЕТ ---
function pasteContent(id, data = {}, send = false) {
    if (buttons[id].content === undefined) return;

    const template = Handlebars.compile(buttons[id].content);
    const btn = buttons[id];

    if ($('.fr-element.fr-view p').text().trim() === '')
        $('.fr-element.fr-view p').empty();
    $('span.fr-placeholder').empty();

    const contentToPaste = template(data).replace(/\n/g, '<br>');
    $('div.fr-element.fr-view p').append(contentToPaste);

    $('a.overlay-titleCloser').trigger('click');

    if (send === true) {
        const pinStatus = btn.prefix === NARASSSMOTRENII_PREFIX;
        const shouldClose = btn.close !== false;
        editThreadData(btn.prefix, pinStatus, shouldClose);
        $('.button--icon.button--icon--reply.rippleButton').trigger('click');
    }
}

// --- СТИЛИ И FIX МОДАЛКИ ---
function applyModalFixes(customTitle) {
    $('.overlay-container').css({
        position: 'fixed', top: 0, left: 0,
        width: '100%', height: '100%',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        backgroundColor: 'rgba(0,0,0,0.65)', zIndex: '999999', overflow: 'hidden'
    });
    $('.overlay').css({
        background: 'rgba(42,44,46,0.45)', borderRadius: '8px',
        boxShadow: '0 0 30px rgba(0,0,0,0.75)',
        maxWidth: '880px', width: 'calc(100% - 60px)', maxHeight: '85vh',
        overflow: 'hidden', display: 'flex', flexDirection: 'column'
    });
    $('.overlay-title').css({
        background: 'rgba(26,29,31,0.85)', color: '#fff',
        textAlign: 'center', fontWeight: '700', padding: '10px',
        borderBottom: '1px solid rgba(255,255,255,0.06)'
    });
    $('.overlay-content').css({
        flex: '1', overflowY: 'auto',
        background: 'rgba(42,44,46,0.30)',
        padding: '12px', color: '#fff',
        scrollbarWidth: 'none', '-ms-overflow-style': 'none'
    });

    if (!$('#modal-style-fix').length) {
        $('head').append(`
            <style id="modal-style-fix">
                .select_answer{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-start;padding:8px;}
                .select_answer .answer-button{flex:1 1 calc(20% - 8px);min-width:120px;height:auto;
                    background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);
                    color:#fff;border-radius:6px;padding:10px 12px;white-space:normal;
                    transition:transform .08s ease,background .12s ease;}
                .select_answer .answer-button:hover{background:rgba(255,255,255,0.08);transform:translateY(-1px);}
                .separator-title{flex-basis:100%;text-align:center;color:#f5c542;font-weight:700;margin:10px 0;}
                .overlay-content::-webkit-scrollbar{width:0px;height:0px;}
                .overlay-content{-ms-overflow-style:none;scrollbar-width:none;}
            </style>
        `);
    }

    $('body').addClass('modal-opened').css({overflow:'hidden'});
    $(document).off('click.modalfix', '.overlay-titleCloser').on('click.modalfix', '.overlay-titleCloser', function() {
        $('body').removeClass('modal-opened').css({overflow:'auto'});
    });
}
// --- ЗАПУСК СКРИПТА ---
$(document).ready(() => {
    if (typeof XF === 'undefined' || typeof jQuery === 'undefined' || typeof XF.alert === 'undefined') return;

    const threadData = getThreadData();
    const mainButtonId = 'rp_bio_templates_btn';

    // Основная кнопка вызова шаблонов
    addButton('Шаблоны RP-Биографий', mainButtonId);

    // Дополнительные кнопки перемещения
    const moveRejectedId = 'move_rejected_btn';
    const moveApprovedId = 'move_approved_btn';
    const moveArchiveId = 'move_archive_btn';

    // В одобренные
    $('.button--icon--reply').before(`
        <button type="button" id="${moveApprovedId}" class="button rippleButton"
        style="background:#28a745;border:1px solid #1e7e34;border-radius:10px;color:white;font-weight:bold;padding:6px 14px;margin:4px;">
        В одобренные</button>`);

    // В неодобренные
    $('.button--icon--reply').before(`
        <button type="button" id="${moveRejectedId}" class="button rippleButton"
        style="background:#FF0000;border:1px solid #CC0000;border-radius:10px;color:white;font-weight:bold;padding:6px 14px;margin:4px;">
        В неодобренные</button>`);

    // В архив
    $('.button--icon--reply').before(`
        <button type="button" id="${moveArchiveId}" class="button rippleButton"
        style="background:#007bff;border:1px solid #0069d9;border-radius:10px;color:white;font-weight:bold;padding:6px 14px;margin:4px;">
        В архив</button>`);

    // Обработчики перемещений
    function moveThread(targetNodeId, prefixId) {
        if (typeof XF === 'undefined' || !XF.config || !XF.config.csrf) return;

        const threadUrl = document.URL.split('?')[0].replace(/\/$/, '');
        const moveUrl = `${threadUrl}/move`;
        const threadTitle = $('.p-title-value')[0].lastChild.textContent.trim();

        const data = {
            prefix_id: prefixId,
            title: threadTitle,
            target_node_id: targetNodeId,
            redirect_type: 'none',
            notify_watchers: 1,
            starter_alert: 1,
            starter_alert_reason: '',
            _xfToken: XF.config.csrf,
            _xfRequestUri: document.URL.split(XF.config.url.fullBase)[1],
            _xfWithData: 1,
            _xfResponseType: 'json',
            _xfRequest: 1
        };

        fetch(moveUrl, {
            method: 'POST',
            body: getFormData(data),
        }).then(() => location.reload())
        .catch(error => console.error('Ошибка перемещения:', error));
    }

    // Обработчики для кнопок
    $(document).on('click', `#${moveApprovedId}`, () => moveThread(MOVE_NODE_APPROVED, ODOBRENO_PREFIX));
    $(document).on('click', `#${moveRejectedId}`, () => moveThread(MOVE_NODE_REJECTED, OTKAZANO_PREFIX));
    $(document).on('click', `#${moveArchiveId}`, () => moveThread(MOVE_NODE_ARCHIVE, OTKAZANO_PREFIX));

    // --- ОБРАБОТЧИК ДЛЯ КНОПКИ ШАБЛОНОВ ---
    $(document).on('click', `#${mainButtonId}`, () => {
        const customTitle = 'Выберите шаблон RP-Биографии';
        XF.alert(buttonsMarkup(buttons), null, customTitle);
        setTimeout(() => { applyModalFixes(customTitle); }, 20);

        buttons.forEach((btn, id) => {
            if (!btn.content) return;
            $(document).off('click', `#answers-${id}`).on('click', `#answers-${id}`, function() {
                const send = btn.status;
                pasteContent(id, threadData, send);
            });
        });
    });

});
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址