tsx81429 发表于 2025-4-2 23:38:33

GitHub Actions实现Z-lib与WebDAV联动

GitHub Actions实现Z-lib与WebDAV联动



需求

获取到想看的书后,通常需要多装备查看,尤其盼望平板可以直接阅读,虽然有甚多平台可以同步,但是各种平台使用app差别,或者有一些需要收费。z-lib能够查到很多书,但是通常需要下载到当地。
后发现z-lib可以发送到邮箱,下载起来更为方便,但是需要开邮箱。
同时发现使用WebDAV网盘可以在平板上举行网盘文件阅读,由于博主用的是安卓平板以是这里使用的是静读天下(写的时候查到一个新的app叫安读anx-reader,转头试一下)。
以是需求就是将z-lib的书发到邮箱之后需要主动化地将文件上传到WebDAV,高大上一点也可以说是自建线上图书馆()
配置要求


[*] 一个163邮箱账号(代码写的是163邮箱的,别的邮箱也可以,改一下代码的对应部分即可)
[*] 一个Github账号
[*] 一个Z-lib账号
解决过程

最初的需求
• 从邮箱主动提取特定发件人当天的邮件附件,并将其传输到WebDAV网盘。
实验使用云服务器
• 思量过使用云服务器来运行脚本,但思量到长期租用的成本较高且利用率低,以为不划算。
探索按量付费的云函数服务
• 发现大多数平台需要企业认证或绑定信用卡,对于个人用户来说门槛过高,因此放弃。
实验IFTTT
• 理论上可以连接差别的应用程序,但在实际使用中发现对国内服务的支持不足,免费版功能受限,无法满足需求。
实验ActionFlow
• 按照示例配置订阅功能时出现错误,附件上传环节始终失败,经过多次调试未果,终极放弃。
GitHub Actions方案
Python脚本实现
• 使用imaplib​连接邮箱并筛选符合条件的邮件。
• 下载附件到当地,并添加防重复上传和错误处置惩罚机制。
配置文件调解
• 触发频率从每15分钟一次改为逐日定时实行。
• 增加手动触发选项。
流程步骤

[*]拉代替码。
[*]配置Python环境。
[*]安装依赖库。
[*]运行脚本。
[*]使用现成的WebDAV上传工具传输文件。
解决的问题
• imaplib​报错:通过查阅文档发现需要先选择收件箱再搜索邮件。
• WebDAV上传失败:查抄WebDAV的权限配置和路径格式,发现问题在于路径斜杠方向错误。
其他改进
• 添加上传后主动清算当地附件的功能。
总结
• 结合开源工具和云服务,以零成本方式实现了需求。
• 关键在于分步骤排查问题,并合理利用GitHub Actions等现有平台。
• 豆包在脚本编写和调试逻辑方面提供了很多实用发起,大大淘汰了试错时间。
教程

一. 准备163邮箱接口



[*]登录邮箱网页版,点击页面右上角的 “设置” 图标,选择 “POP3/SMTP/IMAP”。
[*]在 “IMAP/SMTP 服务” 处,选择开启服务,根据提示完成手机验证等操作。开启后会得到授权码,后续配置客户端时需要用到。
[*]记录为​​EMAIL_ADDRESS​​ 、​​EMAIL_PASSWORD​​
二. 准备WebDAV接口

准备WebDAV网盘可以是本身配置的也可以使用坚果云等网盘

[*] 坚果云 教程 每月有上传下载流量的限定 坚果云第三方应用授权WebDAV开启方法 | 坚果云帮助中央 登岸后找到下图所示的地方设置密码
​https://i-blog.csdnimg.cn/direct/631fb5f52fa041bd97269ee92ae633ac.png​
[*] Infini 网盘 外网的 免费20G空间 有邀请码加5G 邀请码:8HPAP
注册登录后打开配置页面:My Page|InfiniCLOUD 翻到Apps Connection
​https://i-blog.csdnimg.cn/direct/10330049534345ed85ac0b1e14f422ed.png​
其他的方式就不外多赘述了,总之末了需要一个链接、一个ID和一个密码,分别记录为​WEBDAV_ENDPOINT​、WEBDAV_PASSWORD​和WEBDAV_USERNAME​。
三、配置Github Actions

1. 创建 GitHub 仓库

起首,你需要在 GitHub 上创建一个新的仓库,用于存放你的项目代码和 GitHub Actions 配置文件。


[*]登录你的 GitHub 账号。
[*]点击右上角的 “+” 号,选择 “New repository”。
[*]填写仓库名称、描述等信息,选择仓库的可见性(公开或私有),然后点击 “Create repository”。
2. 准备项目代码

准备需要运行的脚本,这里使用Python接收邮箱信息,对发件人为Z-library的邮件下载附件并上传到网盘,创建了scripts目录存放脚本 scripts/get_and_upload_emails.py​
import imaplib
import email
import os
import datetime


