感谢 awesome-english-ebooks 项目作者,感谢 Claude Code 老师。
获取邮箱密授权码
我用 QQ 邮箱,需要使用授权码代替邮箱密码。
- 登录 QQ 邮箱 → 设置 → 账号与安全
- 下滑找到 “POP3/IMAP/SMTP/Exchange/CardDAV 服务”,开启
- 按提示发短信验证,获得授权码,保存好
创建 Github 仓库并添加密钥
- 在 GitHub 新建一个仓库(随意命名,如 ebook-sync)
- 进入仓库 → Settings → Secrets and variables → Actions → New repository secret
添加两项:
- QQ_EMAIL 你的 QQ 邮箱地址
- QQ_AUTH_CODE 第一步获取的授权码
设置 Github Actions
在本地 git 初始化一个空文件夹,然后添加 .github/workflows/send-epub.yml 文件,内容如下。完成后推送到 ebook-sync 仓库。
name: Send New EPUBs to Email
on: schedule: - cron: '0 22 * * 5' # 每周六 06:00 北京时间 workflow_dispatch:
permissions: contents: write
jobs: send-epub: runs-on: ubuntu-latest
steps: - name: Checkout magz repo uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }}
- name: Find and download new epub files id: find_epubs run: | # 读取上次处理的 commit SHA LAST_SHA="" if [ -f .last_commit ]; then LAST_SHA=$(cat .last_commit) fi
# 获取源仓库最新的 commit SHA LATEST_SHA=$(curl -s \ "https://api.github.com/repos/hehonghui/awesome-english-ebooks/commits?per_page=1" \ | jq -r '.[0].sha')
# 没有新 commit,直接结束 if [ "$LAST_SHA" = "$LATEST_SHA" ]; then echo "没有新的 commit,无需处理" echo "has_files=false" >> $GITHUB_OUTPUT exit 0 fi
# 获取新增的 commits if [ -z "$LAST_SHA" ]; then # 首次运行:取最近 7 天 SINCE=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || \ date -u -v-7d +%Y-%m-%dT%H:%M:%SZ) COMMITS=$(curl -s \ "https://api.github.com/repos/hehonghui/awesome-english-ebooks/commits?since=${SINCE}&per_page=100" \ | jq -r '.[].sha') else # 后续运行:取 LAST_SHA 之后的所有 commits COMMITS=$(curl -s \ "https://api.github.com/repos/hehonghui/awesome-english-ebooks/compare/${LAST_SHA}...${LATEST_SHA}" \ | jq -r '.commits[].sha') fi
# 保存最新 SHA,供后续步骤提交 echo "$LATEST_SHA" > .last_commit
mkdir -p downloads HAS_ANY=false
for SHA in $COMMITS; do FILES_JSON=$(curl -s \ "https://api.github.com/repos/hehonghui/awesome-english-ebooks/commits/${SHA}" \ | jq -r '.files[] | select(.status == "added") | select(.filename | ascii_downcase | endswith(".epub")) | [.filename, .raw_url] | @tsv')
while IFS=$'\t' read -r FILENAME RAW_URL; do [ -z "$FILENAME" ] && continue DIR=$(echo "$FILENAME" | cut -d'/' -f1) BASENAME=$(basename "$FILENAME") SAVE_PATH="downloads/${DIR}___${BASENAME}"
[ -f "$SAVE_PATH" ] && continue
echo "下载: $BASENAME" curl -sL "$RAW_URL" -o "$SAVE_PATH" HAS_ANY=true done <<< "$FILES_JSON" done
if [ "$HAS_ANY" = true ]; then echo "has_files=true" >> $GITHUB_OUTPUT else echo "has_files=false" >> $GITHUB_OUTPUT fi
- name: Send one email per magazine if: steps.find_epubs.outputs.has_files == 'true' env: QQ_EMAIL: ${{ secrets.QQ_EMAIL }} QQ_AUTH_CODE: ${{ secrets.QQ_AUTH_CODE }} run: | python3 << 'EOF' import smtplib import os import glob import zipfile from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders from collections import defaultdict
MAG_NAMES = { "01_economist": "The Economist", "02_new_yorker": "The New Yorker", "04_atlantic": "The Atlantic", "05_wired": "Wired", }
SIZE_LIMIT = 50 * 1024 * 1024 # 50MB
email = os.environ["QQ_EMAIL"] password = os.environ["QQ_AUTH_CODE"]
magazines = defaultdict(list) for filepath in glob.glob("downloads/*___*.epub"): filename = os.path.basename(filepath) dir_key, basename = filename.split("___", 1) magazines[dir_key].append((basename, filepath))
for dir_key, files in magazines.items(): mag_name = MAG_NAMES.get(dir_key, dir_key)
msg = MIMEMultipart() msg["Subject"] = f"本周新增 EPUB - {mag_name}" msg["From"] = email msg["To"] = email
body_lines = [f"你好,\n\n本周《{mag_name}》新增了以下 EPUB 文件:\n"] oversized = []
for basename, filepath in files: size = os.path.getsize(filepath) if size <= SIZE_LIMIT: zip_name = basename.replace(".epub", ".zip") zip_path = filepath.replace(".epub", ".zip") with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: zf.write(filepath, basename) with open(zip_path, "rb") as f: part = MIMEBase("application", "octet-stream") part.set_payload(f.read()) encoders.encode_base64(part) part.add_header( "Content-Disposition", f'attachment; filename="{zip_name}"' ) msg.attach(part) body_lines.append(f"- {basename}(附件,zip 格式,解压后即可阅读)") else: size_mb = size / 1024 / 1024 url = f"https://github.com/hehonghui/awesome-english-ebooks/raw/master/{dir_key}/{basename}" oversized.append(f"- {basename}({size_mb:.1f}MB,超过 50MB)\n 下载链接:{url}")
if oversized: body_lines.append("\n以下文件超过 50MB,以链接形式发送:\n") body_lines.extend(oversized)
body_lines.append("\n仓库地址:https://github.com/hehonghui/awesome-english-ebooks") msg.attach(MIMEText("\n".join(body_lines), "plain", "utf-8"))
with smtplib.SMTP_SSL("smtp.qq.com", 465) as server: server.login(email, password) server.send_message(msg) print(f"已发送:{mag_name}") EOF
- name: Save last processed commit SHA run: | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git config user.name "github-actions[bot]" git add .last_commit git diff --staged --quiet || git commit -m "chore: update last commit SHA [skip ci]" git push
- name: No new epub this week if: steps.find_epubs.outputs.has_files == 'false' run: echo "没有新增 epub 文件,无需发送邮件"验证
在该仓库的 Actions 页的左侧点击 Send New EPUBs to Email,然后点击右侧的 Run workflow。
测试运行正常的话你会收到四封邮件,内容分别为原杂志仓库各杂志的最新一期提醒,杂志内容打包成 zip 文件1以附件形式附在其中。
后续每周六早晨六点会自动检查新文件并发送。
脚注
-
QQ 邮箱会对附件内容做审查,发送 Epub 文件会被拦截,打包成 zip 文件可行。 ↩