Custom Bilibili Auto Follow/Unfollow

A script to automatically follow/unfollow on Bilibili with enhanced UI and controls.

  1. // ==UserScript==
  2. // @name Custom Bilibili Auto Follow/Unfollow
  3. // @namespace https://github.com/Larch4/Custom-Bilibili-Auto-Follow-Unfollow
  4. // @version 5.5
  5. // @description A script to automatically follow/unfollow on Bilibili with enhanced UI and controls.
  6. // @author Larch4
  7. // @match https://space.bilibili.com/*
  8. // @grant none
  9. // @license GNU Affero General Public License v3.0
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const FOLLOW_INTERVAL_DEFAULT = 2000; // 关注时间间隔(默认2秒)
  16. const UNFOLLOW_INTERVAL_DEFAULT = 2000; // 取消关注时间间隔(默认2秒)
  17.  
  18. let timeoutId = null;
  19. let isRunning = false;
  20. let useRandomInterval = true; // 是否使用随机间隔
  21. let followUnfollowTimeout = null; // 关注/取消关注的定时器
  22. let modalCheckInterval = null; // 弹窗检查的定时器
  23. let followInterval = FOLLOW_INTERVAL_DEFAULT;
  24. let unfollowInterval = UNFOLLOW_INTERVAL_DEFAULT;
  25.  
  26. function createPanel() {
  27. const panel = document.createElement('div');
  28. panel.id = 'bilibili-auto-follow-panel';
  29. panel.style.cssText = `
  30. position: fixed; bottom: 20px; right: 20px;
  31. background-color: #fff; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
  32. color: #333; padding: 15px; border-radius: 10px; z-index: 10000;
  33. display: flex; flex-direction: column; align-items: stretch; width: 240px;
  34. font-family: Arial, sans-serif; transition: max-height 0.3s ease, opacity 0.3s ease; /* 添加过渡效果 */
  35. overflow: hidden; max-height: 400px; /* 初始的最大高度 */
  36. opacity: 1; /* 初始不透明 */
  37. `;
  38.  
  39. panel.innerHTML = `
  40. <div style="font-size: 16px; font-weight: bold; margin-bottom: 10px; text-align: center; color: #00a1d6;">
  41. Bilibili 自动关注脚本
  42. <button id="foldButton" style="
  43. background-color: #00a1d6; color: #fff; border: none; padding: 6px 10px;
  44. border-radius: 6px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; align-self: center; margin-bottom: 0px; margin-left: 20px;">展开</button>
  45. </div>
  46.  
  47. <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
  48. <button id="toggleButton" style="
  49. flex: 1; background-color: #00a1d6; color: #fff; border: none; padding: 8px 12px;
  50. border-radius: 6px; cursor: pointer; font-size: 14px; transition: background-color 0.3s;
  51. ">开始</button>
  52. <span id="statusText" style="flex: 1; margin-left: 105px; font-size: 14px; color: #666;">未运行</span>
  53. </div>
  54.  
  55. <div id="additionalContent" style="max-height: 0; opacity: 0; overflow: hidden; transition: max-height 0.5s ease, opacity 0.5s ease;">
  56. <div style="margin-bottom: 10px; display: flex; flex-direction: column;">
  57. <label>关注间隔(秒):</label>
  58. <input id="followIntervalInput" type="number" min="1" value="${FOLLOW_INTERVAL_DEFAULT / 1000}" style="
  59. width: 95%; padding: 5px; margin-top: 5px; border: 1px solid #ddd; border-radius: 4px;
  60. ">
  61. </div>
  62. <div style="margin-bottom: 10px; display: flex; flex-direction: column;">
  63. <label>取消关注间隔(秒):</label>
  64. <input id="unfollowIntervalInput" type="number" min="1" value="${UNFOLLOW_INTERVAL_DEFAULT / 1000}" style="
  65. width: 95%; padding: 5px; margin-top: 5px; border: 1px solid #ddd; border-radius: 4px;
  66. ">
  67. </div>
  68.  
  69. <div style="display: flex; align-items: center; margin-bottom: 10px;">
  70. <input id="useRandomInterval" type="checkbox" checked style="margin-right: 9px;">
  71. <label for="useRandomInterval" style="font-size: 14px; color: #333; cursor: pointer;">
  72. 使用随机时间间隔
  73. </label>
  74. </div>
  75.  
  76.  
  77. <div id="logContainer" style="
  78. max-height: 120px; overflow-y: auto; border: 1px solid #ddd; padding: 5px; margin-top: 10px; border-radius: 4px;
  79. font-size: 12px; color: #333; background-color: #f9f9f9;
  80. "></div>
  81. </div>
  82.  
  83. <div id="errorMessage" style="color: red; font-size: 12px; margin-top: 10px;"></div>
  84. `;
  85.  
  86. document.body.appendChild(panel);
  87. makePanelDraggable(panel);
  88.  
  89. const toggleButton = panel.querySelector('#toggleButton');
  90. toggleButton.addEventListener('click', toggleScript);
  91.  
  92. const foldButton = panel.querySelector('#foldButton');
  93. const additionalContent = panel.querySelector('#additionalContent');
  94. let isFolded = true; // 初始状态为折叠
  95.  
  96. foldButton.addEventListener('click', () => {
  97. if (isFolded) {
  98. additionalContent.style.maxHeight = '400px'; // 设置合适的高度以显示完整内容
  99. additionalContent.style.opacity = '1'; // 设为完全不透明
  100. foldButton.textContent = '收缩';
  101. } else {
  102. additionalContent.style.maxHeight = '0';
  103. additionalContent.style.opacity = '0';
  104. foldButton.textContent = '展开';
  105. }
  106. isFolded = !isFolded;
  107. });
  108.  
  109. // 绑定按钮和复选框事件
  110. document.getElementById('toggleButton').addEventListener('click', toggleScript);
  111. document.getElementById('useRandomInterval').addEventListener('change', toggleRandomInterval);
  112.  
  113. panel.querySelector('#followIntervalInput').addEventListener('input', (e) => {
  114. followInterval = parseInt(e.target.value, 10) * 1000;
  115. });
  116. panel.querySelector('#unfollowIntervalInput').addEventListener('input', (e) => {
  117. unfollowInterval = parseInt(e.target.value, 10) * 1000;
  118. });
  119.  
  120. return {
  121. toggleButton,
  122. statusText: panel.querySelector('#statusText'),
  123. errorMessage: panel.querySelector('#errorMessage'),
  124. logContainer: panel.querySelector('#logContainer')
  125. };
  126. }
  127.  
  128. function makePanelDraggable(panel) {
  129. let isDragging = false;
  130. let offsetX = 0;
  131. let offsetY = 0;
  132.  
  133. panel.addEventListener('mousedown', (e) => {
  134. // 检查点击的元素,如果是按钮、文本或输入框,则不启动拖动
  135. if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SPAN' || e.target.tagName === 'INPUT') return;
  136.  
  137. isDragging = true;
  138. offsetX = e.clientX - panel.getBoundingClientRect().left; // 使用 getBoundingClientRect() 获取准确的左偏移量
  139. offsetY = e.clientY - panel.getBoundingClientRect().top; // 使用 getBoundingClientRect() 获取准确的顶部偏移量
  140. e.preventDefault();
  141.  
  142. // 添加事件监听器
  143. document.addEventListener('mousemove', onMouseMove);
  144. document.addEventListener('mouseup', onMouseUp);
  145. });
  146.  
  147. function onMouseMove(e) {
  148. if (isDragging) {
  149. // 只有在拖动过程中才修改面板的位置
  150. panel.style.right = 'auto';
  151. panel.style.bottom = 'auto';
  152. panel.style.left = `${e.clientX - offsetX}px`;
  153. panel.style.top = `${e.clientY - offsetY}px`;
  154. }
  155. }
  156.  
  157. function onMouseUp() {
  158. if (isDragging) {
  159. isDragging = false;
  160. // 移除事件监听器
  161. document.removeEventListener('mousemove', onMouseMove);
  162. document.removeEventListener('mouseup', onMouseUp);
  163. }
  164. }
  165. }
  166.  
  167. function toggleScript() {
  168. if (isRunning) {
  169. stopAutoFollowUnfollow();
  170. } else {
  171. startAutoFollowUnfollow();
  172. }
  173. isRunning = !isRunning;
  174. }
  175.  
  176. function startAutoFollowUnfollow() {
  177. toggleFollowState(1); // 开始时默认进入关注状态
  178. modalCheckInterval = setInterval(removeModal, 1000); // 启动弹窗检查定时器
  179. updateUI('暂停', '#f25d8e', '运行中');
  180. }
  181. function stopAutoFollowUnfollow() {
  182. clearTimeout(followUnfollowTimeout);
  183. clearInterval(modalCheckInterval); // 清除弹窗检查定时器
  184. updateUI('开始', '#00a1d6', '未运行');
  185. }
  186.  
  187. function removeModal() {
  188. const modal = document.querySelector('.modal-container');
  189. if (modal) {
  190. modal.remove();
  191. }
  192. }
  193.  
  194. // 切换随机间隔选项
  195. function toggleRandomInterval() {
  196. useRandomInterval = document.getElementById('useRandomInterval').checked;
  197. }
  198.  
  199. function updateUI(buttonText, buttonColor, statusTextValue) {
  200. const toggleButton = document.getElementById('toggleButton');
  201. const statusText = document.getElementById('statusText');
  202. toggleButton.textContent = buttonText;
  203. toggleButton.style.backgroundColor = buttonColor;
  204. statusText.textContent = statusTextValue;
  205. }
  206.  
  207. function toggleFollowState(action) {
  208. followOrUnfollow(action);
  209. const nextAction = action === 0 ? 1 : 0;
  210. const delay = getInterval(action === 0 ? unfollowInterval : followInterval); // 使用随机间隔
  211. followUnfollowTimeout = setTimeout(() => toggleFollowState(nextAction), delay);
  212. }
  213.  
  214. function followOrUnfollow(action) {
  215. try {
  216. let button;
  217. if (action === 0) { // 取消关注
  218. const dropdown = document.querySelector('.be-dropdown.h-f-btn.h-unfollow');
  219. if (dropdown) {
  220. const dropdownMenu = dropdown.querySelector('.be-dropdown-menu');
  221. if (dropdownMenu && dropdownMenu.style.display === 'none') {
  222. dropdownMenu.style.display = 'block'; // 显示菜单
  223. }
  224.  
  225. button = dropdownMenu ? Array.from(dropdownMenu.querySelectorAll('.be-dropdown-item'))
  226. .find(item => item.textContent.trim() === '取消关注') : null;
  227. }
  228. } else { // 关注
  229. button = document.querySelector(".h-f-btn.h-follow");
  230. }
  231.  
  232. if (button && button.offsetParent !== null) {
  233. button.click();
  234. logMessage(`已${action === 0 ? '取消关注' : '关注'}一个UP主`);
  235. } else {
  236. showErrorMessage(`未找到${action === 1 ? '' : '取消'}关注按钮`);
  237. }
  238. } catch (error) {
  239. showErrorMessage("执行关注/取消关注时出错: " + error.message);
  240. }
  241. }
  242.  
  243. function getInterval(baseInterval) {
  244. let interval = useRandomInterval ? baseInterval + Math.random() * 1000 : baseInterval;
  245. console.log(`当前间隔时间: ${interval} 毫秒`); // 输出当前间隔时间
  246. return interval;
  247. }
  248.  
  249. function logMessage(message) {
  250. const logContainer = document.getElementById('logContainer');
  251. const logEntry = document.createElement('div');
  252. logEntry.textContent = message;
  253. logContainer.appendChild(logEntry);
  254. logContainer.scrollTop = logContainer.scrollHeight;
  255. }
  256.  
  257. function showErrorMessage(message) {
  258. const errorMessage = document.getElementById('errorMessage');
  259. if (errorMessage) {
  260. errorMessage.textContent = message;
  261. setTimeout(() => {
  262. errorMessage.textContent = '';
  263. }, 3000);
  264. }
  265. }
  266.  
  267. const { toggleButton, statusText, errorMessage, logContainer } = createPanel();
  268. })();

QingJ © 2025

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