Google学生号的申请

Google 学生号的申请,备份一下,省的忘记了: 首先前提是要有一个纯净的美国IP,可打开下面网址先看看纯净不纯净 ping0.cc 然后打开网页看看有没有权限 https://gemini.google.com/students 然后不要乱点,一定要右键复制链接 但是奇怪的是,我这边直接点了就过了,也是奇葩 如果能Copy链接,就拷贝了 然后到 https://batch.1key.me 打开验证通过 然后刷新页面,绑卡,就OK了。 然后啊,需要打开 https://aistudio.google.com/?authuser=3 拿到缺省项目的key,然后 export GEMINI_API_KEY="你的API_KEY" 尝试gemini登录一下,问问它是什么模型 最后别忘记在主号里面,管理会员资格,管理会员方案,打开共享 就可以放心的登录antigravity了

2026年01月13日 · 1 分钟 · 21 字 · 八戒

claude code的Ralph Wiggum编程模式和技巧

拉尔夫·维古姆(Ralph Wiggum):没完没了的 Claude 代码循环 这其实是个梗: Ralph Wiggum 是动画片《辛普森一家》中那个总是搞不清状况、智商不太在线的孩子。他最著名的梗图是 “I’m helping”(我在帮忙)。 我们在calude-code源代码仓库中可以看到这个插件 https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum 核心概念: # You run ONCE: /ralph-loop "Your task description" --completion-promise "DONE" # Then Claude Code automatically: # 1. Works on the task # 2. Tries to exit # 3. Stop hook blocks exit # 4. Stop hook feeds the SAME prompt back # 5. Repeat until completion 翻译一下: # 执行命令 /ralph-loop "Your task description" --completion-promise "DONE" # 接下来 Claude Code 将自动执行以下操作: # 1. 处理/执行该任务 # 2. 尝试退出程序 # 3. “停止挂钩(Stop hook)”拦截并阻止退出 # 4. “停止挂钩”将完全相同的提示词(prompt)重新传回给系统 # 5. 重复上述循环,直到任务完成(即出现 "DONE") 说白了就是Claude执行了一轮后要退出,然后拉尔夫就蹦出来阻止,没完;再跑一轮,又想退出,又被阻止,如此往复。 ...

2026年01月12日 · 2 分钟 · 402 字 · 八戒

claude code的session历史回顾和继续工具

现在基本Claude code 和 Codex 和 Gemini 混用 运维本身的项目也不会怎么大 用Claude的时候,开了很多项目去学习、研究;那历史和切回来继续研究就变得很重要,每次如果都从头开始分析,那崩溃了 手搓了一个工具claude-history,可以观看历史,并且生成命令,可以在以前的session基础上继续研究 http://github.com/zhangrr/claude-history 纯用Go,前后端一起,监听端口是8888 ✨ 特性 单文件实现:所有功能集成在 main.go 一个文件中(约 900 行) 零依赖部署:编译后的二进制文件包含前端资源,无需额外文件 高性能:使用 Go 标准库,性能优异 跨平台:支持 Linux、macOS、Windows 实时流式传输:通过 SSE (Server-Sent Events) 实现实时更新 文件监控:使用 fsnotify 监控 Claude 会话文件变化

2026年01月12日 · 1 分钟 · 35 字 · 八戒

如何用codex、claude code混合口嗨编程

codex和claude code两者各有所长,尝试在一个音乐项目里把两者结合起来,试试看 首先 clone 项目 git clone https://github.com/truelife0958/music888 然后命令它: 请你把这个项目的api音乐源都提取出来然后制作一个api接口文档 然后得出一个结果 然后新建一个目录,把这个文档单独拷贝进去,新启动一个codex 请你看一下这个api接口文档并测试一下哪些是有效的,帮我制作一个音乐播放web,需要有喜欢,最近播放,以及用户创立。 让它运行,打开页面看看 然后claude装好skills,Apple-Hig-Designer,地址是:https://github.com/axiaoge2/Apple-Hig-Designer 分析当前项目,Using apple-hig-desiner skills 重新设计前端页面 代开claude 回答几个问题 确定方案后开始改造 牛B吹得挺大: 打开一看,250啊 啥狗屁玩意,继续修去 <link rel="stylesheet" href="/.claude/skills/Apple-Hig-Designer/resources/design-tokens.css" /> 无法访问 刷新,擦的,图标都是错的 顶栏的图标都是错的 修好后再看 确实是苹果风格,点开设置,又拉跨了 继续命令它改: 设置,齿轮按钮打开后,配置窗口跑到最左面了,没有在右边 擦的,什么玩意,放中央,简直了 弹窗放到按钮附近 最终还可以,但是。 感觉这个skills还没有codex原生生成的好看。 编程基本变成了口嗨的行为了,真的是脑洞大开。

2026年01月09日 · 1 分钟 · 41 字 · 八戒

Caddy配合zot实现ip白名单登录docker registry

公司有两个docker的registry,一个是在公司内网,一个是在hk的公网 那jenkins打镜像的时候,FROM底包是在hk,打出镜像后,又要推到公司的内网,这样jenkins的pipeline看起来就很痛苦 打包登录公网一次,打包的地址是内网的harbor,推包再登录内网一次,脚本如下: docker.withRegistry("https://hk.rendoumi.com", "hk-user") { image = docker.build("harbor.rendoumi.dev/${IMAGE_NAME}:${TAG}", "${PROJECT_PATH}") } docker.withRegistry("https://harbor.rendoumi.dev", "harbor-user") { if (image) { image.push() } } 流水线不多的话这么还行得通,流水线很多,就得改个一溜够 那就想如果登录hk的服务器使用白名单机制,公司的jenkins worknodes服务器去访问,就不用登录,直接访问,就好了 那公司私网的registry是用的harbor搭的,公网的registry用的caddy+zot搭建 问了下 Gemini, 结果非常的荒谬,居然出现幻觉,呓推出了zot根本不存在的ip白名单限制规则,简直了!!! 后来跑到 zot 的官网看了半天文档,发现不行,根本没有这种配置 那就只能从Caddy入手了,从公司jenkins worknode出来的ip先到Caddy 然后Caddy模拟登录信息,给后端的zot发请求,就可以通过了 做法如下: 首先把 hk registry 的用户名和密码生成base64字串,得到XXXXX的BASE64字符串 echo -n "user1:pass1" | base64 然后修改Caddyfile,原有的配置 hk.rendoumi.com { reverse_proxy 127.0.0.1:5000 { header_up X-Real-IP {remote_host} transport http { dial_timeout 15s response_header_timeout 300s } } } 改成如下: hk.rendoumi.com { @trusted { remote_ip 111.222.333.444 } handle @trusted { reverse_proxy 127.0.0.1:5000 { header_up Authorization "Basic XXXXXXXXXXXXXXXX" header_up X-Real-IP {remote_host} transport http { dial_timeout 15s response_header_timeout 300s } } } handle { reverse_proxy 127.0.0.1:5000 { header_up X-Real-IP {remote_host} transport http { dial_timeout 15s response_header_timeout 300s } } } } 解释一下,上面如果是 111.222.333.444 的ip来访问,那就模拟登录的 Head 头信息,如果不是,就直接放行,由后端的zot进行验证 ...

