GitHub Actions实现Z-lib与WebDAV联动

打印 上一主题 下一主题

主题 1583|帖子 1583|积分 4749

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开启方法 | 坚果云帮助中央 登岸后找到下图所示的地方设置密码

  • Infini 网盘 外网的 免费20G空间 有邀请码加5G 邀请码:8HPAP
    注册登录后打开配置页面:My Page|InfiniCLOUD 翻到Apps Connection

其他的方式就不外多赘述了,总之末了需要一个链接、一个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​
  1. import imaplib
  2. import email
  3. import os
  4. import datetime
  5. class Email(object):
  6.     def __init__(self):
  7.         self.host = "imap.163.com"
  8.         self.account = os.getenv('EMAIL_ADDRESS')
  9.         self.password = os.getenv('EMAIL_PASSWORD')
  10.         self.server = self.login()
  11.         self.log_file = 'upload_log.txt'
  12.         self.last_check_file = 'last_check.txt'
  13.         self.attachments_folder = 'attachments'
  14.         if not os.path.exists(self.attachments_folder):
  15.             os.makedirs(self.attachments_folder)
  16.     def login(self):
  17.         server = imaplib.IMAP4_SSL(self.host)
  18.         server.login(self.account, self.password)
  19.         imaplib.Commands["ID"] = ('AUTH',)
  20.         args = ("name", self.account, "contact", self.account, "version", "1.0.0", "vendor", "myclient")
  21.         server._simple_command("ID", str(args).replace(",", "").replace("\'", """))
  22.         return server
  23.     def get_last_check_date(self):
  24.         if os.path.exists(self.last_check_file):
  25.             with open(self.last_check_file, 'r') as f:
  26.                 try:
  27.                     return datetime.datetime.strptime(f.read().strip(), "%Y-%m-%d %H:%M:%S")
  28.                 except ValueError:
  29.                     pass
  30.         return None
  31.     def set_last_check_date(self, date):
  32.         with open(self.last_check_file, 'w') as f:
  33.             f.write(date.strftime("%Y-%m-%d %H:%M:%S"))
  34.     def get_new_mail_attachments(self):
  35.         last_check = self.get_last_check_date()
  36.         now = datetime.datetime.now()
  37.         attachments = []
  38.         try:
  39.             self.server.select(mailbox='INBOX')
  40.             if last_check:
  41.                 search_criteria = f'(SINCE "{last_check.strftime("%d-%b-%Y")}" BEFORE "{now.strftime("%d-%b-%Y")}")'
  42.             else:
  43.                 search_criteria = f'(SENTSINCE "{now.strftime("%d-%b-%Y")}")'
  44.             stat, data = self.server.search(None, search_criteria)
  45.             if stat == 'OK':
  46.                 mail_list = data[0].split()
  47.                 for index in mail_list:
  48.                     status, message = self.server.fetch(index, "(RFC822)")
  49.                     if status == 'OK':
  50.                         msg = email.message_from_bytes(message[0][1])
  51.                         msg_date = email.utils.parsedate_to_datetime(msg['Date'])
  52.                         if last_check and msg_date <= last_check:
  53.                             continue
  54.                         sender = email.header.decode_header(msg["From"])[0][0]
  55.                         if isinstance(sender, bytes):
  56.                             charset = email.header.decode_header(msg["From"])[0][1]
  57.                             sender = sender.decode(charset) if charset else sender.decode()
  58.                         if "Z-Library" in sender:
  59.                             for part in msg.walk():
  60.                                 if part.get_content_maintype() == 'multipart':
  61.                                     continue
  62.                                 if part.get('Content-Disposition') is None:
  63.                                     continue
  64.                                 file_name = part.get_filename()
  65.                                 if file_name:
  66.                                     save_path = os.path.join(self.attachments_folder, file_name)
  67.                                     with open(save_path, 'wb') as f:
  68.                                         f.write(part.get_payload(decode=True))
  69.                                     attachments.append(save_path)
  70.         except Exception as e:
  71.             print(f"Error getting attachments: {e}")
  72.         finally:
  73.             self.set_last_check_date(now)
  74.         return attachments
  75.     def check_and_upload(self, attachments):
  76.         uploaded_files = self.read_upload_log()
  77.         for attachment in attachments:
  78.             if attachment not in uploaded_files:
  79.                 print(f"Uploading {attachment}...")
  80.                 self.update_upload_log(attachment)
  81.             else:
  82.                 print(f"{attachment} has already been uploaded, skipping.")
  83.     def read_upload_log(self):
  84.         if os.path.exists(self.log_file):
  85.             with open(self.log_file, 'r') as f:
  86.                 return f.read().splitlines()
  87.         return []
  88.     def update_upload_log(self, file_name):
  89.         with open(self.log_file, 'a') as f:
  90.             f.write(file_name + '\n')
  91.     def str_to_unicode(self, s, charset):
  92.         if charset:
  93.             return s.decode(charset)
  94.         return s
  95.     def parse_message(self, msg):
  96.         content = ""
  97.         for part in msg.walk():
  98.             if part.get_content_type() == "text/plain":
  99.                 charset = part.get_content_charset()
  100.                 if charset is None:
  101.                     charset = "utf-8"
  102.                 try:
  103.                     content += part.get_payload(decode=True).decode(charset, errors='ignore')
  104.                 except UnicodeDecodeError:
  105.                     pass
  106.         return content
  107. if __name__ == "__main__":
  108.     email_client = Email()
  109.     attachments = email_client.get_new_mail_attachments()
  110.     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​,并使用文本编辑器打开它。
    1. name: Upload Email Attachments to WebDAV
    2. on:
    3.   schedule:
    4.     - cron: '0 0 * * *'  # 每天 0 点 0 分执行
    5.   workflow_dispatch:  # 允许手动触发此工作流程
    6.   
    7. #   - cron: '*/15 * * * *'  # 每 15 分钟检查一次
    8. #    - cron: '0 0 * * *'
    9. # on: [workflow_dispatch] #点击触发
    10. # on: [push] # 触发工作流程的事件,这里设置为在push时触发
    11. jobs:
    12.   upload-attachments:
    13.     runs-on: ubuntu-latest
    14.     steps:
    15.       - name: Checkout code
    16.         uses: actions/checkout@v3
    17.       - name: Set up Python
    18.         uses: actions/setup-python@v4
    19.         with:
    20.           python-version: 3.x
    21.       - name: Install dependencies
    22.         run: pip install --upgrade pip
    23.       - name: Get new email attachments and check upload
    24.         env:
    25.           EMAIL_ADDRESS: ${{ secrets.EMAIL_ADDRESS }}
    26.           EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
    27.         run: python scripts/get_and_upload_emails.py
    28.       - name: Upload to WebDAV
    29.         uses: bxb100/action-upload@main
    30.         with:
    31.           provider: webdav
    32.           provider_options: |
    33.             endpoint=${{ secrets.WEBDAV_ENDPOINT }}
    34.             username=${{ secrets.WEBDAV_USERNAME }}
    35.             password=${{ secrets.WEBDAV_PASSWORD }}
    36.             root=/reading/
    37.           include: 'attachments/*'
    38.       - name: Delete local attachments after successful upload
    39.         if: success()
    40.         run: |
    41.           if [ -d "attachments" ]; then
    42.             rm -rf attachments/*
    43.           fi
    复制代码
4.运行Actions

设置环境变量存储前面的账号信息密码等等



这里直接测试可以直接点击运行,如果需要设置定时运行,更改YAML 文件的schedule位置即可。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81429

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表