Sleazy Fork is available in English.

Kemono2Neko

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

질문, 리뷰하거나, 이 스크립트를 신고하세요.
  1. // ==UserScript==
  2. // @name Kemono2Neko
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description Transfer followings from kemono.su to nekohouse.su with a progress bar.
  6. // @author NBXX
  7. // @match https://nekohouse.su/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_addStyle
  10. // @license MIT
  11. // @connect kemono.su
  12. // @connect nekohouse.su
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // 定位到要嵌入按钮的目标位置
  19. const targetContainer = document.querySelector('body > div.global-sidebar > div.global-sidebar-entry.stuck-bottom > div');
  20. if (!targetContainer) {
  21. console.error('目标容器未找到,无法插入按钮。');
  22. return;
  23. }
  24.  
  25. // 复制目标位置的格式,创建一个新的按钮容器
  26. const transferContainer = targetContainer.cloneNode(true);
  27. transferContainer.innerHTML = ''; // 清空原有内容
  28. const transferButton = document.createElement('div');
  29. transferButton.id = 'transfer-button';
  30. transferButton.innerText = '开始同步';
  31. transferButton.style.cursor = 'pointer';
  32. transferContainer.appendChild(transferButton);
  33. targetContainer.parentNode.insertBefore(transferContainer, targetContainer);
  34.  
  35. // 创建一个进度条容器
  36. const progressBarContainer = document.createElement('div');
  37. progressBarContainer.id = 'progress-bar-container';
  38. progressBarContainer.style.display = 'none';
  39. document.body.appendChild(progressBarContainer);
  40.  
  41. GM_addStyle(`
  42. #progress-bar-container {
  43. position: fixed;
  44. bottom: 0;
  45. left: 0;
  46. width: 100%;
  47. height: 20px;
  48. background: #000;
  49. z-index: 10000;
  50. display: flex;
  51. overflow: hidden;
  52. }
  53. .progress-bar {
  54. height: 100%;
  55. }
  56. .progress-bar.success {
  57. background: green;
  58. }
  59. .progress-bar.failure {
  60. background: red;
  61. }
  62. .progress-bar.pending {
  63. background: black;
  64. }
  65. `);
  66.  
  67. transferButton.addEventListener('click', startTransfer);
  68.  
  69. function startTransfer() {
  70. // 显示进度条
  71. progressBarContainer.style.display = 'flex';
  72.  
  73. // 获取 nekohouse.su 的现有关注数据
  74. GM_xmlhttpRequest({
  75. method: 'GET',
  76. url: 'https://nekohouse.su/api/v1/account/favorites',
  77. onload: function(response) {
  78. if (response.status === 200) {
  79. const existingFavorites = JSON.parse(response.responseText);
  80. const existingIds = new Set(existingFavorites.map(fav => `${fav.service}:${fav.id}`));
  81.  
  82. // 获取 kemono.su 的关注数据
  83. GM_xmlhttpRequest({
  84. method: 'GET',
  85. url: 'https://kemono.su/api/v1/account/favorites?type=artist',
  86. onload: function(kemonoResponse) {
  87. if (kemonoResponse.status === 200) {
  88. let favorites = JSON.parse(kemonoResponse.responseText);
  89.  
  90. // 过滤掉已经存在的关注
  91. favorites = favorites.filter(fav => !existingIds.has(`${fav.service}:${fav.id}`));
  92. const total = favorites.length;
  93.  
  94. // 创建进度条元素
  95. for (let i = 0; i < total; i++) {
  96. const progressBar = document.createElement('div');
  97. progressBar.className = 'progress-bar pending';
  98. progressBar.style.width = `${100 / total}%`;
  99. progressBarContainer.appendChild(progressBar);
  100. }
  101.  
  102. // 开始逐个添加关注
  103. let completed = 0;
  104. favorites.forEach((fav, index) => {
  105. const url = `https://nekohouse.su/favorites/archive/${fav.service}/${fav.id}`;
  106. GM_xmlhttpRequest({
  107. method: 'POST',
  108. url: url,
  109. onload: function(postResponse) {
  110. const progressBar = progressBarContainer.children[index];
  111. if (postResponse.status === 200) {
  112. progressBar.classList.remove('pending');
  113. progressBar.classList.add('success');
  114. transferButton.innerText = `${fav.name} 已添加`;
  115. } else {
  116. progressBar.classList.remove('pending');
  117. progressBar.classList.add('failure');
  118. }
  119. // 检查是否完成
  120. completed++;
  121. if (completed === total) {
  122. setTimeout(() => {
  123. progressBarContainer.style.display = 'none';
  124. transferButton.innerText = '开始同步';
  125. }, 2000);
  126. }
  127. },
  128. onerror: function() {
  129. const progressBar = progressBarContainer.children[index];
  130. progressBar.classList.remove('pending');
  131. progressBar.classList.add('failure');
  132. // 检查是否完成
  133. completed++;
  134. if (completed === total) {
  135. setTimeout(() => {
  136. progressBarContainer.style.display = 'none';
  137. transferButton.innerText = '开始同步';
  138. }, 2000);
  139. }
  140. }
  141. });
  142. });
  143. } else {
  144. console.error('Failed to fetch kemono favorites:', kemonoResponse);
  145. }
  146. },
  147. onerror: function() {
  148. console.error('Error occurred while trying to fetch kemono favorites.');
  149. }
  150. });
  151. } else {
  152. console.error('Failed to fetch nekohouse favorites:', response);
  153. }
  154. },
  155. onerror: function() {
  156. console.error('Error occurred while trying to fetch nekohouse favorites.');
  157. }
  158. });
  159. }
  160. })();