class Email(object):
    def __init__(self):
      self.host = "imap.163.com"
      self.account = os.getenv('EMAIL_ADDRESS')
      self.password = os.getenv('EMAIL_PASSWORD')
      self.server = self.login()
      self.log_file = 'upload_log.txt'
      self.last_check_file = 'last_check.txt'
      self.attachments_folder = 'attachments'
      if not os.path.exists(self.attachments_folder):
            os.makedirs(self.attachments_folder)

    def login(self):
      server = imaplib.IMAP4_SSL(self.host)
      server.login(self.account, self.password)
      imaplib.Commands["ID"] = ('AUTH',)
      args = ("name", self.account, "contact", self.account, "version", "1.0.0", "vendor", "myclient")
      server._simple_command("ID", str(args).replace(",", "").replace("\'", "\""))
      return server

    def get_last_check_date(self):
      if os.path.exists(self.last_check_file):
            with open(self.last_check_file, 'r') as f:
                try:
                  return datetime.datetime.strptime(f.read().strip(), "%Y-%m-%d %H:%M:%S")
                except ValueError:
                  pass
      return None

    def set_last_check_date(self, date):
      with open(self.last_check_file, 'w') as f:
            f.write(date.strftime("%Y-%m-%d %H:%M:%S"))

    def get_new_mail_attachments(self):
      last_check = self.get_last_check_date()
      now = datetime.datetime.now()
      attachments = []
      try:
            self.server.select(mailbox='INBOX')
            if last_check:
                search_criteria = f'(SINCE "{last_check.strftime("%d-%b-%Y")}" BEFORE "{now.strftime("%d-%b-%Y")}")'
            else:
                search_criteria = f'(SENTSINCE "{now.strftime("%d-%b-%Y")}")'
            stat, data = self.server.search(None, search_criteria)
            if stat == 'OK':
                mail_list = data.split()
                for index in mail_list:
                  status, message = self.server.fetch(index, "(RFC822)")
                  if status == 'OK':
                        msg = email.message_from_bytes(message)
                        msg_date = email.utils.parsedate_to_datetime(msg['Date'])
                        if last_check and msg_date <= last_check:
                            continue
                        sender = email.header.decode_header(msg["From"])
                        if isinstance(sender, bytes):
                            charset = email.header.decode_header(msg["From"])
                            sender = sender.decode(charset) if charset else sender.decode()
                        if "Z-Library" in sender:
                            for part in msg.walk():
                              if part.get_content_maintype() == 'multipart':
                                    continue
                              if part.get('Content-Disposition') is None:
                                    continue
                              file_name = part.get_filename()
                              if file_name:
                                    save_path = os.path.join(self.attachments_folder, file_name)
                                    with open(save_path, 'wb') as f:
                                        f.write(part.get_payload(decode=True))
                                    attachments.append(save_path)
      except Exception as e:
            print(f"Error getting attachments: {e}")
      finally:
            self.set_last_check_date(now)
      return attachments

    def check_and_upload(self, attachments):
      uploaded_files = self.read_upload_log()
      for attachment in attachments:
            if attachment not in uploaded_files:
                print(f"Uploading {attachment}...")
                self.update_upload_log(attachment)
            else:
                print(f"{attachment} has already been uploaded, skipping.")

    def read_upload_log(self):
      if os.path.exists(self.log_file):
            with open(self.log_file, 'r') as f:
                return f.read().splitlines()
      return []

    def update_upload_log(self, file_name):
      with open(self.log_file, 'a') as f:
            f.write(file_name + '\n')

    def str_to_unicode(self, s, charset):
      if charset:
            return s.decode(charset)
      return s

    def parse_message(self, msg):
      content = ""
      for part in msg.walk():
            if part.get_content_type() == "text/plain":
                charset = part.get_content_charset()
                if charset is None:
                  charset = "utf-8"
                try:
                  content += part.get_payload(decode=True).decode(charset, errors='ignore')
                except UnicodeDecodeError:
                  pass
      return content


if __name__ == "__main__":
    email_client = Email()
    attachments = email_client.get_new_mail_attachments()
    email_client.check_and_upload(attachments)
3. 准备action脚本

GitHub Actions 使用 YAML 文件来定义工作流(Workflow)。工作流包含一个或多个作业(Job),每个作业又包含一个或多个步骤(Step)。


[*] 在项目根目录下创建一个名为 .github/workflows​ 的文件夹
[*] 在 .github/workflows​ 文件夹中创建一个 YAML 文件,例如 .github/workflows/upload_email_attachments.yml​,并使用文本编辑器打开它。
name: Upload Email Attachments to WebDAV

on:
schedule:
    - cron: '0 0 * * *'# 每天 0 点 0 分执行
workflow_dispatch:# 允许手动触发此工作流程

#   - cron: '*/15 * * * *'# 每 15 分钟检查一次
#    - cron: '0 0 * * *'
# on: #点击触发
# on: # 触发工作流程的事件,这里设置为在push时触发

jobs:
upload-attachments:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
      uses: actions/checkout@v3

      - name: Set up Python
      uses: actions/setup-python@v4
      with:
          python-version: 3.x

      - name: Install dependencies
      run: pip install --upgrade pip

      - name: Get new email attachments and check upload
      env:
          EMAIL_ADDRESS: ${{ secrets.EMAIL_ADDRESS }}
          EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
      run: python scripts/get_and_upload_emails.py

      - name: Upload to WebDAV
      uses: bxb100/action-upload@main
      with:
          provider: webdav
          provider_options: |
            endpoint=${{ secrets.WEBDAV_ENDPOINT }}
            username=${{ secrets.WEBDAV_USERNAME }}
            password=${{ secrets.WEBDAV_PASSWORD }}
            root=/reading/
          include: 'attachments/*'

      - name: Delete local attachments after successful upload
      if: success()
      run: |
          if [ -d "attachments" ]; then
            rm -rf attachments/*
          fi

4.运行Actions

设置环境变量存储前面的账号信息密码等等
​https://i-blog.csdnimg.cn/direct/3856575767d742fb8c0aa64426837abd.png​

​https://i-blog.csdnimg.cn/direct/e555650459424ca6a8515d89bb4efe2e.png​
这里直接测试可以直接点击运行,如果需要设置定时运行,更改YAML 文件的schedule位置即可。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: GitHub Actions实现Z-lib与WebDAV联动