Sleazy Fork is available in English.

Kemono2Neko

Transfer followings from kemono.su to nekohouse.su with a progress bar.

// ==UserScript==
// @name         Kemono2Neko
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Transfer followings from kemono.su to nekohouse.su with a progress bar.
// @author       NBXX
// @match        https://nekohouse.su/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license MIT
// @connect      kemono.su
// @connect      nekohouse.su
// ==/UserScript==

(function() {
    'use strict';

    // 定位到要嵌入按钮的目标位置
    const targetContainer = document.querySelector('body > div.global-sidebar > div.global-sidebar-entry.stuck-bottom > div');
    if (!targetContainer) {
        console.error('目标容器未找到,无法插入按钮。');
        return;
    }

    // 复制目标位置的格式,创建一个新的按钮容器
    const transferContainer = targetContainer.cloneNode(true);
    transferContainer.innerHTML = ''; // 清空原有内容
    const transferButton = document.createElement('div');
    transferButton.id = 'transfer-button';
    transferButton.innerText = '开始同步';
    transferButton.style.cursor = 'pointer';
    transferContainer.appendChild(transferButton);
    targetContainer.parentNode.insertBefore(transferContainer, targetContainer);

    // 创建一个进度条容器
    const progressBarContainer = document.createElement('div');
    progressBarContainer.id = 'progress-bar-container';
    progressBarContainer.style.display = 'none';
    document.body.appendChild(progressBarContainer);

    GM_addStyle(`
        #progress-bar-container {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 20px;
            background: #000;
            z-index: 10000;
            display: flex;
            overflow: hidden;
        }
        .progress-bar {
            height: 100%;
        }
        .progress-bar.success {
            background: green;
        }
        .progress-bar.failure {
            background: red;
        }
        .progress-bar.pending {
            background: black;
        }
    `);

    transferButton.addEventListener('click', startTransfer);

    function startTransfer() {
        // 显示进度条
        progressBarContainer.style.display = 'flex';

        // 获取 nekohouse.su 的现有关注数据
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://nekohouse.su/api/v1/account/favorites',
            onload: function(response) {
                if (response.status === 200) {
                    const existingFavorites = JSON.parse(response.responseText);
                    const existingIds = new Set(existingFavorites.map(fav => `${fav.service}:${fav.id}`));

                    // 获取 kemono.su 的关注数据
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: 'https://kemono.su/api/v1/account/favorites?type=artist',
                        onload: function(kemonoResponse) {
                            if (kemonoResponse.status === 200) {
                                let favorites = JSON.parse(kemonoResponse.responseText);

                                // 过滤掉已经存在的关注
                                favorites = favorites.filter(fav => !existingIds.has(`${fav.service}:${fav.id}`));
                                const total = favorites.length;

                                // 创建进度条元素
                                for (let i = 0; i < total; i++) {
                                    const progressBar = document.createElement('div');
                                    progressBar.className = 'progress-bar pending';
                                    progressBar.style.width = `${100 / total}%`;
                                    progressBarContainer.appendChild(progressBar);
                                }

                                // 开始逐个添加关注
                                let completed = 0;
                                favorites.forEach((fav, index) => {
                                    const url = `https://nekohouse.su/favorites/archive/${fav.service}/${fav.id}`;
                                    GM_xmlhttpRequest({
                                        method: 'POST',
                                        url: url,
                                        onload: function(postResponse) {
                                            const progressBar = progressBarContainer.children[index];
                                            if (postResponse.status === 200) {
                                                progressBar.classList.remove('pending');
                                                progressBar.classList.add('success');
                                                transferButton.innerText = `${fav.name} 已添加`;
                                            } else {
                                                progressBar.classList.remove('pending');
                                                progressBar.classList.add('failure');
                                            }
                                            // 检查是否完成
                                            completed++;
                                            if (completed === total) {
                                                setTimeout(() => {
                                                    progressBarContainer.style.display = 'none';
                                                    transferButton.innerText = '开始同步';
                                                }, 2000);
                                            }
                                        },
                                        onerror: function() {
                                            const progressBar = progressBarContainer.children[index];
                                            progressBar.classList.remove('pending');
                                            progressBar.classList.add('failure');
                                            // 检查是否完成
                                            completed++;
                                            if (completed === total) {
                                                setTimeout(() => {
                                                    progressBarContainer.style.display = 'none';
                                                    transferButton.innerText = '开始同步';
                                                }, 2000);
                                            }
                                        }
                                    });
                                });
                            } else {
                                console.error('Failed to fetch kemono favorites:', kemonoResponse);
                            }
                        },
                        onerror: function() {
                            console.error('Error occurred while trying to fetch kemono favorites.');
                        }
                    });
                } else {
                    console.error('Failed to fetch nekohouse favorites:', response);
                }
            },
            onerror: function() {
                console.error('Error occurred while trying to fetch nekohouse favorites.');
            }
        });
    }
})();