diff --git a/conf.yml b/conf.yml index 412bf28..58a4e35 100644 --- a/conf.yml +++ b/conf.yml @@ -1,6 +1,23 @@ # 爬虫相关配置 spider_settings: - enable: true # 是否启用爬虫 - json_url: "https://blog.qyliu.top/friend.json" # 请填写对应格式json的地址,仅支持网络地址 - article_count: 5 # 请填写每个博客需要获取的最大文章数量 + enable: true # 是否启用爬虫 + json_url: "https://blog.qyliu.top/friend.json" # 请填写对应格式json的地址,仅支持网络地址 + article_count: 5 # 请填写每个博客需要获取的最大文章数量 + +# 邮箱推送功能配置 +email_push: + enabled: true # 是否启用邮箱推送功能 + to_email: recipient@example.com # 收件人邮箱地址 + subject: "今天的 RSS 订阅更新" # 邮件主题 + body_template: "rss_template.html" # 邮件正文的 HTML 模板文件 + +# SMTP 配置 +smtp: + email: your_email@example.com # 发件人邮箱地址 + server: smtp.example.com # SMTP 服务器地址 + port: 587 # SMTP 端口号 + use_tls: true # 是否使用 TLS 加密 + username: your_smtp_username # SMTP 用户名 + password: your_smtp_password # SMTP 密码 + diff --git a/dev_test/last_articles.json b/dev_test/last_articles.json new file mode 100644 index 0000000..e5ea8d0 --- /dev/null +++ b/dev_test/last_articles.json @@ -0,0 +1,44 @@ +{ + "articles": [ + { + "title": "东软软件园实习日记", + "author": "", + "link": "https://blog.qyliu.top/posts/13e6e155/", + "published": "2024-07-01 08:39", + "summary": "大学生累成狗,今天我终于理解了为什么这么说,好不容易结束了为期七天的实训课程,又要写实验报告加小组作业,好不容易完成了小组作业,这不,十五天实习又来了!烦!并且还要天天写学习日志?我直接当作日记写好不好!", + "content": "大学生累成狗,今天我终于理解了为什么这么说,好不容易结束了为期七天的实训课程,又要写实验报告加小组作业,好不容易完成了小组作业,这不,十五天实习又来了!烦!并且还要天天写学习日志?我直接当作日记写好不好!" + }, + { + "title": "Github Action实现友链状态检测", + "author": "", + "link": "https://blog.qyliu.top/posts/c2262998/", + "published": "2024-06-23 17:00", + "summary": "随着友情链接数量的增加,人工检测变得繁琐,我最初尝试通过爬取数据进行检测,但数据更新滞后。在群友安小歪的启发下,我采用了GitHub Action自动运行检测脚本,比较有效实现了友链有效性的自动化监测,同时将数据展示在了友情链接页面中,除此之外,原有的摸鱼页面也被我整合到了友链朋友圈页面中。", + "content": "随着友情链接数量的增加,人工检测变得繁琐,我最初尝试通过爬取数据进行检测,但数据更新滞后。在群友安小歪的启发下,我采用了GitHub Action自动运行检测脚本,比较有效实现了友链有效性的自动化监测,同时将数据展示在了友情链接页面中,除此之外,原有的摸鱼页面也被我整合到了友链朋友圈页面中。" + }, + { + "title": "安全跳转页面·插件版", + "author": "", + "link": "https://blog.qyliu.top/posts/1dfd1f41/", + "published": "2024-06-16 17:00", + "summary": "经过两个月的努力,我终于找到了完美的外链跳转解决方案!初始版本使用外置JS存在诸多bug,如图片灯箱、友链和站内跳转链接等问题。经过一段时间的学习和代码调整,我取得了阶段性进展,现在能够实现各种所需功能。最近,我在hexo-external-link插件的基础上进行了底层重构,最终实现了真正的插件版外链替换,不再依赖JS,功能更加完善,且使用更加方便!", + "content": "经过两个月的努力,我终于找到了完美的外链跳转解决方案!初始版本使用外置JS存在诸多bug,如图片灯箱、友链和站内跳转链接等问题。经过一段时间的学习和代码调整,我取得了阶段性进展,现在能够实现各种所需功能。最近,我在hexo-external-link插件的基础上进行了底层重构,最终实现了真正的插件版外链替换,不再依赖JS,功能更加完善,且使用更加方便!" + }, + { + "title": "Alist宝塔部署及其美化", + "author": "", + "link": "https://blog.qyliu.top/posts/a84f5e47/", + "published": "2024-06-04 11:24", + "summary": "Alist 是一个轻量级的目录列表程序,可以用于管理文件索引。将 Alist 部署在宝塔面板中,并进行美化,可以提升用户体验。在本教程中,将介绍我的部署经过,并给出相应的美化代码。", + "content": "Alist 是一个轻量级的目录列表程序,可以用于管理文件索引。将 Alist 部署在宝塔面板中,并进行美化,可以提升用户体验。在本教程中,将介绍我的部署经过,并给出相应的美化代码。" + }, + { + "title": "计算机网络期末总复习", + "author": "", + "link": "https://blog.qyliu.top/posts/8dfa25e1/", + "published": "2024-05-30 16:48", + "summary": "应该注意到标题的改变了吧,本来是一章节一章节的复习,奈何我突然发现,老师画的重点和考研的重点并不是很符合,没办法咯,于是我决定将计算机网络考研复习部分的内容暂时搁置,后面逐步更新,反正一定是会更新的,因为408必考呀!然后将期末复习内容先整理出来,按照老师的考点进行复习。", + "content": "应该注意到标题的改变了吧,本来是一章节一章节的复习,奈何我突然发现,老师画的重点和考研的重点并不是很符合,没办法咯,于是我决定将计算机网络考研复习部分的内容暂时搁置,后面逐步更新,反正一定是会更新的,因为408必考呀!然后将期末复习内容先整理出来,按照老师的考点进行复习。" + } + ] +} \ No newline at end of file diff --git a/dev_test/main.ipynb b/dev_test/main.ipynb index 2f20836..f2d672b 100644 --- a/dev_test/main.ipynb +++ b/dev_test/main.ipynb @@ -195,6 +195,66 @@ "print(f\"feedparser 版本: {feedparser.__version__}\")\n" ] }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# 将系统路径设置为../\n", + "import sys\n", + "sys.path.append(\"../\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"emails\": [\n", + " \"3162475700@qq.com\"\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "from rss_subscribe.push_article_update import extract_emails_from_issues\n", + "import json\n", + "\n", + "api_url = \"https://api.github.com/repos/willow-god/Friend-Circle-Lite/issues\"\n", + "emails = extract_emails_from_issues(api_url)\n", + "print(json.dumps(emails, indent=2))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "从 https://blog.qyliu.top/ 获取到 5 篇文章,其中 0 篇为新文章\n", + "None\n" + ] + } + ], + "source": [ + "from rss_subscribe.push_article_update import get_latest_articles_from_link\n", + "import json\n", + "\n", + "url = \"https://blog.qyliu.top/\"\n", + "articles = get_latest_articles_from_link(url, last_articles_path=\"../rss_subscribe/last_articles.json\")\n", + "print(articles)" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/friend_circle_lite/__pycache__/__init__.cpython-311.pyc b/friend_circle_lite/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..d0d0852 Binary files /dev/null and b/friend_circle_lite/__pycache__/__init__.cpython-311.pyc differ diff --git a/friend_circle_lite/__pycache__/get_info.cpython-311.pyc b/friend_circle_lite/__pycache__/get_info.cpython-311.pyc new file mode 100644 index 0000000..0edce97 Binary files /dev/null and b/friend_circle_lite/__pycache__/get_info.cpython-311.pyc differ diff --git a/push_rss_update/__init__.py b/push_rss_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/push_rss_update/send_email.py b/push_rss_update/send_email.py new file mode 100644 index 0000000..e69de29 diff --git a/rss_subscribe/__init__.py b/rss_subscribe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rss_subscribe/__pycache__/__init__.cpython-311.pyc b/rss_subscribe/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..188f927 Binary files /dev/null and b/rss_subscribe/__pycache__/__init__.cpython-311.pyc differ diff --git a/rss_subscribe/__pycache__/push_article_update.cpython-311.pyc b/rss_subscribe/__pycache__/push_article_update.cpython-311.pyc new file mode 100644 index 0000000..600191d Binary files /dev/null and b/rss_subscribe/__pycache__/push_article_update.cpython-311.pyc differ diff --git a/rss_subscribe/last_articles.json b/rss_subscribe/last_articles.json new file mode 100644 index 0000000..e5ea8d0 --- /dev/null +++ b/rss_subscribe/last_articles.json @@ -0,0 +1,44 @@ +{ + "articles": [ + { + "title": "东软软件园实习日记", + "author": "", + "link": "https://blog.qyliu.top/posts/13e6e155/", + "published": "2024-07-01 08:39", + "summary": "大学生累成狗,今天我终于理解了为什么这么说,好不容易结束了为期七天的实训课程,又要写实验报告加小组作业,好不容易完成了小组作业,这不,十五天实习又来了!烦!并且还要天天写学习日志?我直接当作日记写好不好!", + "content": "大学生累成狗,今天我终于理解了为什么这么说,好不容易结束了为期七天的实训课程,又要写实验报告加小组作业,好不容易完成了小组作业,这不,十五天实习又来了!烦!并且还要天天写学习日志?我直接当作日记写好不好!" + }, + { + "title": "Github Action实现友链状态检测", + "author": "", + "link": "https://blog.qyliu.top/posts/c2262998/", + "published": "2024-06-23 17:00", + "summary": "随着友情链接数量的增加,人工检测变得繁琐,我最初尝试通过爬取数据进行检测,但数据更新滞后。在群友安小歪的启发下,我采用了GitHub Action自动运行检测脚本,比较有效实现了友链有效性的自动化监测,同时将数据展示在了友情链接页面中,除此之外,原有的摸鱼页面也被我整合到了友链朋友圈页面中。", + "content": "随着友情链接数量的增加,人工检测变得繁琐,我最初尝试通过爬取数据进行检测,但数据更新滞后。在群友安小歪的启发下,我采用了GitHub Action自动运行检测脚本,比较有效实现了友链有效性的自动化监测,同时将数据展示在了友情链接页面中,除此之外,原有的摸鱼页面也被我整合到了友链朋友圈页面中。" + }, + { + "title": "安全跳转页面·插件版", + "author": "", + "link": "https://blog.qyliu.top/posts/1dfd1f41/", + "published": "2024-06-16 17:00", + "summary": "经过两个月的努力,我终于找到了完美的外链跳转解决方案!初始版本使用外置JS存在诸多bug,如图片灯箱、友链和站内跳转链接等问题。经过一段时间的学习和代码调整,我取得了阶段性进展,现在能够实现各种所需功能。最近,我在hexo-external-link插件的基础上进行了底层重构,最终实现了真正的插件版外链替换,不再依赖JS,功能更加完善,且使用更加方便!", + "content": "经过两个月的努力,我终于找到了完美的外链跳转解决方案!初始版本使用外置JS存在诸多bug,如图片灯箱、友链和站内跳转链接等问题。经过一段时间的学习和代码调整,我取得了阶段性进展,现在能够实现各种所需功能。最近,我在hexo-external-link插件的基础上进行了底层重构,最终实现了真正的插件版外链替换,不再依赖JS,功能更加完善,且使用更加方便!" + }, + { + "title": "Alist宝塔部署及其美化", + "author": "", + "link": "https://blog.qyliu.top/posts/a84f5e47/", + "published": "2024-06-04 11:24", + "summary": "Alist 是一个轻量级的目录列表程序,可以用于管理文件索引。将 Alist 部署在宝塔面板中,并进行美化,可以提升用户体验。在本教程中,将介绍我的部署经过,并给出相应的美化代码。", + "content": "Alist 是一个轻量级的目录列表程序,可以用于管理文件索引。将 Alist 部署在宝塔面板中,并进行美化,可以提升用户体验。在本教程中,将介绍我的部署经过,并给出相应的美化代码。" + }, + { + "title": "计算机网络期末总复习", + "author": "", + "link": "https://blog.qyliu.top/posts/8dfa25e1/", + "published": "2024-05-30 16:48", + "summary": "应该注意到标题的改变了吧,本来是一章节一章节的复习,奈何我突然发现,老师画的重点和考研的重点并不是很符合,没办法咯,于是我决定将计算机网络考研复习部分的内容暂时搁置,后面逐步更新,反正一定是会更新的,因为408必考呀!然后将期末复习内容先整理出来,按照老师的考点进行复习。", + "content": "应该注意到标题的改变了吧,本来是一章节一章节的复习,奈何我突然发现,老师画的重点和考研的重点并不是很符合,没办法咯,于是我决定将计算机网络考研复习部分的内容暂时搁置,后面逐步更新,反正一定是会更新的,因为408必考呀!然后将期末复习内容先整理出来,按照老师的考点进行复习。" + } + ] +} \ No newline at end of file diff --git a/rss_subscribe/push_article_update.py b/rss_subscribe/push_article_update.py new file mode 100644 index 0000000..0873f0f --- /dev/null +++ b/rss_subscribe/push_article_update.py @@ -0,0 +1,96 @@ +import requests +import re +from friend_circle_lite.get_info import check_feed, parse_feed +import json +import os + +# 标准化的请求头 +headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' +} + +def extract_emails_from_issues(api_url): + """ + 从GitHub issues API中提取以[e-mail]开头的title中的邮箱地址。 + + 参数: + api_url (str): GitHub issues API的URL。 + + 返回: + dict: 包含所有提取的邮箱地址的字典。 + { + "emails": [ + "3162475700@qq.com" + ] + } + """ + try: + response = requests.get(api_url, headers=headers) + response.raise_for_status() + issues = response.json() + except Exception as e: + print(f"无法获取该链接:{api_url}\n出现的问题为:{e}") + return None + + email_pattern = re.compile(r'^\[e-mail\](.+)$') + emails = [] + + for issue in issues: + title = issue.get("title", "") + match = email_pattern.match(title) + if match: + email = match.group(1).strip() + emails.append(email) + + return {"emails": emails} + +def get_latest_articles_from_link(url, count=5, last_articles_path="./rss_subscribe/last_articles.json"): + """ + 从指定链接获取最新的文章数据并与本地存储的上次的文章数据进行对比。 + + 参数: + url (str): 用于获取文章数据的链接。 + count (int): 获取文章数的最大数。如果小于则全部获取,如果文章数大于则只取前 count 篇文章。 + + 返回: + list: 更新的文章列表,如果没有更新的文章则返回 None。 + """ + # 本地存储上次文章数据的文件 + local_file = last_articles_path + + # 检查和解析 feed + session = requests.Session() + feed_type, feed_url = check_feed(url, session) + if feed_type == 'none': + print(f"无法访问 {url} 的 feed") + return None + + # 获取最新的文章数据 + latest_data = parse_feed(feed_url, session ,count) + latest_articles = latest_data['articles'] + + # 读取本地存储的上次的文章数据 + if os.path.exists(local_file): + with open(local_file, 'r', encoding='utf-8') as file: + last_data = json.load(file) + else: + last_data = {'articles': []} + + last_articles = last_data['articles'] + + # 找到更新的文章 + updated_articles = [] + last_titles = {article['link'] for article in last_articles} + + for article in latest_articles: + if article['link'] not in last_titles: + updated_articles.append(article) + + print(f"从 {url} 获取到 {len(latest_articles)} 篇文章,其中 {len(updated_articles)} 篇为新文章") + + # 更新本地存储的文章数据 + with open(local_file, 'w', encoding='utf-8') as file: + json.dump({'articles': latest_articles}, file, ensure_ascii=False, indent=4) + + # 如果有更新的文章,返回这些文章,否则返回 None + return updated_articles if updated_articles else None \ No newline at end of file