OpenRouter Activity Exporter

Export OpenRouter activity data to JSON

当前为 2024-10-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name OpenRouter Activity Exporter
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description Export OpenRouter activity data to JSON
  6. // @author Romboter
  7. // @match https://openrouter.ai/activity*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_addStyle
  10. // @license GNU GPLv3
  11. // ==/UserScript==
  12. /* jshint esversion: 8 */
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. let activityData = [];
  18. let currentPage = 1;
  19. const DEBUG = false; // Set to false to minimize logs in production
  20. const MAX_RETRIES = 3;
  21.  
  22. // Create a floating button
  23. const button = document.createElement('button');
  24. button.innerHTML = 'Export Activity';
  25. button.id = 'orp-export-activity-button';
  26. document.body.appendChild(button);
  27.  
  28. // Style the button
  29. GM_addStyle(`#orp-export-activity-button {
  30. position: fixed;
  31. bottom: 20px;
  32. right: 20px;
  33. padding: 10px 20px;
  34. background-color: #007bff;
  35. color: white;
  36. border: none;
  37. border-radius: 5px;
  38. cursor: pointer;
  39. z-index: 1000;
  40. }
  41. #orp-export-activity-button:hover {
  42. background-color: #0056b3;
  43. }`);
  44.  
  45. // Attach click event to the button
  46. button.addEventListener('click', function() {
  47. button.disabled = true;
  48. button.innerHTML = 'Loading...';
  49. fetchActivity(currentPage);
  50. });
  51.  
  52. function log(...args) {
  53. if (DEBUG) {
  54. console.log(...args);
  55. }
  56. }
  57.  
  58. function handleError(message) {
  59. alert(`Error: ${message}`);
  60. button.disabled = false;
  61. button.innerHTML = 'Export Activity';
  62. }
  63.  
  64. async function retryFetch(url, options, retries = MAX_RETRIES) {
  65. for (let i = 0; i < retries; i++) {
  66. try {
  67. const response = await fetch(url, options);
  68. if (response.ok) {
  69. return response;
  70. }
  71. } catch (e) {
  72. log(`Fetch attempt ${i + 1} failed:`, e);
  73. }
  74. }
  75. throw new Error('Maximum retries reached for fetching activity data.');
  76. }
  77.  
  78. function extractData(responseText) {
  79. const data = {
  80. transactions: null,
  81. appInfo: null,
  82. pagination: null
  83. };
  84.  
  85. // Extract transactions array
  86. const transactionsRegex = /"transactions":\s*(\[[\s\S]*?\])\s*\]/;
  87. const transactionsMatch = responseText.match(transactionsRegex);
  88.  
  89. if (transactionsMatch) {
  90. const individualTransactionRegex = /\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/g;
  91. const individualTransactions = transactionsMatch[1].match(individualTransactionRegex);
  92. if (individualTransactions) {
  93. data.transactions = individualTransactions.map(transaction => {
  94. try {
  95. const parsedTransaction = JSON.parse(transaction);
  96. // Filter out pagination object if mistakenly included
  97. if (parsedTransaction.page !== undefined && parsedTransaction.hasNextPage !== undefined) {
  98. return null;
  99. }
  100. return parsedTransaction;
  101. } catch (parseError) {
  102. log('Error parsing individual transaction:', parseError);
  103. return null;
  104. }
  105. }).filter(Boolean);
  106. }
  107.  
  108. if (!data.transactions || data.transactions.length === 0) {
  109. log('Failed to parse any transactions');
  110. }
  111. } else {
  112. log('No transactions match found');
  113. }
  114.  
  115. // Extract pagination info
  116. const paginationRegex = /"page":\s*(\d+),\s*"hasNextPage":\s*(true|false)/;
  117. const paginationMatch = responseText.match(paginationRegex);
  118.  
  119. if (paginationMatch) {
  120. data.pagination = {
  121. page: parseInt(paginationMatch[1]),
  122. hasNextPage: paginationMatch[2] === 'true'
  123. };
  124. } else {
  125. log('No pagination match found');
  126. }
  127.  
  128. return data;
  129. }
  130.  
  131. async function fetchActivity(page) {
  132. try {
  133. log(`Fetching activity for page: ${page}`);
  134. const url = `https://openrouter.ai/activity?page=${page}`;
  135. const options = {
  136. "credentials": "include",
  137. "headers": {
  138. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
  139. "Accept": "*/*",
  140. "Accept-Language": "en-US,en;q=0.5",
  141. "RSC": "1",
  142. "Next-Url": "/activity",
  143. "Priority": "u=0"
  144. },
  145. "referrer": "https://openrouter.ai/activity",
  146. "method": "GET",
  147. "mode": "cors"
  148. };
  149.  
  150. const response = await retryFetch(url, options);
  151. const responseText = await response.text();
  152.  
  153. if (responseText.startsWith("<!DOCTYPE html>")) {
  154. handleError("Received HTML instead of JSON. Possible login issue.");
  155. return;
  156. }
  157.  
  158. const extractedData = extractData(responseText);
  159.  
  160. if (extractedData.transactions && Array.isArray(extractedData.transactions)) {
  161. activityData.push(...extractedData.transactions);
  162. log(`Added ${extractedData.transactions.length} transactions. Total transactions: ${activityData.length}`);
  163. } else {
  164. log('No valid transactions data found in response.');
  165. }
  166.  
  167. if (extractedData.pagination && extractedData.pagination.hasNextPage) {
  168. log('Next page found, fetching next page...');
  169. button.innerHTML = `Loading... (Page ${page})`;
  170. fetchActivity(extractedData.pagination.page + 1);
  171. } else {
  172. log('No more pages, downloading activity data...');
  173. downloadActivityData();
  174. }
  175. } catch (e) {
  176. handleError("Error fetching activity data: " + e.message);
  177. }
  178. }
  179.  
  180. function downloadActivityData() {
  181. log('Downloading activity data. Total transactions:', activityData.length);
  182. const blob = new Blob([JSON.stringify(activityData, null, 2)], { type: "application/json" });
  183. const url = URL.createObjectURL(blob);
  184. const a = document.createElement('a');
  185. a.href = url;
  186. a.download = 'activity_transactions.json';
  187. a.click();
  188. URL.revokeObjectURL(url);
  189. button.disabled = false;
  190. button.innerHTML = 'Export Activity';
  191. }
  192. })();

QingJ © 2025

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