检测你的api key被倒了几手

也是注册上了,在论坛看到了一个无脑复刻的项目来学习了一下
可以知道你的api key经历了几次中转
对自己的api key有疑惑的佬友可以过来测一下

(提示一下:只支持openai系列。如果长时间没有响应,大概率不是真的az或者官方的。检测原理是通过发送一张图片,没有响应意味着不支持图片输入。当然不排除误判的可能,还是得根据实际情况自己判断)

API 测试工具 (2535158651.workers.dev)

部署的教程可以看下面这篇帖子

[教程][关于api转了几手]一个可部署于workers的私人api中转溯源工具 - 开发调优 - LINUX DO

具体的部署可以找原帖的佬友问一下,或者直接在下面问我也行,我也是新手,共同学习,一起进步
贴上源码

const CONFIG = {
    WORKER_DOMAIN: '<填上你的worker 的域名>', // worker 的域名
  };
  
  const HTML_CONTENT = `
  <!DOCTYPE html>
  <html lang="zh">
  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>API 测试工具</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
      <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
      <style>
          body {
              font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
              background-color: #FAF9F6;
          }
          
          .loading-dots:after {
              content: ' .';
              animation: dots 1.5s steps(5, end) infinite;
          }
          
          @keyframes dots {
              0%, 20% { content: ' .' }
              40% { content: ' ..' }
              60% { content: ' ...' }
              80%, 100% { content: ' ....' }
          }
          
          .fade-in {
              animation: fadeIn 0.3s ease-in;
          }
          
          @keyframes fadeIn {
              from { opacity: 0; }
              to { opacity: 1; }
          }
  
          pre {
              transition: all 0.2s ease;
          }
  
          input:focus, button:focus {
              outline: none;
              ring-color: #B47464;
          }
  
          .btn-primary {
              background-color: #B47464;
              transition: background-color 0.2s ease;
          }
  
          .btn-primary:hover {
              background-color: #9A6557;
          }
      </style>
  </head>
  <body class="bg-gray-100">
      <div class="container mx-auto px-4 py-12">
          <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-7xl mx-auto">
              <!-- 左侧面板:表单和日志 -->
              <div class="bg-white rounded-2xl shadow-lg p-8">
                  <h1 class="text-4xl font-semibold mb-8 text-center text-gray-800">API 测试工具</h1>
                  
                  <form id="apiForm" class="space-y-6">
                      <div>
                          <label class="block text-base font-medium text-gray-700 mb-2">API URL (v1/chat/completions)</label>
                          <input type="text" id="url" 
                                 class="block w-full rounded-xl border-gray-300 shadow-sm p-3 border focus:border-[#B47464] focus:ring-2 focus:ring-[#B47464]" 
                                 placeholder="https://api.example.com/v1/chat/completions">
                      </div>
                      
                      <div>
                          <label class="block text-base font-medium text-gray-700 mb-2">API Key</label>
                          <input type="password" id="key" 
                                 class="block w-full rounded-xl border-gray-300 shadow-sm p-3 border focus:border-[#B47464] focus:ring-2 focus:ring-[#B47464]">
                      </div>
                      
                      <div>
                          <label class="block text-base font-medium text-gray-700 mb-2">模型</label>
                          <input type="text" id="model" 
                                 class="block w-full rounded-xl border-gray-300 shadow-sm p-3 border focus:border-[#B47464] focus:ring-2 focus:ring-[#B47464]" 
                                 value="gpt-4o" placeholder="gpt-4o">
                      </div>
                      
                      <button type="submit" 
                              class="w-full btn-primary text-white rounded-xl py-3 text-lg font-medium focus:outline-none focus:ring-2 focus:ring-[#B47464] focus:ring-offset-2">
                          发送请求
                      </button>
                  </form>
  
                  <div class="mt-8 space-y-6">
                      <div class="border-t border-gray-100 pt-6">
                          <h2 class="text-xl font-medium mb-3 text-gray-800">请求状态</h2>
                          <pre id="status" class="bg-gray-50 p-4 rounded-xl overflow-x-auto text-gray-700"></pre>
                      </div>
                      
                      <div class="border-t border-gray-100 pt-6">
                          <h2 class="text-xl font-medium mb-3 text-gray-800">
                              请求日志
                              <span id="logsStatus" class="text-sm font-normal text-[#B47464] loading-dots">正在获取日志</span>
                          </h2>
                          <div id="logsContainer" class="bg-gray-50 p-4 rounded-xl">
                              <pre id="logs" class="overflow-x-auto text-gray-700"></pre>
                          </div>
                      </div>
                  </div>
              </div>
  
              <!-- 右侧面板:响应结果 -->
              <div id="result" class="hidden lg:block bg-white rounded-2xl shadow-lg p-8 h-full sticky top-4">
                  <h2 class="text-xl font-medium mb-3 text-gray-800">响应结果</h2>
                  <pre id="response" class="bg-gray-50 p-4 rounded-xl overflow-x-auto text-gray-700 h-[calc(100vh-12rem)] overflow-y-auto"></pre>
              </div>
          </div>
      </div>
  
      <script>
          const form = document.getElementById('apiForm');
          const result = document.getElementById('result');
          const statusEl = document.getElementById('status');
          const logsEl = document.getElementById('logs');
          const logsStatus = document.getElementById('logsStatus');
          const responseEl = document.getElementById('response');
  
          form.addEventListener('submit', async (e) => {
              e.preventDefault();
              
              result.classList.remove('hidden');
              logsStatus.style.display = 'inline';
              logsEl.textContent = '';
              responseEl.textContent = '';
  
              const url = document.getElementById('url').value;
              const key = document.getElementById('key').value;
              const model = document.getElementById('model').value;
  
              try {
                  const response = await fetch('/v1/serve', {
                      method: 'POST',
                      headers: { 'Content-Type': 'application/json' },
                      body: JSON.stringify({ url, key, model })
                  });
  
                  if (!response.body) {
                      throw new Error('ReadableStream 不可用');
                  }
  
                  const reader = response.body.getReader();
                  const decoder = new TextDecoder('utf-8');
                  let done = false;
                  let buffer = '';
  
                  while (!done) {
                      const { value, done: doneReading } = await reader.read();
                      done = doneReading;
                      if (value) {
                          buffer += decoder.decode(value, { stream: true });
                          let lines = buffer.split('\\n');
                          buffer = lines.pop();
                          lines.forEach(line => {
                              if (line.trim() !== '') {
                                  try {
                                      const data = JSON.parse(line);
                                      if (data.type === 'status') {
                                          statusEl.textContent = data.message;
                                      } else if (data.type === 'log') {
                                          logsEl.textContent += data.message + '\\n';
                                          logsEl.scrollTop = logsEl.scrollHeight;
                                      } else if (data.type === 'response') {
                                          responseEl.textContent = JSON.stringify(data.message, null, 2);
                                          logsStatus.style.display = 'none';
                                      }
                                  } catch (parseError) {
                                      console.error('JSON 解析错误:', parseError, '内容:', line);
                                      statusEl.textContent = '错误: JSON 解析失败';
                                      logsStatus.style.display = 'none';
                                  }
                              }
                          });
                      }
                  }
  
                  if (buffer.trim() !== '') {
                      try {
                          const data = JSON.parse(buffer);
                          if (data.type === 'status') {
                              statusEl.textContent = data.message;
                          } else if (data.type === 'log') {
                              logsEl.textContent += data.message + '\\n';
                              logsEl.scrollTop = logsEl.scrollHeight;
                          } else if (data.type === 'response') {
                              responseEl.textContent = JSON.stringify(data.message, null, 2);
                              logsStatus.style.display = 'none';
                          }
                      } catch (parseError) {
                          console.error('JSON 解析错误:', parseError, '内容:', buffer);
                          statusEl.textContent = '错误: JSON 解析失败';
                          logsStatus.style.display = 'none';
                      }
                  }
  
              } catch (error) {
                  statusEl.textContent = '错误: ' + error.message;
                  logsStatus.style.display = 'none';
              }
          });
      </script>
  </body>
  </html>
  `;
  
  addEventListener('fetch', event => {
      event.respondWith(handleRequest(event.request))
  })
  
  // 存储请求信息到 D1
  async function storeRequest(env, traceId, request) {
      console.log('Storing request:', { traceId, ip: request.headers.get('cf-connecting-ip') });
      try {
          await env.DB.prepare(`
              INSERT INTO requests (trace_id, ip, asn, as_org, city, country, colo, timestamp)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?)
          `).bind(
              traceId,
              request.headers.get('cf-connecting-ip'),
              request.cf.asn,
              request.cf.asOrganization,
              request.cf.city,
              request.cf.country,
              request.cf.colo,
              Date.now()
          ).run();
          console.log('Request stored successfully');
      } catch (error) {
          console.error('Error storing request:', error);
          throw error;  // 重新抛出错误以便追踪
      }
  }
  
  // 获取特定 traceId 的所有请求
  async function getRequests(env, traceId) {
      console.log('Getting requests for traceId:', traceId);
      try {
          // 添加时间窗口条件,获取最近10秒内的请求
          const result = await env.DB.prepare(`
              SELECT DISTINCT ip, asn, as_org, city, country, colo
              FROM requests
              WHERE trace_id = ?
              AND timestamp >= ?
              ORDER BY timestamp ASC
          `).bind(
              traceId,
              Date.now() - 10000  // 10秒时间窗口
          ).all();
          
          // 添加调试日志
          console.log('SQL result:', result);
          console.log('Found records:', result.results?.length || 0);
          
          if (!result.results || result.results.length === 0) {
              console.log('No records found for traceId:', traceId);
              return [];
          }
          
          return result.results;
      } catch (error) {
          console.error('Error getting requests:', error);
          return [];
      }
  }
  
  async function handleFakeImage(request, env) {
      console.log('Handling fake image request');
      const url = new URL(request.url);
      const traceId = url.searchParams.get('traceId');
  
      if (!traceId) {
          return new Response('Missing traceId parameter', { status: 400 });
      }
  
      // 存储请求信息
      await storeRequest(env, traceId, request);
  
      // 返回随机图片
      try {
          const imageResponse = await fetch('https://picsum.photos/200/300', {
              headers: { 'Cache-Control': 'no-cache' }
          });
  
          if (!imageResponse.ok) {
              throw new Error('Failed to fetch image');
          }
  
          const imageBuffer = await imageResponse.arrayBuffer();
          return new Response(imageBuffer, {
              headers: {
                  'Content-Type': imageResponse.headers.get('content-type') || 'image/jpeg',
                  'Cache-Control': 'no-store'
              }
          });
      } catch (e) {
          console.error('Error in handleFakeImage:', e);
          throw e;
      }
  }
  
  // 设置超时时间为5秒
  const TIMEOUT = 10000;
  
  // 超时处理函数
  function timeoutPromise(ms) {
      return new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), ms));
  }
  
  async function handleOpenAIRequest(request, env) {
      console.log('Handling OpenAI request');
      try {
          const { url, key, model = 'gpt-4o' } = await request.json();
  
          if (!url || !key) {
              throw new Error('Missing url or key parameter');
          }
  
          const traceId = Date.now().toString();
          const encoder = new TextEncoder();
  
          const stream = new ReadableStream({
              async start(controller) {
                  try {
                      function sendMessage(type, message) {
                          const msg = JSON.stringify({ type, message }) + '\n';
                          controller.enqueue(encoder.encode(msg));
                          console.log('Sent message:', msg);
                      }
  
                      // 发送初始状态
                      sendMessage('status', '请求已发送,TraceId: ' + traceId);
                      sendMessage('log', '开始请求 - 模型: ' + model);
  
                      // 构造图片 URL
                      const imageUrl = `${CONFIG.WORKER_DOMAIN}/static/img?traceId=${traceId}`;
                      
                      // 使用 Promise.race 实现超时控制
                      const fetchPromise = fetch(url, {
                          method: 'POST',
                          headers: {
                              'Accept': 'application/json',
                              'Content-Type': 'application/json',
                              'Authorization': `Bearer ${key}`
                          },
                          body: JSON.stringify({
                              model,
                              messages: [{
                                  role: "user",
                                  content: [
                                      { type: "image_url", image_url: { url: imageUrl } },
                                      { type: "text", text: "What is this?" }
                                  ]
                              }],
                              max_tokens: 30,
                              stream: false
                          })
                      });
  
                      // 设置超时机制
                      const response = await Promise.race([fetchPromise, timeoutPromise(TIMEOUT)]);
  
                      const responseText = await response.text();
                      let responseData;
                      try {
                          responseData = JSON.parse(responseText);
                          sendMessage('log', '请求成功');
                      } catch (e) {
                          sendMessage('error', 'Invalid JSON response: ' + responseText);
                          return;
                      }
  
                      // API 请求完成后,等待一小段时间让数据写入完成
                      await new Promise(resolve => setTimeout(resolve, 1000));
  
                      // 现在查询中转节点信息
                      sendMessage('log', '正在检测中转节点...');
                      const requests = await getRequests(env, traceId);
                      console.log('Found requests:', requests);
  
                      if (requests && requests.length > 0) {
                          for (const req of requests) {
                              sendMessage('log', 
                                  `检测到中转节点: ${req.as_org || 'Unknown'} ` +
                                  `(${req.ip}) - 位置: ${req.city || 'Unknown'}, ` +
                                  `${req.country} [DC: ${req.colo}]`
                              );
                              // 添加小延迟确保消息正确发送
                              await new Promise(resolve => setTimeout(resolve, 100));
                          }
                      } else {
                          sendMessage('log', '未检测到中转节点');
                      }
  
                      // 最后发送 API 响应结果
                      sendMessage('response', responseData);
  
                  } catch (error) {
                      console.error('Error in stream:', error);
                      sendMessage('error', error.message);
                  } finally {
                      controller.close();
                  }
              }
          });
  
          return new Response(stream, {
              headers: { 
                  'Content-Type': 'text/plain',
                  'Cache-Control': 'no-cache',
                  'Connection': 'keep-alive'
              }
          });
      } catch (error) {
          console.error('Error in handleOpenAIRequest:', error);
          return new Response(JSON.stringify({ error: error.message }), {
              status: 500,
              headers: { 'Content-Type': 'application/json' }
          });
      }
  }
  async function handleRequest(request, env) {
      console.log('Request received:', request.url);
      const url = new URL(request.url);
      const pathname = url.pathname;
  
      try {
          if (pathname === '/v1/serve' && request.method === 'POST') {
              return await handleOpenAIRequest(request, env);
          } else if (pathname === '/static/img' && request.method === 'GET') {
              return await handleFakeImage(request, env);
          } else if (pathname === '/' || pathname === '/index.html') {
              return new Response(HTML_CONTENT, {
                  headers: { 'Content-Type': 'text/html;charset=UTF-8' }
              });
          }
          
          return new Response('Not Found', { status: 404 });
      } catch (error) {
          console.error('Error in handleRequest:', error);
          throw error;
      }
  }
  
  export default {
      async fetch(request, env, ctx) {
          try {
              return await handleRequest(request, env);
          } catch (error) {
              console.error('Fatal error:', error);
              return new Response(JSON.stringify({
                  error: 'Internal Server Error',
                  message: error.message
              }), {
                  status: 500,
                  headers: { 'Content-Type': 'application/json' }
              });
          }
      }
  };

(狗头自保:完全不会存储你的信息)

32 个赞

支持一下,小测了一下,转了三次。手中的key瞬间不香了

不重要,价格合适就行
我用着转了6手的公益api
还有3毛1刀的超低价api,转了2手
还有各种查不到转的 逆向api

3 个赞

666,但是公益的话就无所谓了。
我的3.3一刀,哭死
快给我推荐一下公益和你的低价key,建议私聊

哇,感谢!

站在巨人的肩膀上

18手奥拓,能动就行 :rofl:

1 个赞

强啊! :joy:


1 个赞

富可敌国!敢这样做的中转站可不多啊
不愧是富可敌国

主打就是一个源头,然后拉友商垫背

佬,可以看看Claude吗

很实用的工具,谢谢分享 :+1:


还有一个就是我拿我自己的openai官转测试,显示是azure应该是openai用的azure的服务器

claude貌似不行

1 个赞

太帅了
我也不太清楚claude为什么不太行,这个worker好像只支持openai格式的请求

太帅了佬

还是论坛大佬多,我只是板砖,学习一下 :grinning:

佬网站挂了吗


我这里可以啊?