2026-08-31 11:35:00
In case you weren't aware, there's quite a debate over httpx v1.0 release, especially maintainer of both openai-python and anthropic-sdk-python showed up and decided to switch to httpx2, a fork of httpx that maintains v0.28 styles
httpx is a Python library that can handle both sync/async http, replacing requests (sync-only) and aiohttp (async-opnly).
You can view original discussion starting here https://github.com/encode/httpx/discussions/3344
The maintainer closed discussion and issues
verify= and cert= → explicit ssl.SSLContext
for version < v1.0
# Boolean on/off — still fine, not deprecated
httpx.Client(verify=True) # default: verify using certifi's CA bundle
httpx.Client(verify=False) # danger: accept any certificate
# String path — NOW DEPRECATED, raises a warning
httpx.Client(verify="/path/to/custom-ca-bundle.pem")
# cert= for a client certificate (mutual TLS) — NOW DEPRECATED
httpx.Client(cert=("/path/to/client.pem", "/path/to/client.key"))
v1.0
import ssl, certifi, httpx
# Equivalent to default verify=True
ctx = ssl.create_default_context(cafile=certifi.where())
httpx.Client(verify=ctx)
# Custom CA bundle
ctx = ssl.create_default_context(cafile="/path/to/custom-ca-bundle.pem")
httpx.Client(verify=ctx)
# Client certificate (mutual TLS) is now loaded onto the context itself
ctx = ssl.create_default_context(cafile=certifi.where())
ctx.load_cert_chain("/path/to/client.pem", "/path/to/client.key")
httpx.Client(verify=ctx)
proxies= → proxy= / mounts=
Before:
# Old dict-based per-scheme mapping
httpx.Client(proxies={"http://": "http://localhost:8030", "https://": "http://localhost:8031"})
After:
# Single proxy for everything
httpx.Client(proxy="http://localhost:8030")
# Per-scheme routing via mounts (a dict of URL-pattern -> Transport)
httpx.Client(mounts={
"http://": httpx.HTTPTransport(proxy="http://localhost:8030"),
"https://": httpx.HTTPTransport(proxy="http://localhost:8031"),
})
app= shortcut → explicit transport=
What app= was for: letting you point a Client directly at a WSGI or ASGI Python callable instead of a URL, so you could test your web app without spinning up a real server.
Before:
httpx.Client(app=my_asgi_app, base_url="http://testserver")
After:
httpx.Client(transport=httpx.ASGITransport(app=my_asgi_app), base_url="http://testserver")
# or for WSGI:
httpx.Client(transport=httpx.WSGITransport(app=my_wsgi_app), base_url="http://testserver")
allow_redirects= → follow_redirects=, default flipped to False
What changed: in the 0.20 release, redirects stopped being followed automatically by default.
Before (pre-0.20 behavior, and the old kwarg name):
httpx.get("http://api.github.com/")
# silently redirects http -> https, meaning every request was actually sent twice
After:
httpx.get("http://api.github.com/", follow_redirects=True) # opt-in explicitly
# or
httpx.Client(follow_redirects=True) # opt-in for the whole client
The maintainers gave a concrete example — a client configured for http://api.github.com/ was silently sending every single request twice (once to get redirected, once to the real HTTPS URL), wasting a round-trip nobody asked for. They decided implicit redirect-following causes more surprise bugs (accidentally hitting an endpoint twice, e.g. a payment POST) than it saves typing, so it became opt-in. This was explicitly flagged as "no universally right answer, just a trade-off" in their own release notes.
Before: json={"a": 1, "b": 2} serialized with the stdlib default spacing → {"a": 1, "b": 2} (space after : and ,).
After: same call now serializes to {"a":1,"b":2} (no extra spaces).
This is a good change!
Before: spaces in query params encoded as +, and / inside a query value encoded as %2F (this is the old application/x-www-form-urlencoded-style behavior, same as requests).
After: spaces encoded as %20, and / left unescaped in the query portion (matches how Chrome/Safari/Firefox actually build URLs, per the WHATWG spec rather than the older RFC 3986 reading).
# requests-style / old httpx:
"?q=hello+world&path=a%2Fb"
# new httpx:
"?q=hello%20world&path=a/b"
This is a case where IETF RFC3986 vs. WHATWG's disagree, and real browsers follow WHATWG.
.netrc handling → explicit httpx.NetRCAuth()
Before: if you had a ~/.netrc file, httpx would silently pick up and use those credentials on any matching request.
After:
httpx.get(url, auth=httpx.NetRCAuth())
Silently reading a credentials file from disk shouldn't be done by default.
response.iter_lines() behaviorBefore: yielded lines including trailing newline characters ("line one\n").
After: matches Python's own str.splitlines() / file-iteration convention — newlines stripped ("line one"), and a related performance bug fixed.
Matching stdlib conventions (for line in file: style) is less surprising than inventing your own line-splitting semantics.
QueryParams became immutableBefore (implied by the old API): client.params.update(...) mutated in place.
After:
client.params = client.params.merge({"key": "value"})
# or the more granular:
params.set("key", "value")
params.add("key", "value") # allow duplicate keys
params.remove("key")
Mutable shared state (like a dict) attached to a client is a classic source of bugs
Background: today, one package gives you both a blocking (Client) and non-blocking (AsyncClient) interface, because under the hood both share the same code via a code-generation trick (unasync) that turns the async source into the sync version automatically at build time.
Before:
import httpx
r = httpx.get("https://example.org") # sync
async def main():
async with httpx.AsyncClient() as cli:
r = await cli.get("https://example.org") # async
After (previewed):
# pip install httpx
import httpx
r = httpx.get("https://example.org") # sync only
# pip install ahttpx <-- a *different* package
import ahttpx
r = await ahttpx.get("https://example.org") # async only
The maintainers reasoned that if you only ever use the sync client, you're still forced to install anyio and sniffio (async-support dependencies) that you never touch. Splitting the packages lets sync-only users have a genuinely smaller dependency tree, and removes the slightly awkward Client/AsyncClient naming duplication in favor of one name per package.
The controversy (worth knowing about): this is the most contested item in the whole redesign. Simon Willison and several SDK maintainers (including Anthropic's and OpenAI's own Python SDK maintainers, both of which depend on httpx<1) argued in the public discussion that Python's inability to install two versions of the same package side-by-side means a hard breaking split could fragment the ecosystem for a long transition window, similar to what happened with Pydantic 1→2. Proposed alternatives floated in that thread include shipping the new design under a different name entirely (httpx2) or keeping both old and new APIs inside one package under a versioned namespace (httpx.v1). None of this is resolved as of the latest visible discussion activity.
My take: The whole reason why ppl chose httpx over aiohttp is because it can handle both.
json=, data=, files= shortcuts replaced by typed content= objectsBackground: today httpx (like requests before it) offers three different keyword arguments depending on what kind of body you're sending, and picks the right Content-Type header for you based on which one you used.
Before:
client.post(url, json={"key": "value"}) # application/json
client.post(url, data={"key": "value"}) # form-urlencoded
client.post(url, files={"upload": open("report.pdf", "rb")}) # multipart/form-data
client.post(url, data={"name": "a"}, files={"upload": f}) # mixed form + file
After (previewed):
client.post(url, content=httpx.JSON({"key": "value"}))
client.post(url, content=httpx.Form({"key": "value"}))
client.post(url, content=httpx.Files({"upload": httpx.File("report.pdf")}))
client.post(url, content=httpx.MultiPart(
form={"name": "a"},
files={"upload": httpx.File("report.pdf")},
))
Java boi's fantacy to please the type checker.
Background: currently timeout= accepts a bare float, a tuple of floats, or a Timeout instance; proxy= accepts a string, a URL, or a Proxy instance; similar flexibility exists for auth= and headers=.
The design-call notes floated constraining these to fewer accepted shapes for clarity, but a maintainer pushed back in the same conversation that "it's not clear tightening the API types is a better user experience and could cause churn" — so this one is explicitly unresolved, not a committed change.
# `proxy=` → `ProxyTypes`
httpx.Client(proxy="http://localhost:8030") # plain string
httpx.Client(proxy=httpx.URL("http://localhost:8030")) # URL object
httpx.Client(proxy=httpx.Proxy("http://localhost:8030")) # Proxy object, needed if you
# also want proxy auth/headers
# `timeout=` → `TimeoutTypes`
httpx.Client(timeout=10.0) # single float applied to connect/read/write/pool
httpx.Client(timeout=None) # disable timeouts entirely
httpx.Client(timeout=httpx.Timeout(connect=5.0, read=30.0, write=10.0, pool=5.0)) # explicit object
# `auth=` → `AuthTypes`
httpx.get(url, auth=("username", "password")) # 2-tuple -> Basic auth
httpx.get(url, auth=httpx.DigestAuth("username", "password")) # explicit Auth subclass
httpx.get(url, auth=my_callable) # any callable(request) -> request, via FunctionAuth
# `headers=` → `HeaderTypes`
httpx.get(url, headers={"User-Agent": "my-app"}) # plain dict
httpx.get(url, headers=[("User-Agent", "my-app")]) # list of tuples (allows duplicate keys)
httpx.get(url, headers=httpx.Headers({"User-Agent": "my-app"})) # explicit Headers object
tl;dr httpx v1.0 is trying to giving type system a handjob like Java
2026-08-29 16:47:00
当前的 MiMoCodev0.1.13里,Responses API 有两套入口
openai / github-copilot / azure。gitlab 且每个模型加上 "useResponsesApi": true 就tmd很神奇。。一眼 vibe coding。我感觉世界上理解 Responses API 意义的人可能比例很少。。。
gitlab
{
"$schema": "https://mimo.xiaomi.com/mimocode/config.json",
"provider": {
"gitlab": {
"name": "GitLab",
"models": {
"gpt-5-codex": { "useResponsesApi": true }
}
}
}
}
可以通过 GITLAB_INSTANCE_URL GITLAB_TOKEN 环境变量去变通,但是 token 来自 directAccessClient.getDirectAccessToken() 走的是 GitLab 的 token-exchange 协议(auth.openai.com 那套 OAuth exchange)
所以即使能把 instanceUrl 骗过去,token 交换这一步也会因为不是真 GitLab token 而失败——没有可配置的开源端点/key 入口,也没有绕过 token-exchange 的开关。
openai 这个 provider id支持自定义 options.baseURL + apiKey
{
"$schema": "https://mimo.xiaomi.com/mimocode/config.json",
"provider": {
"openai": {
"name": "My Responses Backend",
"npm": "@ai-sdk/openai",
"models": { "my-model": { "name": "my-model" } },
"options": {
"baseURL": "https://你的后端域名/v1",
"apiKey": "你的key"
}
}
}
}
provider id 必须是 openai/github-copilot/azure 之一
@ai-sdk/openai-compatible 和 @ai-sdk/openai 都是来自 Vercel
@ai-sdk/openai-compatible |
@ai-sdk/openai |
|
|---|---|---|
| 是什么 | MiMoCode 自带/打包的通用 OpenAI 兼容适配器 | Vercel 官方的 OpenAI 原生 provider |
| 适用对象 | 任何"像 OpenAI"的第三方端点(OpenRouter、DeepSeek、本地 vLLM、各种网关) | OpenAI 自家 API,或任何支持 OpenAI Responses API 的后端(靠 baseURL 指过去) |
| 在 MiMoCode 里的默认角色 | 自定义 provider 的默认 npm(你不写 npm 时,up() 函数默认填它) |
绑定内置 openai provider id |
| 协议能力 | 只有 Chat Completions(languageModel()) |
Chat Completions + Responses API(responses() 方法) |
| 路由差异 | 通用路径 W() 里固定走 M.languageModel(id) → chat |
内置 openai loader 的 getModel 是 return Z.responses(Q) → 默认就走 Responses |
npm: "@ai-sdk/openai-compatible" + 任意自定义 id** → 永远走 Chat Completions,拿不到 Responses API。这是最常用但偏偏不支持 responses 的那个。npm: "@ai-sdk/openai" 本身有 responses() 方法,但只有当 provider id 叫 openai、github-copilot、azure,它们 loader 里也调 responses())时,MiMoCode 才会用 Z.responses(Q)。自定义 id + @ai-sdk/openai 会掉回通用 languageModel() = chat。| model | completions | responses |
|---|---|---|
| deepseek-v4-flash | ✅ 200 | ✅ 200 |
| deepseek-v4-flash-vision-exp | ✅ 200 | ✅ 200 |
| deepseek-v4-pro | ✅ 200 | ✅ 200 |
| glm-5 | ✅ 200 | ❌ 500 |
| glm-5.1 | ✅ 200 | ❌ 500 |
| glm-5.2 | ✅ 200 | ❌ 500 |
| glm-5.3 | ✅ 200 | ❌ 500 |
| glm-5.3-flash | ✅ 200 | ❌ 500 |
| gpt-5.6-luna | ❌ 500 | ✅ 200 |
| grok-4.5 | ❌ 503 | ✅ 200 |
| grok-4.6 | ❌ 401 | ✅ 200 |
| hy3 | ✅ 200 | ❌ 500 |
| hy3-preview | ❌ 400 | ❌ 500 |
| hy4-preview | ✅ 200 | ❌ 500 |
| kimi-k2.5 | ✅ 200 | ❌ 500 |
| kimi-k2.6 | ✅ 200 | ❌ 500 |
| kimi-k2.7-code | ✅ 200 | ❌ 500 |
| kimi-k3 | ✅ 200 | ❌ 401 |
| longcat-2.0 | ✅ 200 | ❌ 500 |
| mimo-v2-omni | ❌ 400 | ❌ 500 |
| mimo-v2-pro | ❌ 400 | ❌ 500 |
| mimo-v2.5 | ✅ 200 | ❌ 500 |
| mimo-v2.5-pro | ✅ 200 | ❌ 500 |
| minimax-m2.5 | ✅ 200 | ❌ 401 |
| minimax-m2.7 | ❌ 500 | ❌ 500 |
| minimax-m3 | ✅ 200 | ❌ 401 |
| muse-spark-1.2-contributor | ❌ 500 | ✅ 200 |
| qwen3.5-plus | ✅ 200 | ❌ 401 |
| qwen3.6-plus | ✅ 200 | ❌ 401 |
| qwen3.7-max | ✅ 200 | ❌ 401 |
| qwen3.7-plus | ✅ 200 | ❌ 401 |
| qwen3.8-flash | ✅ 200 | ❌ 401 |
| qwen3.8-max | ✅ 200 | ❌ 401 |
| model | completions | responses |
|---|---|---|
| big-pickle | ⚠️ 429 | ❌ 500 |
| hy3-free | ✅ 200 | ❌ 500 |
| laguna-s-2.1-free | ✅ 200 | ❌ 500 |
| ling-3.0-flash-fin-free | ✅ 200 | ❌ 500 |
| mimo-v2.5-free | ⚠️ 429 | ❌ 500 |
| muse-spark-1.2-contributor-free | ❌ 500 | ✅ 200 |
| nemotron-3-ultra-free | ❌ 000 | ❌ 500 |
需要付费的 401 Insufficient balance 我就懒得测了。
2026-08-16 14:06:00
git fetch origin main --depth=1 一次只下一个 pack,包含所有缺失对象,国内这网络你懂的,断开就废了,然后从头下载,往往又出事;
老外不懂国内网络条件这么艰苦,只能自己让AI改造。结果:
https://github.com/est/snippets/tree/master/git-fr
步骤:
git fetch --filter=blob:none --depth=1 —— 只拉 commit+tree,这个速度很快,只有KB级别的传输rev-list --objects --missing=print 算缺失集合,500 个 blob 一批回填踩过的坑
git fetch origin <blobsha> 每次都会打印 fatal: bad object <sha> + error: ... did not send all necessary objects,但事后检查对象blob却真的下载了。后来发现因为cat-file 会拉数据,rev-list --missing=print 不会。git fetch origin <sha> 拿到的 pack 是薄包(thin pack)——服务器按客户端声明做 delta 压缩。如果本地声明的是 refs/remotes/origin/main(一个 commit),服务器据此假定我们拥有整棵树的全部 blob所以就啥都不给GIT_TRACE=1 git cat-file blob <missing> 抓到了 git 自己 lazy fetch 时实际执行的命令:git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags \
--no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin
关键:
- fetch.negotiationAlgorithm=noop —— 不发 haves,服务器没法薄包化,只能发完整对象;
- --filter=blob:none —— 显式声明的 want 会绕过过滤器直接发送(partial clone 语义);
- --stdin —— want 列表从 stdin 读,天然支持批量。
还有很多放到 README.md 里了
安装
把 fr.sh 放到 PATH(如 ~/.local/bin/git-fr),即可 git fr 使用。
或 git config --global alias.fr '!<绝对路径>/fr.sh'。
感觉,现在搞这些东西似乎意义不大了?反正大家都是让AI自己临时写一个脚本。。。🤣
有空让AI写个支持多线程并发 fetch 的。
2026-08-08 09:53:00
去年搓了个词典,本来懒得动了,但是看到opencode订阅还没用完,deepseek那么便宜,想着不用白不用,找了个最浪费token的方式,让AI再写一部词典
当年的AI我记得还是 gemini-2,其实很多东西不成熟。我也对AI脾气没摸准,很自然的就:“请输出 JSON”。今年学聪明了,直接上YAML。AI高呼你这想法真离经叛道但又极度合理。
甚至,block scalar(|)支持多行文本,不转义!
一个小技巧是,尽量避免嵌套的,什么 map套entries,每个 entry 再挂一个 senses 列表、每个 sense 再挂一个 examples 列表,别玩这一套。直接拍扁,层级尽可能少,整个 schema 里不允许出现一个方括号!
AI帮我写了个 validator,调教这一块还得是AI卷AI。不对劲的就返回错误让AI二次输出。但是基本都是1-pass通过。之前JSON翻车太多了。
跑了一圈还发现,短码对模型不友好。词性枚举最初写成 n/v/adj/adv,模型就开始输出 v.、V、vb、verb,改成全称 noun/verb/adjective 。我觉得这个跟 LLM 预训练 token 的分布高度相关,不要以为「缩写」对人和机器更友好!LLM是反过来的。
10年前第一版想的是薅Google Dictionary羊毛,去年第二版静态站中毒,想弄json,后来发现不对。20k的词汇量,对应20k个零碎小文件不可控。
这次第三版,回到传统RDBMS。一开始想一张 FTS5 全文索引表,让用户能模糊搜索、子串匹配,甚至搜中文释义——感觉这才配得上"AI 词典"的科技感。
然后又想了下,问了自己一个本该一开始就问的问题:用户到底是怎么用词典的?
输入一个完整的词,得到词条。前缀联想 输入 "run" 提示 "running" 用一句 LIKE 'run%' 就够了,一共三张表
words:词头senses:义项surfaces:一切能解析到这个词的可供检索字符串:原形、变形、同义词、反义词、搭配一个激进的决定,words 这个表甚至不保存词汇的归一化原始形式!直接作为 lemma 放到 surfaces 表里!
接下来,让AI写了个「递归」采集器,很简单,随便想几个单词,prompt就是让AI做词典;然后词典里的解释、例句再分词,再翻译。直到所有翻译和解释能自我包含,这样自动生成一部完整的词典。
让AI搓 BFS,起初几万词汇很舒服,很快发现,漂移进生僻词陷阱了:water 的释义带出了 odorless、colorless、tasteless,这些是英语词没错,所以又让AI给每个词标上分级,A1 A2 B1 B2 C1 C2 按顺序遍历。但我脑子抽了,没想到,这词在生成YAML之前,不知道自己的等级,AI提醒我,假设子词继承父词级别——够用就行。
后来想了下,反正AI闲着也是闲着,于是下载了一个权威 CEFR 词表,17 万词
https://github.com/Maximax67/Words-CEFR-Dataset/
这个词表的 level 是浮点,1.0=A1 … 6.0=C2,1.5 是 A1/A2 边界,天然可以先处理常用词后采集偏科的
甚至还提供了真实词频,the 出现了 928 亿次,还有 lemma 链接:ran→run、went→go、children→child,甚至还可以求出 clothes 没有链接,所以它是真词头。
跑起来之后,词典开始自己生长,敲入 caffeinate 睡了一觉。第二天起来一看,额度才用了 10% 不到。。Deepseek太耐烧了
顺便发现opencode关只在流式模式下返回内容,非流式直接返回空 body。挺浪费的说实话。
整理发现这个 CEFR 也tmd掺水,AI看了下挺多 复数、连字符、西语、意大利语词汇混进去,后来干脆一刀切100w以上词频才纳入采集
哎,还得二次清洗
后续会把这个词典做出 on-demand 的:用户查一个词,命中缓存直接返回;没命中,当场调一次 AI 生成、校验、入库
最后,地址依然是:https://def.est.im/
清洗的时候AI又说怎么这么多 人名 地名 乐队名,噪音可以跳过。
我想了下,其实对 language learner 还挺有用的。你输入一个名字,其实想知道这个名字的内涵,来源,是否得体,有坑的。
传统词典那些老学究那有时间给你折腾这那的。
一个地名儿,既然你查了,肯定是从某个书 杂志 地图册 上碰到的,你是冲着大新闻、 奇闻逸事 来的。传统词典只告诉这是某国某地,有啥用?
其次AI又挑三拣四,说这西语词汇,扔了吧。老美现在西语人口那么多,流行文化我感觉都半边天了,用户查词汇碰到的概率很高。
过去纸质时代,让双语高手静下来编一部这样的词典,不容易
但是AI时代,有机会了。
我想就从这样的第一性原理,做一个完全服务语言学习者,感兴趣的词典。
不过 tokens 已经耗尽了,下次再说。
2026-08-07 10:35:00
起初一个很简单的想法,把一个app/服务的私人数据,集中存数据库麻烦,不想维护担责,干脆放用户自己邮箱里,所谓"BYOS(自带存储)"。反正协议 SMTP/POP3/IMAP 很方便,然后调查了下很快发现,时代变了。
本来寄希望IMAP 这个协议,感觉能缝成一个读写API,
-APPEND 写(一封邮件 = 一个对象),FETCH 读,STORE 改 flag,COPY/MOVE 移动
- "目录 + KV"模型,flag(\Seen、\Flagged 等)即元数据位
- SEARCH查询 主题、日期、自定义 header等
- 增量同步: UID + UIDVALIDITY,IDLE 可做准实时推送(仅单文件夹)
其实古董的 GMailFS / GMail Drive 之类把 Gmail 当虚拟磁盘的开源项目很多,各类 邮件备份 工具、笔记类应用的同步,都走过这条路
以前输入帐号密码这种简单方式,早被废了;甚至专用密码、动态密码都下掉了。主流邮箱(Gmail/Outlook/Yahoo)已全面转向 OAuth 2.0,就是它协议上是要走一个握手然后验证token的流程,用户门槛太高了。
让AI跑了一圈发现:
| 供应商 | 开发者接入方式 | 开发者审核门槛 | 用户授权操作 | 用户麻烦程度 |
|---|---|---|---|---|
| Gmail | OAuth 2.0(SASL XOAUTH2),scope https://mail.google.com/,需建 GCP 项目 |
最重:restricted scope,正式对外必须过 OAuth 验证 + 安全评估,每年复审;未过审只能 ≤100 测试用户或内部使用 | 跳转 Google 授权页点同意(OAuth);或开 2FA 后生成 16 位应用专用密码粘贴 | 低(OAuth)~ 中(app password) |
| Outlook.com | OAuth 2.0(SASL XOAUTH2),scope https://outlook.office.com/IMAP.AccessAsUser.All,需注册 Microsoft Entra 应用 |
中等:Entra 注册免费即时;无强制审核;Publisher verification(去掉"未验证应用"警示)可选,需 EV 证书约 $100/年 | 必须先到网页设置手动打开 IMAP(默认关);之后走 OAuth 自动授权,或应用密码 | 中(多一步开 IMAP) |
| Yahoo | OAuth 2.0(SASL OAUTHBEARER),需在 YDN 注册应用;或应用密码 | 低:无 Google 式受限 scope 审核 | OAuth 授权页;或设置里生成应用密码 | 低(OAuth)~ 中(app password) |
| iCloud | 官方支持三方应用 OAuth 授权,或应用专用密码 | 低-中 | 必须开 2FA,到 account.apple.com 生成应用专用密码(每应用一个,上限 25 个) | 中 |
| QQ 邮箱 | 无 OAuth,只有"授权码" | 无(无审核无文档) | 设置 → 账户 → 开启 IMAP/SMTP → 短信验证 → 生成 16 位授权码 → 粘贴进 App | 中(2~3 分钟) |
| 163 / 126 | 同上(客户端授权密码) | 无 | 同上 | 中(2~3 分钟) |
| 供应商 | IMAP 服务器 | 容量 | 单封大小 | 连接/会话限制 | 明文密码状态 |
|---|---|---|---|---|---|
| Gmail | imap.gmail.com:993 | 15GB(与 Drive/相册共享) | 25MB | 15 并发连接;会话约 24h | 已废除,需 OAuth 或 app password |
| Outlook.com | outlook.office365.com:993 | 15GB | 约 35MB 量级 | 多客户端并发触发风控 | 已废除(2022-10 起强制 Modern Auth) |
| Yahoo | imap.mail.yahoo.com:993 | 1TB | 25MB 附件 | 无公开数字 | 2024-05-15 起停用明文密码 |
| iCloud | imap.mail.me.com:993 | 免费 5GB | 约 20MB 量级 | 无公开数字 | 需应用专用密码 |
| QQ 邮箱 | imap.qq.com:993 | 约 16GB 级 | 普通附件约 50MB,大文件走中转站(有时效) | 无公开文档,风控靠账号安全策略 | 无 OAuth,仅授权码 |
| 163 / 126 | imap.163.com:993 | 免费容量较大(自动扩容) | 普通附件约 50MB,大文件走"超大附件"(有时效) | 无公开文档 | 无 OAuth,仅授权码 |
⚠️ 以上数字除标注官方来源者外均为常用量级,上线前需逐家实测;QQ/163 无官方公开 API/协议文档,以实操惯例为准。
Gmail / Outlook / Yahoo
算了。不要折腾。
2026-08-03 23:24:00
最近观察到娃学会了一些脱口而出的骂人的话,他骑车遇到看不惯的现象,就会骂一句 “牲口” !
我也没太多去干预,毕竟比 “肏” 这类秽语要委婉那么一丢丢。
然后我最近也喜欢一边开 Vibe Coding 一边挂机 Rimworld 种田,
然后就发现一个事儿,我在牧场区域种的 恶蘑菇,一种非食用植物,不会被动物吃掉;如果种玉米 稻米 土豆就会被偷吃,然后基地的粮食就少了一份。
所以突然就得到这么一个莫名其妙的理论:
动物并不会去主动破坏自己吃不了的东西
可能也有反例,狼、狮子会杀死竞争者的幼崽,黑猩猩会打架,一些鸟会破坏别的鸟的蛋;松鼠会偷走自己暂时吃不了的东西;海豚玩弄猎物;
通过这些例子,感觉破坏性和智力正相关,也就是利用预测能力和因果性去糟蹋东西。骂人是动物,仿佛动物低人一等,但是人心有的时候,甚至还不如“畜牲”
如果把这个限定缩小一点:
动物作恶的能力仅限身体。
人类里的坏逼,可能是唯一为了某个概念性的目标,会系统性破坏自己无法直接利用、甚至未来有价值东西的动物
智力让生物获得了创造未来的能力,也让它获得了毁灭未来的能力。
植物貌似也只会利用身体去占领局部环境,比如分泌毒素,遮挡阳光,向环境释放某种抑制剂。
但人类可以把攻击性扩大到远超生存需求的规模。为了荣誉、信仰、身份、复仇去糟蹋自己甚至不认识的人或者物。
这是一种非常特殊的“脱离现实反馈”的能力。
所以「畜牲」这个骂法有点讽刺——很多时候,骂别人「像动物」,其实是在指责对方缺少克制;但换个角度看,动物反而经常遵循一种很严格的生态逻辑:消耗资源、竞争、生存,不会为了纯粹抽象的理由把世界变得更糟。
btw 这个游戏真的有毒。好多年前尝试畜牧,结果直接生了一小崽,指数增长,然后过冬吃光了粮食,就纷纷饿死。然后来年就严格限制动物数量,定期宰杀,发现最简单的办法是砍掉雄性,留下1-2个传种,留下产奶产蛋的雌性。在感叹残酷的同时,结果无意中发现了 回交 。
据说宋朝马政废弛,就是因为儒生不忍心这么干,所以导致良马欠缺。
你说这个这个是人类的扭曲的道德还是动物的天性呢?