2026年01月09日 · 1 分钟 · 145 字 · 八戒

薅羊毛之扣子编程

前几天佬友说扣子编程出了一个免费版,所以也想薅一把 事先声明:扣子编程是国内的,所以无法翻墙,本质也是一个容器。能做一个代理隐藏IP来访问国内的网站。 所以,说老实话,自己很少有这个场景来使用 废话不多说,直接开始: 扣子编程的网址:https://code.coze.cn 本质是火山引擎旗下的东西,也是一个容器,而且吧,比较有意思,跟 claude 和 codex 差不多 基本是:讲述式编程方式,一天智能建3个项目,一个项目是1cpu,2G内存 打开网站,新建项目,然后点击网页应用,下面的文本输入框就是编程的地方了: 我们先让它自己生成一个应用,再在生成的基础上进行修改,直接硬来会有无穷的麻烦,它有自己固定的架构 建立一个极简的nodejs程序,不依赖任何前端和后端框架,不要用到next框架。前端页面是index.html,里面是Hello world,主程序是index.js,用来显示index.html的内容,绝对不要用到任何js框架。 然后系统就叽里呱啦、哔哔赖赖开始自己搞了,它缺省创立的项目无论你再怎么强调,都是基于next.js的,都会拉出一坨next的屎,所以会神经兮兮的思考来思考去,产生一堆废物,我们可以不用理他 然后显示正常 从上图我们得到几个关键信息: 端口是5000 文件夹可以看到文件,里面有一堆缺省的配置,就算强调,依然有next拉的屎 部署按钮,我们部署测试一下 按部署按钮,会得到一个域名 然后部署成功,点击箭头 打开后,就是我们想要的 我们记下来这个域名,大善人啊,免费域名和免费证书 然后回到文件夹,来修改三个文件,index.html 和 index.js 和 package.json 先来改 package.json, 在dependencies中,加两句,注意这两句的上一句需要加个逗号,axios最后没有逗号 "ws": "^8.14.2", "axios": "^1.12.2" 注意:写到最后,发现这里可以再部署一下再改index.jsp和index.html比较好,就不用经历我下面的回档了!!!!! 我下面是没有再部署,直接硬改,点开文件夹图标,选中index.js,先别贴,需要修改混淆的: const http = require('http'); const https = require('https'); const fs = require('fs'); const dns = require('dns'); const axios = require('axios'); const net = require('net'); const path = require('path'); const crypto = require('crypto'); const { Buffer } = require('buffer'); const { WebSocket, createWebSocketStream } = require('ws'); // 生成 UUID v4 function generateUUID() { return crypto.randomUUID(); } const UUID = process.env.UUID || generateUUID(); const DOMAIN = process.env.DOMAIN || 'xxxxxxxx.coze.site'; // 填写项目域名 const WSPATH = process.env.WSPATH || UUID.slice(0, 8); // 节点路径,默认获取uuid前8位 const SUB_PATH = process.env.SUB_PATH || 'sub'; // 节点的订阅路径 const NAME = process.env.NAME || ''; // 节点名称 const PORT = process.env.PORT || 5000; // http和ws服务端口 const TLS_KEY_PATH = process.env.TLS_KEY_PATH || ''; const TLS_CERT_PATH = process.env.TLS_CERT_PATH || ''; let ISP = ''; const GetISP = async () => { try { const res = await axios.get('https://api.ip.sb/geoip'); const data = res.data; ISP = `${data.country_code}-${data.isp}`.replace(/ /g, '_'); } catch (e) { ISP = 'Unknown'; } } GetISP(); function normalizeSecret(value) { if (!value) return ''; return value.includes('\\n') ? value.replace(/\\n/g, '\n') : value; } function readOptionalFile(filePath) { if (!filePath) return ''; const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath); try { return fs.readFileSync(resolvedPath, 'utf8'); } catch (error) { console.warn(`Failed to read TLS file ${resolvedPath}: ${error.message}`); return ''; } } function getTLSOptions() { let key = normalizeSecret(process.env.TLS_KEY); let cert = normalizeSecret(process.env.TLS_CERT); if (!key) { key = readOptionalFile(TLS_KEY_PATH); } if (!cert) { cert = readOptionalFile(TLS_CERT_PATH); } if (key && cert) { return { key, cert }; } if (key || cert) { console.warn('Both TLS key and certificate are required; falling back to HTTP.'); } return null; } const requestHandler = (req, res) => { if (req.url === '/') { const filePath = path.join(__dirname, 'index.html'); fs.readFile(filePath, 'utf8', (err, content) => { if (err) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('Hello world!'); return; } res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(content); }); return; } else if (req.url === `/${SUB_PATH}`) { const namePart = NAME ? `${NAME}-${ISP}` : ISP; const vlessURL = `vless://${UUID}@${DOMAIN}:443?encryption=none&security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`; const trojanURL = `trojan://${UUID}@${DOMAIN}:443?security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`; const subscription = vlessURL + '\n' + trojanURL; const base64Content = Buffer.from(subscription).toString('base64'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(base64Content + '\n'); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found\n'); } }; const tlsOptions = getTLSOptions(); const httpServer = tlsOptions ? https.createServer(tlsOptions, requestHandler) : http.createServer(requestHandler); const wss = new WebSocket.Server({ server: httpServer }); const uuid = UUID.replace(/-/g, ""); const DNS_SERVERS = ['114.114.114.114', '223.5.5.5']; // Custom DNS async function resolveHost(host) { const target = String(host || '').trim(); if (!target) throw new Error('Host is empty'); if (net.isIP(target)) return target; try { const addresses = await dns.promises.lookup(target, { all: true }); const preferred = addresses.find(a => a.family === 4) || addresses[0]; if (preferred?.address) return preferred.address; } catch (_) { // fall through to custom DNS servers } const resolver = new dns.promises.Resolver(); for (const server of DNS_SERVERS) { try { resolver.setServers([server]); const v4 = await resolver.resolve4(target); if (v4?.[0]) return v4[0]; } catch (_) {} try { resolver.setServers([server]); const v6 = await resolver.resolve6(target); if (v6?.[0]) return v6[0]; } catch (_) {} } throw new Error(`Failed to resolve ${target}`); } // VLE-SS处理 function handleVlessConnection(ws, msg) { const [VERSION] = msg; const id = msg.slice(1, 17); if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return false; let i = msg.slice(17, 18).readUInt8() + 19; const port = msg.slice(i, i += 2).readUInt16BE(0); const ATYP = msg.slice(i, i += 1).readUInt8(); const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') : (ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) : (ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : '')); ws.send(new Uint8Array([VERSION, 0])); const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } // Tro-jan处理 function handleTrojanConnection(ws, msg) { try { if (msg.length < 58) return false; const receivedPasswordHash = msg.slice(0, 56).toString(); const possiblePasswords = [ UUID, ]; let matchedPassword = null; for (const pwd of possiblePasswords) { const hash = crypto.createHash('sha224').update(pwd).digest('hex'); if (hash === receivedPasswordHash) { matchedPassword = pwd; break; } } if (!matchedPassword) return false; let offset = 56; if (msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const cmd = msg[offset]; if (cmd !== 0x01) return false; offset += 1; const atyp = msg[offset]; offset += 1; let host, port; if (atyp === 0x01) { host = msg.slice(offset, offset + 4).join('.'); offset += 4; } else if (atyp === 0x03) { const hostLen = msg[offset]; offset += 1; host = msg.slice(offset, offset + hostLen).toString(); offset += hostLen; } else if (atyp === 0x04) { host = msg.slice(offset, offset + 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []) .map(b => b.readUInt16BE(0).toString(16)).join(':'); offset += 16; } else { return false; } port = msg.readUInt16BE(offset); offset += 2; if (offset < msg.length && msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } catch (error) { return false; } } // Ws 连接处理 wss.on('connection', (ws, req) => { const url = req.url || ''; ws.once('message', msg => { if (msg.length > 17 && msg[0] === 0) { const id = msg.slice(1, 17); const isVless = id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16)); if (isVless) { if (!handleVlessConnection(ws, msg)) { ws.close(); } return; } } if (!handleTrojanConnection(ws, msg)) { ws.close(); } }).on('error', () => {}); }); httpServer.listen(PORT, () => { const scheme = tlsOptions ? 'HTTPS/WSS' : 'HTTP/WS'; console.log(`Server is running on ${scheme} port ${PORT}`); }); 原始文件长这样: ...

