Poe Checkbox Selector

Selects all messages on poe.com for easy conversation sharing

目前為 2024-12-04 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Poe Checkbox Selector
  3. // @namespace https://codeberg.org/TwilightAlicorn/Poe-Checkbox-Selector
  4. // @author TwilightAlicorn
  5. // @version 3.1
  6. // @license MIT
  7. // @description Selects all messages on poe.com for easy conversation sharing
  8. // @match https://poe.com/*
  9. // @grant GM_log
  10. // @run-at document-idle
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const state = {
  17. isRunning: false,
  18. currentButton: null,
  19. checkInterval: null,
  20. lastPath: null
  21. };
  22.  
  23. const CONSTANTS = {
  24. CLICK_DELAY: 100,
  25. CHECK_INTERVAL: 500,
  26. SCROLL_ATTEMPTS: 30,
  27. SCROLL_AMOUNT: 20000,
  28. SELECTORS: {
  29. checkboxLabels: 'label[class*="checkbox-and-radio_label__"][class*="ChatMessage_checkbox__"]',
  30. rightNavItem: '.BaseNavbar_rightNavItem__3DfWJ span',
  31. mainContent: '[class*="ChatPageMain"]',
  32. selectionHeader: 'div[class*="LeftSideChatMessageHeader_leftSideMessageHeader__"][class*="LeftSideChatMessageHeader_selectionModeMessageHeader__"]',
  33. botInfoCard: 'div[class*="BotInfoCard_sectionContainer__"]'
  34. }
  35. };
  36.  
  37. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  38.  
  39. async function scrollToTop() {
  40.  
  41. const possibleContainers = [
  42. document.querySelector(CONSTANTS.SELECTORS.mainContent),
  43. document.querySelector('[class*="ChatMessagesView"]'),
  44. document.querySelector('[class*="InfiniteScroll"]'),
  45. document.querySelector('.ChatPage_chatPageContent__'),
  46. Array.from(document.querySelectorAll('div'))
  47. .find(div => div.scrollHeight > window.innerHeight * 2)
  48. ].filter(Boolean);
  49.  
  50. for (const container of possibleContainers) {
  51. let lastScrollTop = container.scrollTop;
  52.  
  53. for (let attempt = 0; attempt < CONSTANTS.SCROLL_ATTEMPTS; attempt++) {
  54. if (botInfoCardExists()) return;
  55.  
  56. container.scroll({
  57. top: container.scrollTop - CONSTANTS.SCROLL_AMOUNT,
  58. behavior: 'smooth'
  59. });
  60.  
  61. if (container.scrollTop === lastScrollTop) break;
  62. lastScrollTop = container.scrollTop;
  63. await sleep(25);
  64. }
  65.  
  66. if (botInfoCardExists()) return;
  67. }
  68. }
  69.  
  70. function botInfoCardExists() {
  71. return !!document.querySelector(CONSTANTS.SELECTORS.botInfoCard);
  72. }
  73.  
  74. async function selectCheckboxes() {
  75. if (!state.isRunning) return;
  76.  
  77. if (!botInfoCardExists()) {
  78. await scrollToTop();
  79. return;
  80. }
  81.  
  82. const labels = document.querySelectorAll(CONSTANTS.SELECTORS.checkboxLabels);
  83.  
  84. for (const label of labels) {
  85. if (!state.isRunning) return;
  86.  
  87. const checkbox = label.querySelector('input[type="checkbox"]');
  88. if (checkbox && !checkbox.checked) {
  89. try {
  90. label.click();
  91. await sleep(CONSTANTS.CLICK_DELAY);
  92. } catch (error) {
  93. GM_log(`Click error: ${error.message}`);
  94. }
  95. }
  96. }
  97.  
  98. if (botInfoCardExists()) {
  99. stopScript();
  100. }
  101. }
  102.  
  103. function updateButtonStyle() {
  104. if (!state.currentButton) return;
  105.  
  106. const innerWrap = state.currentButton.querySelector('.button_innerWrap__BtYlH');
  107. if (innerWrap) {
  108. const allSelected = document.querySelectorAll('input[type="checkbox"]:not(:checked)').length === 0;
  109. innerWrap.setAttribute('data-state', state.isRunning ? 'loading' : (allSelected ? 'success' : 'idle'));
  110. }
  111. }
  112.  
  113. function stopScript() {
  114. state.isRunning = false;
  115. if (state.checkInterval) {
  116. clearInterval(state.checkInterval);
  117. state.checkInterval = null;
  118. }
  119. updateButtonStyle();
  120. }
  121.  
  122. function toggleScript() {
  123. state.isRunning = !state.isRunning;
  124.  
  125. if (state.isRunning) {
  126. selectCheckboxes();
  127. if (!state.checkInterval) {
  128. state.checkInterval = setInterval(selectCheckboxes, CONSTANTS.CHECK_INTERVAL);
  129. }
  130. } else {
  131. stopScript();
  132. }
  133.  
  134. updateButtonStyle();
  135. }
  136.  
  137. function createToggleButton() {
  138. const button = document.createElement('button');
  139. button.id = 'auto-select-button';
  140. button.className = 'button_root__TL8nv button_ghost__YsMI5 button_sm__hWzjK button_center__RsQ_o button_showIconOnly-compact-below___fiXt';
  141. button.type = 'button';
  142. button.innerHTML = `
  143. <style>
  144. @keyframes spin {
  145. 0% { transform: rotate(0deg); }
  146. 100% { transform: rotate(360deg); }
  147. }
  148.  
  149. #auto-select-button {
  150. display: flex;
  151. align-items: center;
  152. gap: 8px;
  153. }
  154.  
  155. #auto-select-button .button_innerWrap__BtYlH {
  156. display: flex;
  157. align-items: center;
  158. gap: 2px;
  159. }
  160.  
  161. #auto-select-button .checkbox-icon,
  162. #auto-select-button .loading-icon,
  163. #auto-select-button .success-icon {
  164. display: none !important;
  165. height: 18px;
  166. width: 18px;
  167. flex: 0 0 auto;
  168. }
  169.  
  170. #auto-select-button .loading-icon {
  171. animation: spin 1s linear infinite;
  172. }
  173.  
  174. #auto-select-button [data-state="idle"] .checkbox-icon,
  175. #auto-select-button [data-state="loading"] .loading-icon,
  176. #auto-select-button [data-state="success"] .success-icon {
  177. display: block !important;
  178. }
  179.  
  180. #auto-select-button .button_label__mCaDf {
  181. display: inline-block;
  182. margin-left: 4px;
  183. }
  184. </style>
  185. <span class="button_innerWrap__BtYlH" data-state="idle">
  186. <svg class="checkbox-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24">
  187. <path fill="currentColor" d="M19 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.11 21 21 20.1 21 19V5C21 3.9 20.11 3 19 3ZM19 19H5V5H19V19Z"/>
  188. <path class="checkbox-fill" fill="currentColor" d="M17 7H7V17H17V7Z" fill-opacity="0"/>
  189. </svg>
  190. <svg class="loading-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
  191. <path fill="currentColor" d="M12 2A10 10 0 1 0 22 12A10 10 0 0 0 12 2Zm0 18a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z" opacity=".5"/>
  192. <path fill="currentColor" d="M20 12h2A10 10 0 0 0 12 2V4A8 8 0 0 1 20 12Z"/>
  193. </svg>
  194. <svg class="success-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
  195. <path fill="currentColor" d="M19 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.11 21 21 20.1 21 19V5C21 3.9 20.11 3 19 3ZM19 19H5V5H19V19Z"/>
  196. <path fill="currentColor" d="M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z"/>
  197. </svg>
  198. <span class="button_label__mCaDf">Auto Select</span>
  199. </span>
  200. `;
  201.  
  202. button.addEventListener('click', (e) => {
  203. e.preventDefault();
  204. e.stopPropagation();
  205. toggleScript();
  206. });
  207.  
  208. return button;
  209. }
  210.  
  211. function insertButton() {
  212. const existingButton = document.getElementById('auto-select-button');
  213. const selectionHeader = document.querySelector(CONSTANTS.SELECTORS.selectionHeader);
  214.  
  215. if (!selectionHeader) {
  216. if (state.isRunning) stopScript();
  217. if (existingButton) {
  218. existingButton.remove();
  219. state.currentButton = null;
  220. }
  221. return false;
  222. }
  223.  
  224. if (existingButton) {
  225. state.currentButton = existingButton;
  226. return true;
  227. }
  228.  
  229. if (state.currentButton) {
  230. state.currentButton.remove();
  231. state.currentButton = null;
  232. }
  233.  
  234. const rightNavItem = document.querySelector(CONSTANTS.SELECTORS.rightNavItem);
  235. if (!rightNavItem) return false;
  236.  
  237. const button = createToggleButton();
  238. rightNavItem.insertBefore(button, rightNavItem.firstChild);
  239. state.currentButton = button;
  240. return true;
  241. }
  242.  
  243. function setupObservers() {
  244. const mutationObserver = new MutationObserver(() => {
  245. insertButton();
  246. });
  247.  
  248. mutationObserver.observe(document.body, {
  249. childList: true,
  250. subtree: true,
  251. attributes: true,
  252. attributeFilter: ['class']
  253. });
  254.  
  255. const routeObserver = new MutationObserver(() => {
  256. const currentPath = window.location.pathname;
  257. if (currentPath !== state.lastPath) {
  258. state.lastPath = currentPath;
  259. if (currentPath.includes('/chat/')) {
  260. waitForMainContent().then(() => setTimeout(insertButton, 500));
  261. } else {
  262. if (state.currentButton) {
  263. state.currentButton.remove();
  264. state.currentButton = null;
  265. }
  266. stopScript();
  267. }
  268. }
  269. });
  270.  
  271. routeObserver.observe(document.querySelector('head > title'), {
  272. subtree: true,
  273. characterData: true,
  274. childList: true
  275. });
  276. }
  277.  
  278. function waitForMainContent() {
  279. return new Promise((resolve) => {
  280. const check = () => {
  281. if (document.querySelector(CONSTANTS.SELECTORS.mainContent)) {
  282. resolve();
  283. } else {
  284. setTimeout(check, 100);
  285. }
  286. };
  287. check();
  288. });
  289. }
  290.  
  291. function initialize() {
  292. state.lastPath = window.location.pathname;
  293. if (window.location.pathname.includes('/chat/')) {
  294. waitForMainContent().then(() => setTimeout(insertButton, 500));
  295. }
  296. setupObservers();
  297. }
  298.  
  299. if (document.readyState === 'complete') {
  300. initialize();
  301. } else {
  302. window.addEventListener('load', initialize);
  303. }
  304. })();

QingJ © 2025

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