2026年01月06日 · 14 分钟 · 2955 字 · 八戒

huggingface.co薅羊毛记

应佬友的要求再出一篇文章,前面薅了wispbyte.com的羊毛,但是吧容器比较弱小,强一点的就是huggingface.co的容器: Free版本就是2vCPU和16GB RAM,就已经非常强了 那先介绍一下huggingface,其实这是一家非常强悍的公司,可以称作是“AI界的GitHub”,可以分享模型,数据集,然后演示应用。 我们用到的就是这个演示应用(Spaces) 首先注册好账号,然后右上角点击头像按钮,新建一个Space 然后填入必须项 Space name:就写myhome Short description:Save Our Home License:MIT SDK:选择Docker Docker template: 选择Blank Space hardware:选择Free 其它都不选,最后CreateSpace即可 然后就建好了,其实是给你开了一个git的repo 我们到右上角,选择Files选项 然后打开的文件页面,只有两个文件 老套路,建立一个Dockerfile文件,就是容器的打包文件 Dockerfile的内容,编辑好,然后提交,注意:Linux有严格的大小写要求,务必遵守 FROM nikolaik/python-nodejs:python3.14-nodejs22 WORKDIR /tmp COPY index.js /tmp/index.js COPY package.json /tmp/package.json COPY index.html /tmp/index.html CMD npm install && node index.js 然后我们把index.js准备好,需要修改几个地方 const http = require('http'); const https = require('https'); const fs = require('fs'); const axios = require('axios'); const net = require('net'); const path = require('path'); const crypto = require('crypto'); const { Buffer } = require('buffer'); const { WebSocket, createWebSocketStream } = require('ws'); // 生成 UUID v4 function generateUUID() { return crypto.randomUUID(); } const UUID = process.env.UUID || generateUUID(); const DOMAIN = process.env.DOMAIN || '1234.abc.com'; // 填写项目域名 const WSPATH = process.env.WSPATH || UUID.slice(0, 8); // 节点路径,默认获取uuid前8位 const SUB_PATH = process.env.SUB_PATH || 'sub'; // 获取节点的订阅路径 const NAME = process.env.NAME || ''; // 节点名称 const PORT = process.env.PORT || 7860; // http和ws服务端口 const TLS_KEY_PATH = process.env.TLS_KEY_PATH || 'tls.key'; const TLS_CERT_PATH = process.env.TLS_CERT_PATH || 'tls.crt'; let ISP = ''; const GetISP = async () => { try { const res = await axios.get('https://api.ip.sb/geoip'); const data = res.data; ISP = `${data.country_code}-${data.isp}`.replace(/ /g, '_'); } catch (e) { ISP = 'Unknown'; } } GetISP(); function normalizeSecret(value) { if (!value) return ''; return value.includes('\\n') ? value.replace(/\\n/g, '\n') : value; } function readOptionalFile(filePath) { if (!filePath) return ''; const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath); try { return fs.readFileSync(resolvedPath, 'utf8'); } catch (error) { console.warn(`Failed to read TLS file ${resolvedPath}: ${error.message}`); return ''; } } function getTLSOptions() { let key = normalizeSecret(process.env.TLS_KEY); let cert = normalizeSecret(process.env.TLS_CERT); if (!key) { key = readOptionalFile(TLS_KEY_PATH); } if (!cert) { cert = readOptionalFile(TLS_CERT_PATH); } if (key && cert) { return { key, cert }; } if (key || cert) { console.warn('Both TLS key and certificate are required; falling back to HTTP.'); } return null; } const requestHandler = (req, res) => { if (req.url === '/') { const filePath = path.join(__dirname, 'index.html'); fs.readFile(filePath, 'utf8', (err, content) => { if (err) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('Hello world!'); return; } res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(content); }); return; } else if (req.url === `/${SUB_PATH}`) { const namePart = NAME ? `${NAME}-${ISP}` : ISP; const vlessURL = `vless://${UUID}@${DOMAIN}?encryption=none&security=tls&sni=${DOMAIN}:443&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`; const trojanURL = `trojan://${UUID}@${DOMAIN}?security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}:443&path=%2F${WSPATH}#${namePart}`; const subscription = vlessURL + '\n' + trojanURL; const base64Content = Buffer.from(subscription).toString('base64'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(base64Content + '\n'); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found\n'); } }; const tlsOptions = getTLSOptions(); const httpServer = tlsOptions ? https.createServer(tlsOptions, requestHandler) : http.createServer(requestHandler); const wss = new WebSocket.Server({ server: httpServer }); const uuid = UUID.replace(/-/g, ""); const DNS_SERVERS = ['8.8.4.4', '1.1.1.1']; // Custom DNS function resolveHost(host) { return new Promise((resolve, reject) => { if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(host)) { resolve(host); return; } let attempts = 0; function tryNextDNS() { if (attempts >= DNS_SERVERS.length) { reject(new Error(`Failed to resolve ${host} with all DNS servers`)); return; } const dnsServer = DNS_SERVERS[attempts]; attempts++; const dnsQuery = `https://dns.google/resolve?name=${encodeURIComponent(host)}&type=A`; axios.get(dnsQuery, { timeout: 5000, headers: { 'Accept': 'application/dns-json' } }) .then(response => { const data = response.data; if (data.Status === 0 && data.Answer && data.Answer.length > 0) { const ip = data.Answer.find(record => record.type === 1); if (ip) { resolve(ip.data); return; } } tryNextDNS(); }) .catch(error => { tryNextDNS(); }); } tryNextDNS(); }); } // VLE-SS处理 function handleVlessConnection(ws, msg) { const [VERSION] = msg; const id = msg.slice(1, 17); if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return false; let i = msg.slice(17, 18).readUInt8() + 19; const port = msg.slice(i, i += 2).readUInt16BE(0); const ATYP = msg.slice(i, i += 1).readUInt8(); const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') : (ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) : (ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : '')); ws.send(new Uint8Array([VERSION, 0])); const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } // Tro-jan处理 function handleTrojanConnection(ws, msg) { try { if (msg.length < 58) return false; const receivedPasswordHash = msg.slice(0, 56).toString(); const possiblePasswords = [ UUID, ]; let matchedPassword = null; for (const pwd of possiblePasswords) { const hash = crypto.createHash('sha224').update(pwd).digest('hex'); if (hash === receivedPasswordHash) { matchedPassword = pwd; break; } } if (!matchedPassword) return false; let offset = 56; if (msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const cmd = msg[offset]; if (cmd !== 0x01) return false; offset += 1; const atyp = msg[offset]; offset += 1; let host, port; if (atyp === 0x01) { host = msg.slice(offset, offset + 4).join('.'); offset += 4; } else if (atyp === 0x03) { const hostLen = msg[offset]; offset += 1; host = msg.slice(offset, offset + hostLen).toString(); offset += hostLen; } else if (atyp === 0x04) { host = msg.slice(offset, offset + 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []) .map(b => b.readUInt16BE(0).toString(16)).join(':'); offset += 16; } else { return false; } port = msg.readUInt16BE(offset); offset += 2; if (offset < msg.length && msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } catch (error) { return false; } } // Ws 连接处理 wss.on('connection', (ws, req) => { const url = req.url || ''; ws.once('message', msg => { if (msg.length > 17 && msg[0] === 0) { const id = msg.slice(1, 17); const isVless = id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16)); if (isVless) { if (!handleVlessConnection(ws, msg)) { ws.close(); } return; } } if (!handleTrojanConnection(ws, msg)) { ws.close(); } }).on('error', () => {}); }); httpServer.listen(PORT, () => { const scheme = tlsOptions ? 'HTTPS/WSS' : 'HTTP/WS'; console.log(`Server is running on ${scheme} port ${PORT}`); }); 其中几个地方有修改,先看看huggingface给咱们的域名,右边,Settings右边的三个点按钮, ...

2026年01月06日 · 15 分钟 · 3066 字 · 八戒

wispbyte.com薅羊毛记

有了Codex的加持,也渐渐得疯狂起来了,本身运维就是全干,对系统的各个点把控就比较精准 这次准备薅羊毛的是 wispbyte.com , 是欧洲的一个厂家 它可以免费建立一个server,准确的来说是一个容器,并且暴露一个端口出来 那先Create Server,Server Name 和 Server Description就都写 god01 好了 Server Type当然选Free Plan的,运行环境就选NodeJS 选完就按最下面的创建即可。 建好了那就去到 server的manage管理界面,下面有个Startup: 分析那句命令,意思很简单,如果配了git,就去拉代码,如果有存在npm的package.json,就先npm install安装,最后,运行index.js 下面是Docker Image,本来以为是可以随便引用别处的镜像,结果是不能,只能选固定的,那就选不太激进的 nodejs_22 ,保存好 然后就什么都不用动了 然后去到Files选项,缺省路径是 /hoe/container/ 上面图是已经弄好的,如果是新服务器,是空无一物的 我们只需要准备5个文件和一个域名: tls.crt 证书文件,用let’s encrypt申请 tls.key 密钥文件,用let’s encrypt申请 index.html 用来装饰的环保单页面,如果不加,就会显示hello world,太假了,可以让 gemini 给你生成一个,代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Green Network - Protect Earth</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } :root { --primary: #2e7d32; --secondary: #4caf50; --accent: #8bc34a; --light: #e8f5e9; --dark: #1b5e20; --text: #333333; --white: #ffffff; } body { background-color: var(--light); color: var(--text); line-height: 1.7; /* Slightly increased line height for readability */ font-size: 16px; } header { background: rgba(46, 125, 50, 0.95); /* Slightly transparent primary color */ color: var(--white); padding: 1rem 0; position: sticky; top: 0; z-index: 100; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Enhanced shadow */ } .container { width: 90%; max-width: 1200px; margin: 0 auto; } .nav-container { display: flex; justify-content: space-between; align-items: center; } .logo { display: flex; align-items: center; gap: 10px; font-size: 1.8rem; font-weight: 700; } .logo i { font-size: 2rem; } nav ul { display: flex; list-style: none; } nav ul li { margin-left: 2rem; } nav ul li a { color: var(--white); text-decoration: none; font-weight: 500; transition: all 0.3s ease; padding: 0.5rem 0; position: relative; } nav ul li a:hover { color: var(--accent); } nav ul li a::after { content: ''; position: absolute; bottom: 0; left: 0; width: 0; height: 2px; background-color: var(--accent); transition: width 0.3s ease; } nav ul li a:hover::after { width: 100%; } .hero { background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4)), url('https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2073&q=80'); background-size: cover; background-position: center; height: 80vh; display: flex; align-items: center; text-align: center; color: var(--white); } .hero-content { max-width: 800px; margin: 0 auto; } .hero h1 { font-size: 3.5rem; margin-bottom: 1rem; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); } .hero p { font-size: 1.3rem; margin-bottom: 2rem; } .btn { display: inline-block; background-color: var(--accent); color: var(--white); padding: 0.9rem 2.2rem; /* Slightly larger padding */ border-radius: 8px; /* More modern rounded corners */ text-decoration: none; font-weight: 600; transition: all 0.3s ease; border: none; cursor: pointer; box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1); /* Slightly enhanced shadow */ } .btn:hover { background-color: var(--primary); transform: translateY(-5px); /* More pronounced lift effect */ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); /* Stronger hover shadow */ } section { padding: 5rem 0; } .section-title { text-align: center; margin-bottom: 3rem; } .section-title h2 { font-size: 2.5rem; color: var(--primary); margin-bottom: 1rem; position: relative; display: inline-block; } .section-title h2::after { content: ''; position: absolute; bottom: -10px; left: 50%; transform: translateX(-50%); width: 90px; /* Slightly wider underline */ height: 5px; /* Thicker underline */ background-color: var(--accent); border-radius: 3px; } .section-title p { color: var(--text); max-width: 700px; margin: 0 auto; font-size: 1.1rem; } .about-content { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: center; } .about-img { border-radius: 12px; /* More rounded */ overflow: hidden; box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15); /* Enhanced shadow */ } .about-img img { width: 100%; height: auto; display: block; transition: transform 0.5s ease; } .about-img:hover img { transform: scale(1.08); /* More pronounced zoom */ } .about-text h3 { font-size: 2rem; margin-bottom: 1.5rem; color: var(--dark); } .about-text p { margin-bottom: 1.5rem; font-size: 1.05rem; } .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2rem; margin-top: 4rem; /* More space */ } .stat-item { text-align: center; padding: 2.5rem 1.5rem; /* More padding */ background-color: var(--white); border-radius: 12px; /* More rounded */ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); /* Enhanced shadow */ transition: transform 0.3s ease, box-shadow 0.3s ease; } .stat-item:hover { transform: translateY(-12px); /* More pronounced lift */ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Stronger hover shadow */ } .stat-item h3 { font-size: 3rem; /* Larger numbers */ color: var(--dark); margin-bottom: 0.8rem; } .stat-item p { color: var(--text); font-size: 1.1rem; } .initiatives { background-color: var(--white); } .initiatives-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2.5rem; } .initiative-card { background-color: var(--light); border-radius: 12px; /* More rounded */ overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); /* Enhanced shadow */ transition: transform 0.3s ease, box-shadow 0.3s ease; } .initiative-card:hover { transform: translateY(-12px); /* More pronounced lift */ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Stronger hover shadow */ } .initiative-img { height: 220px; /* Slightly taller images */ overflow: hidden; } .initiative-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .initiative-card:hover .initiative-img img { transform: scale(1.15); /* More pronounced zoom */ } .initiative-content { padding: 2rem; } .initiative-content h3 { font-size: 1.6rem; margin-bottom: 1rem; color: var(--dark); } .initiative-content p { margin-bottom: 1.8rem; font-size: 1.05rem; } .cta { background: linear-gradient(to right, var(--primary), var(--secondary)); color: var(--white); text-align: center; padding: 6rem 0; /* More padding */ } .cta h2 { font-size: 3rem; /* Larger heading */ margin-bottom: 1.8rem; } .cta p { font-size: 1.3rem; margin-bottom: 2.5rem; max-width: 700px; margin-left: auto; margin-right: auto; } .cta .btn { background-color: var(--white); color: var(--primary); } .cta .btn:hover { background-color: var(--accent); color: var(--white); } footer { background-color: var(--dark); color: var(--white); padding: 4rem 0 2rem; /* More padding */ } .footer-content { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2.5rem; /* More gap */ margin-bottom: 2.5rem; } .footer-column h3 { font-size: 1.4rem; margin-bottom: 1.8rem; position: relative; padding-bottom: 12px; } .footer-column h3::after { content: ''; position: absolute; bottom: 0; left: 0; width: 50px; /* Wider underline */ height: 3px; /* Thicker underline */ background-color: var(--accent); } .footer-column p { margin-bottom: 1.2rem; } .footer-links { list-style: none; } .footer-links li { margin-bottom: 1rem; } .footer-links li a { color: var(--light); text-decoration: none; transition: all 0.3s ease; } .footer-links li a:hover { color: var(--accent); padding-left: 8px; /* More pronounced slide effect */ } .social-links { display: flex; gap: 1.2rem; margin-top: 1.5rem; } .social-links a { display: inline-flex; align-items: center; justify-content: center; width: 50px; /* Larger social buttons */ height: 50px; background-color: rgba(255, 255, 255, 0.15); /* Slightly more visible background */ border-radius: 8px; /* Square with rounded corners */ color: var(--white); text-decoration: none; font-weight: 600; transition: all 0.3s ease; font-size: 0.9rem; } .social-links a:hover { background-color: var(--accent); transform: translateY(-7px); /* More pronounced lift */ } .copyright { text-align: center; padding-top: 2.5rem; border-top: 1px solid rgba(255, 255, 255, 0.15); } .mobile-menu { display: none; font-size: 1.5rem; cursor: pointer; padding: 0.5rem 1rem; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 5px; transition: all 0.3s ease; } .mobile-menu:hover { background-color: rgba(255, 255, 255, 0.1); } @media (max-width: 992px) { .about-content { grid-template-columns: 1fr; } .initiatives-grid { grid-template-columns: repeat(2, 1fr); } .footer-content { grid-template-columns: repeat(2, 1fr); } .stats { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { .hero h1 { font-size: 2.8rem; /* Adjusted for better readability on smaller screens */ } .hero p { font-size: 1.2rem; /* Adjusted for better readability on smaller screens */ } nav ul { display: none; position: absolute; top: 100%; left: 0; width: 100%; background-color: var(--primary); flex-direction: column; padding: 1rem 0; text-align: center; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); } nav ul.active { display: flex; } nav ul li { margin: 0; padding: 0.8rem 0; } .mobile-menu { display: block; font-size: 1.2rem; /* Slightly smaller for mobile */ padding: 0.4rem 0.8rem; } .initiatives-grid { grid-template-columns: 1fr; } .footer-content { grid-template-columns: 1fr; } .stats { grid-template-columns: 1fr; } .social-links a { width: 45px; /* Slightly smaller social buttons */ height: 45px; font-size: 0.85rem; } } /* Animation for stats counter */ .counter { transition: all 0.5s ease; } </style> </head> <body> <!-- Header --> <header> <div class="container nav-container"> <div class="logo"> <span>Green Network</span> </div> <nav> <div class="mobile-menu"> Menu </div> <ul> <li><a href="#home">Home</a></li> <li><a href="#about">About</a></li> <li><a href="#initiatives">Initiatives</a></li> <li><a href="#get-involved">Get Involved</a></li> <li><a href="#contact">Contact</a></li> </ul> </nav> </div> </header> <!-- Hero Section --> <section class="hero" id="home"> <div class="container hero-content"> <h1>Protect Our Planet, Preserve Our Future</h1> <p>Join the global movement to create a sustainable world through conservation, education, and community action.</p> <a href="#get-involved" class="btn">Join Our Network</a> </div> </section> <!-- About Section --> <section id="about"> <div class="container"> <div class="section-title"> <h2>About Green Network</h2> <p>We are a global community dedicated to environmental protection and sustainable development.</p> </div> <div class="about-content"> <div class="about-img"> <img src="https://images.unsplash.com/photo-1507591064344-4c6ce005b128?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80" alt="Team planting trees"> </div> <div class="about-text"> <h3>Our Mission</h3> <p>Green Network is committed to creating a sustainable future by protecting natural ecosystems, promoting renewable energy, and empowering communities to take environmental action.</p> <p>Founded in 2010, we've grown from a small grassroots organization to an international network with over 50,000 members across 120 countries.</p> <p>Our approach combines scientific research, community engagement, and policy advocacy to address the most pressing environmental challenges of our time.</p> <a href="#" class="btn">Learn More</a> </div> </div> <div class="stats"> <div class="stat-item"> <h3 class="counter" data-target="2500000">0</h3> <p>Trees Planted</p> </div> <div class="stat-item"> <h3 class="counter" data-target="50000">0</h3> <p>Active Members</p> </div> <div class="stat-item"> <h3 class="counter" data-target="120">0</h3> <p>Countries Reached</p> </div> <div class="stat-item"> <h3 class="counter" data-target="1500">0</h3> <p>Cleanup Projects</p> </div> </div> </div> </section> <!-- Initiatives Section --> <section class="initiatives" id="initiatives"> <div class="container"> <div class="section-title"> <h2>Our Initiatives</h2> <p>Discover the key programs and projects we're implementing to protect our planet.</p> </div> <div class="initiatives-grid"> <div class="initiative-card"> <div class="initiative-img"> <img src="https://dialogue.earth/content/uploads/2021/04/lessons-from-the-rush-to-reforest-china-dialogue-2400x1599.jpg" alt="Reforestation"> </div> <div class="initiative-content"> <h3>Global Reforestation</h3> <p>Planting millions of trees worldwide to restore ecosystems, combat climate change, and protect biodiversity.</p> <a href="#" class="btn">Learn More</a> </div> </div> <div class="initiative-card"> <div class="initiative-img"> <img src="https://images.unsplash.com/photo-1621451537084-482c73073a0f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1974&q=80" alt="Ocean Conservation"> </div> <div class="initiative-content"> <h3>Ocean Conservation</h3> <p>Protecting marine ecosystems, reducing plastic pollution, and promoting sustainable fishing practices.</p> <a href="#" class="btn">Learn More</a> </div> </div> <div class="initiative-card"> <div class="initiative-img"> <img src="https://images.unsplash.com/photo-1508514177221-188b1cf16e9d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2072&q=80" alt="Renewable Energy"> </div> <div class="initiative-content"> <h3>Renewable Energy</h3> <p>Promoting solar, wind, and other clean energy sources to reduce dependence on fossil fuels.</p> <a href="#" class="btn">Learn More</a> </div> </div> </div> </div> </section> <!-- CTA Section --> <section class="cta" id="get-involved"> <div class="container"> <h2>Join Our Global Movement</h2> <p>Together, we can create a sustainable future for generations to come. Every action counts, no matter how small.</p> <a href="#" class="btn">Become a Member</a> </div> </section> <!-- Footer --> <footer id="contact"> <div class="container"> <div class="footer-content"> <div class="footer-column"> <h3>Green Network</h3> <p>We are dedicated to protecting our planet through collaborative action, education, and sustainable solutions.</p> <div class="social-links"> <a href="#">Facebook</a> <a href="#">Twitter</a> <a href="#">Instagram</a> <a href="#">LinkedIn</a> </div> </div> <div class="footer-column"> <h3>Quick Links</h3> <ul class="footer-links"> <li><a href="#home">Home</a></li> <li><a href="#about">About Us</a></li> <li><a href="#initiatives">Initiatives</a></li> <li><a href="#get-involved">Get Involved</a></li> <li><a href="#contact">Contact</a></li> </ul> </div> <div class="footer-column"> <h3>Our Programs</h3> <ul class="footer-links"> <li><a href="#">Reforestation</a></li> <li><a href="#">Ocean Cleanup</a></li> <li><a href="#">Wildlife Protection</a></li> <li><a href="#">Climate Education</a></li> <li><a href="#">Sustainable Agriculture</a></li> </ul> </div> <div class="footer-column"> <h3>Contact Us</h3> <p>Copper Creek Drive, New York</p> <p>+1 (312) 171-0771</p> <p>support@greennetwork.org</p> </div> </div> <div class="copyright"> <p>&copy; 2025 Green Network. All rights reserved.</p> </div> </div> </footer> <script> // Mobile Menu Toggle document.querySelector('.mobile-menu').addEventListener('click', function() { document.querySelector('nav ul').classList.toggle('active'); }); // Counter Animation function animateCounters() { const counters = document.querySelectorAll('.counter'); const speed = 200; // The lower the slower counters.forEach(counter => { const target = +counter.getAttribute('data-target'); const count = +counter.innerText; const inc = target / speed; if(count < target) { counter.innerText = Math.ceil(count + inc); setTimeout(animateCounters, 1); } else { counter.innerText = target; } }); } // Initialize counters when page loads window.addEventListener('load', animateCounters); // Smooth scrolling for navigation links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href'); if(targetId === '#') return; const targetElement = document.querySelector(targetId); if(targetElement) { window.scrollTo({ top: targetElement.offsetTop - 80, behavior: 'smooth' }); document.querySelector('nav ul').classList.remove('active'); } }); }); </script> </body> </html> package.json 文件,index.js 运行时需要依赖的安装包文件,安装了2个包,axios和ws ...

2026年01月01日 · 14 分钟 · 2949 字 · 八戒

Claude code的skills编写

最近天天都在学习claude code,Skills == 工具箱(技能库) 我们可以把skills看成是一个个的小程序,这些程序有入口,有参数,有限制。 那通过一篇md文件,来引导claude来智能的传参调用你的程序,这样技能箱就越来越丰富。 一、我们首先准备这个可执行 python 脚本 这个是通过CLIProxyAPI的反代,把antigravity的gemini3模型给抽出来,然后生图 看最下面,有一堆参数 #!/usr/bin/env python3 # ============================================================ # Configuration - Edit these values before use # 配置项 - 使用前请修改以下值 # ============================================================ API_BASE_URL = "http://127.0.0.1:8317" # API server address / API 服务器地址 API_KEY = "key" # Your API key / 你的 API 密钥 MODEL = "gemini-3-pro-image-preview" # Model name / 模型名称 # ============================================================ import argparse import base64 import json import os import sys from datetime import datetime from pathlib import Path from urllib import request from mimetypes import guess_type try: from google import genai from google.genai import types except ImportError: genai = None types = None VALID_ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"] VALID_RESOLUTIONS = ["1K", "2K", "4K"] def _resolve_output_dir(output_dir: str, mkdir: bool) -> str: if os.path.isdir(output_dir): return output_dir if mkdir: os.makedirs(output_dir, exist_ok=True) return output_dir print(f"Warning: Output directory not found: {output_dir}. Saving to current directory.") return "." def _build_filename(output_name: str | None, timestamp: str, index: int, default_ext: str) -> str: if output_name: root, ext = os.path.splitext(output_name) if not ext: ext = default_ext suffix = f"_{index}" if index > 0 else "" return f"{root}{suffix}{ext}" return f"generated_{timestamp}_{index}{default_ext}" def _load_image_inline(image_path: str) -> dict: mime_type, _ = guess_type(image_path) if mime_type is None: mime_type = "image/png" data = Path(image_path).read_bytes() return {"inlineData": {"mimeType": mime_type, "data": base64.b64encode(data).decode("ascii")}} def _post_json(url: str, body: dict) -> dict: data = json.dumps(body).encode("utf-8") req = request.Request( url, data=data, headers={"Content-Type": "application/json", "x-goog-api-key": API_KEY}, ) resp = request.urlopen(req).read() return json.loads(resp) def _save_images(parts, output_dir: str, select: str, output_name: str | None, mkdir: bool) -> list[str]: imgs = [p["inlineData"] for p in parts if "inlineData" in p] if not imgs: return [] if select == "first": imgs = imgs[:1] elif select == "last": imgs = imgs[-1:] elif select == "max_res": imgs = imgs[-1:] ts = datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = _resolve_output_dir(output_dir, mkdir) saved = [] for i, img in enumerate(imgs): data = base64.b64decode(img["data"]) ext = "jpg" if img.get("mimeType", "").endswith("jpeg") else "png" filename = _build_filename(output_name, ts, i, f".{ext}") path = Path(output_dir) / filename path.write_bytes(data) saved.append(str(path)) return saved def _sdk_generate(prompt: str, output_dir: str, aspect_ratio: str, resolution: str, input_image: str | None, output_name: str | None, mkdir: bool, select: str) -> list[str]: if genai is None or types is None: print("Error: google-genai is not installed.") return [] client_kwargs = {"api_key": API_KEY} if API_BASE_URL: client_kwargs["http_options"] = {"base_url": API_BASE_URL} client = genai.Client(**client_kwargs) if input_image: if not Path(input_image).exists(): print(f"Error: Input image not found: {input_image}") return [] with open(input_image, "rb") as f: image_bytes = f.read() mime_type, _ = guess_type(input_image) if mime_type is None: mime_type = "image/png" image_part = types.Part.from_bytes(data=image_bytes, mime_type=mime_type) contents = [prompt, image_part] else: contents = prompt try: response = client.models.generate_content( model=MODEL, contents=contents, config=types.GenerateContentConfig( image_config=types.ImageConfig( aspectRatio=aspect_ratio, imageSize=resolution, ) ), ) except Exception as exc: print(f"API Error: {exc}") return [] saved = [] ts = datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = _resolve_output_dir(output_dir, mkdir) image_parts = [] for part in response.parts: if part.text is not None: print(f"Response text: {part.text}") elif image := part.as_image(): image_parts.append(image) if not image_parts: return [] if select == "first": image_parts = image_parts[:1] elif select == "last": image_parts = image_parts[-1:] elif select == "max_res": image_parts = [max(image_parts, key=lambda x: len(x.image_bytes))] for i, image in enumerate(image_parts): filename = _build_filename(output_name, ts, i, ".png") path = Path(output_dir) / filename image.save(path) saved.append(str(path)) return saved def _http_generate(prompt: str, output_dir: str, aspect_ratio: str, resolution: str, input_image: str | None, select: str, output_name: str | None, mkdir: bool) -> list[str]: if input_image and not Path(input_image).exists(): print(f"Error: Input image not found: {input_image}") return [] url = f"{API_BASE_URL.rstrip('/')}/v1beta/models/{MODEL}:generateContent" parts = [{"text": prompt}] if input_image: parts.append(_load_image_inline(input_image)) body = { "contents": [{"parts": parts}], "generationConfig": { "responseModalities": ["TEXT", "IMAGE"], "imageConfig": {"aspectRatio": aspect_ratio, "imageSize": resolution}, }, } try: resp = _post_json(url, body) except Exception as exc: print(f"API Error: {exc}") return [] if "candidates" not in resp: print(f"Unexpected response: {resp}") return [] parts_out = resp["candidates"][0]["content"]["parts"] return _save_images(parts_out, output_dir, select, output_name, mkdir) def main(): parser = argparse.ArgumentParser(description="Generate images via SDK or HTTP") parser.add_argument("prompt", nargs="*", help="Image description prompt") parser.add_argument("--mode", default="sdk", choices=["sdk", "http"]) parser.add_argument("--aspect-ratio", "-a", default="16:9", choices=VALID_ASPECT_RATIOS) parser.add_argument("--resolution", "-r", default="4K", choices=VALID_RESOLUTIONS) parser.add_argument("--output-dir", "-o", default=".") parser.add_argument("--output-name", default=None) parser.add_argument("--mkdir", action="store_true") parser.add_argument("--input-image", "-i", default=None) parser.add_argument("--select", default="all", choices=["all", "first", "last", "max_res"]) args = parser.parse_args() if not args.prompt: parser.print_help() sys.exit(1) prompt = " ".join(args.prompt) if args.mode == "sdk": saved = _sdk_generate( prompt=prompt, output_dir=args.output_dir, aspect_ratio=args.aspect_ratio, resolution=args.resolution, input_image=args.input_image, output_name=args.output_name, mkdir=args.mkdir, select=args.select, ) else: saved = _http_generate( prompt=prompt, output_dir=args.output_dir, aspect_ratio=args.aspect_ratio, resolution=args.resolution, input_image=args.input_image, select=args.select, output_name=args.output_name, mkdir=args.mkdir, ) if saved: print(f"Generated {len(saved)} image(s):") for f in saved: print(f" - {Path(f).resolve()}") else: print("No images were generated.") sys.exit(1) if __name__ == "__main__": main() 基本用法: ...

2025年12月31日 · 5 分钟 · 961 字 · 八戒

整理下AI大模型厂商和平台,能长期稳定提供免费额度的API (非公益站) - 转自Linux.do

现在AI的使用场景越来越多,公益站有时也不稳定,给大家整理一些能提供相对长期稳定大模型api的厂商和平台,作为备用或测试。 这里主要收集文本大模型,图片视频生成相关的大模型没有专门做整理。 tldr 国内大模型平台太卷了,免费额度真的很多,如果没有特殊需求,国内的api就够用了。 主力模型推荐: 阿里iflow, 字节火山引擎, 阿里 modelscope 魔搭社区。 免费vibe coding推荐: 腾讯codebuddy, 快手codeflicker, 阿里通义灵码/qwen-code 非稳定渠道 一些平台会不定期推出吸引用户的免费活动,适合用来测试和临时应急。下面列出一些,过期了的就评论下提醒我删掉。 AI Ping 20251226 限时免费: glm-4.7, minimax-m2.1, deepseek-v3.2, douban-seeddream文生图 模型限制相关说明 rpm(Requests per minute): 每分钟请求次数 rpd(Requests per day): 每天请求次数 tpm(Tokens per minute): 每分钟输入输出的token数 tpd(Tokens per day): 每天输入输出的token数 Vibe Coding 免费代码工具 国内的 ai coding 太卷了,各家都提供了很大的免费额度 腾讯云代码助手 CodeBuddy, 独立IDE 目前(20251222)免费使用 glm-4.6, deepseek-v3.1-terminus, huyuan-2.0 20251223: 免费提供最新的 glm-4.7 快手 CodeFlicker , 独立IDE 目前(20251222)免费使用 kimi-k2-0905, kat-coder-pro 阿里 通义灵码 , 独立IDE 免费不限量使用 千问系列模型,但不可更换使用其他模型 阿里 qwen-code, cli命令行 free tier ...

2025年12月31日 · 3 分钟 · 523 字 · 八戒