🎈优化RSS推送的内容 (#259)

* 🧪 test(tests): 添加了RSS的单元测试

* 🎈 perf(rss and test): 优化了RSS部分源标题正文重复的问题

部分RSS源(RSSHub的Twitter)存在正文当标题用的情况,导致推送的时候呈现为两段重复的文字,现通过Jaccard相似系数来判断是否需要去重

* Update nonebot_bison/platform/rss.py

Co-authored-by: AzideCupric <57004769+AzideCupric@users.noreply.github.com>

* Update nonebot_bison/platform/rss.py

Co-authored-by: AzideCupric <57004769+AzideCupric@users.noreply.github.com>

* 🐞 fix(platform/rss): 修复了漏掉相似文本在后端位置的问题

* 🐞 fix(rss): 修正一些feed无法正确识别时间的bug

一些feed时间只有updated标签或者没有,原先的代码只能解析用published标签的时间

felinae98#275

* 🎈 perf(rss): 更改字符串相似度比较方法

从Jaccard相似系数比较相似度改为通过最长公共子序列来比较

* 🦄 refactor(rss): 重构实现字符串相似度比较的方法

使用标准库difflib代替原先手搓的LCS

* Update nonebot_bison/utils/__init__.py

Co-authored-by: felinae98 <731499577@qq.com>

* Update nonebot_bison/platform/rss.py

* Update nonebot_bison/platform/rss.py

---------

Co-authored-by: AzideCupric <57004769+AzideCupric@users.noreply.github.com>
Co-authored-by: felinae98 <731499577@qq.com>
This commit is contained in:
UKM 2023-07-18 11:54:49 +08:00 committed by GitHub
parent 1db15ffc75
commit 9838e25bad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 843 additions and 5 deletions

View File

@ -1,4 +1,5 @@
import calendar import calendar
import time
from typing import Any, Optional from typing import Any, Optional
import feedparser import feedparser
@ -7,10 +8,17 @@ from httpx import AsyncClient
from ..post import Post from ..post import Post
from ..types import RawPost, Target from ..types import RawPost, Target
from ..utils import scheduler from ..utils import SchedulerConfig, text_similarity
from .platform import NewMessage from .platform import NewMessage
class RssSchedConf(SchedulerConfig):
name = "rss"
schedule_type = "interval"
schedule_setting = {"seconds": 30}
class Rss(NewMessage): class Rss(NewMessage):
categories = {} categories = {}
@ -19,7 +27,7 @@ class Rss(NewMessage):
name = "Rss" name = "Rss"
enabled = True enabled = True
is_common = True is_common = True
scheduler = scheduler("interval", {"seconds": 30}) scheduler = RssSchedConf
has_target = True has_target = True
@classmethod @classmethod
@ -31,7 +39,12 @@ class Rss(NewMessage):
return feed["feed"]["title"] return feed["feed"]["title"]
def get_date(self, post: RawPost) -> int: def get_date(self, post: RawPost) -> int:
return calendar.timegm(post.published_parsed) if hasattr(post, "published_parsed"):
return calendar.timegm(post.published_parsed)
elif hasattr(post, "updated_parsed"):
return calendar.timegm(post.updated_parsed)
else:
return calendar.timegm(time.gmtime())
def get_id(self, post: RawPost) -> Any: def get_id(self, post: RawPost) -> Any:
return post.id return post.id
@ -45,9 +58,17 @@ class Rss(NewMessage):
return feed.entries return feed.entries
async def parse(self, raw_post: RawPost) -> Post: async def parse(self, raw_post: RawPost) -> Post:
text = raw_post.get("title", "") + "\n" if raw_post.get("title") else "" title = raw_post.get("title", "")
soup = bs(raw_post.description, "html.parser") soup = bs(raw_post.description, "html.parser")
text += soup.text.strip() desc = soup.text.strip()
if not title or not desc:
text = title or desc
else:
if text_similarity(desc, title) > 0.8:
text = desc if len(desc) > len(title) else title
else:
text = f"{title}\n\n{desc}"
pics = list(map(lambda x: x.attrs["src"], soup("img"))) pics = list(map(lambda x: x.attrs["src"], soup("img")))
if raw_post.get("media_content"): if raw_post.get("media_content"):
for media in raw_post["media_content"]: for media in raw_post["media_content"]:

View File

@ -1,3 +1,4 @@
import difflib
import re import re
import sys import sys
from typing import Union from typing import Union
@ -109,3 +110,9 @@ def jaccard_text_similarity(str1: str, str2: str) -> float:
set1 = set(str1) set1 = set(str1)
set2 = set(str2) set2 = set(str2)
return len(set1 & set2) / len(set1 | set2) return len(set1 & set2) / len(set1 | set2)
def text_similarity(str1, str2) -> float:
matcher = difflib.SequenceMatcher(None, str1, str2)
t = sum(temp.size for temp in matcher.get_matching_blocks())
return t / min(len(str1), len(str2))

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
<id>tag:github.com,2008:https://github.com/R3nzTheCodeGOD/R3nzSkin/releases</id>
<link type="text/html" rel="alternate" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases"/>
<link type="application/atom+xml" rel="self" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases.atom"/>
<title>Release notes from R3nzSkin</title>
<updated>2023-06-29T00:46:26+08:00</updated>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.8</id>
<updated>2023-06-25T22:19:30+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.8"/>
<title>R3nzSkin</title>
<content>No content.</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.7</id>
<updated>2023-06-25T08:38:52+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.7"/>
<title>R3nzSkin</title>
<content>No content.</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.6</id>
<updated>2023-06-06T23:05:38+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.6"/>
<title>R3nzSkin</title>
<content type="html">&lt;p&gt;Added update available information&lt;/p&gt;</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.5</id>
<updated>2023-06-03T19:53:30+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.5"/>
<title>R3nzSkin</title>
<content type="html">&lt;p&gt;Added &quot;Hide to tray&quot; option to the injector&lt;/p&gt;</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
</feed>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
<id>tag:github.com,2008:https://github.com/R3nzTheCodeGOD/R3nzSkin/releases</id>
<link type="text/html" rel="alternate" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases"/>
<link type="application/atom+xml" rel="self" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases.atom"/>
<title>Release notes from R3nzSkin</title>
<updated>2023-06-29T00:46:26+08:00</updated>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.9</id>
<updated>2023-06-29T00:46:57+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.9"/>
<title>R3nzSkin</title>
<content>No content.</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.8</id>
<updated>2023-06-25T22:19:30+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.8"/>
<title>R3nzSkin</title>
<content>No content.</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.7</id>
<updated>2023-06-25T08:38:52+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.7"/>
<title>R3nzSkin</title>
<content>No content.</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.6</id>
<updated>2023-06-06T23:05:38+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.6"/>
<title>R3nzSkin</title>
<content type="html">&lt;p&gt;Added update available information&lt;/p&gt;</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
<entry>
<id>tag:github.com,2008:Repository/410126695/v3.0.5</id>
<updated>2023-06-03T19:53:30+08:00</updated>
<link rel="alternate" type="text/html" href="https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.5"/>
<title>R3nzSkin</title>
<content type="html">&lt;p&gt;Added &quot;Hide to tray&quot; option to the injector&lt;/p&gt;</content>
<author>
<name>hotline1337</name>
</author>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/32012110?s=60&amp;v=4"/>
</entry>
</feed>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

57
tests/platforms/static/rss-top5-new.xml vendored Normal file
View File

@ -0,0 +1,57 @@
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>
<![CDATA[ Hot Wallpapers - wallhaven.cc ]]>
</title>
<link>https://wallhaven.cc/hot</link>
<atom:link href="https://rsshub.app/wallhaven/hot?limit=5" rel="self" type="application/rss+xml"/>
<description>
<![CDATA[ Hot Wallpapers - wallhaven.cc - Made with love by RSSHub(https://github.com/DIYgod/RSSHub) ]]>
</description>
<generator>RSSHub</generator>
<webMaster>i@diygod.me (DIYgod)</webMaster>
<language>zh-cn</language>
<lastBuildDate>Tue, 04 Jul 2023 06:23:09 GMT</lastBuildDate>
<ttl>120</ttl>
<item>
<title>
<![CDATA[ 85rjej.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/85/85rjej.jpg" src="https://th.wallhaven.cc/small/85/85rjej.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/85rjej</guid>
<link>https://wallhaven.cc/w/85rjej</link>
</item>
<item>
<title>
<![CDATA[ 2y322g.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/2y/2y322g.jpg" src="https://th.wallhaven.cc/small/2y/2y322g.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/2y322g</guid>
<link>https://wallhaven.cc/w/2y322g</link>
</item>
<item>
<title>
<![CDATA[ rrj6qw.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/rr/rrj6qw.jpg" src="https://th.wallhaven.cc/small/rr/rrj6qw.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/rrj6qw</guid>
<link>https://wallhaven.cc/w/rrj6qw</link>
</item>
<item>
<title>
<![CDATA[ 9drg1d.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/9d/9drg1d.jpg" src="https://th.wallhaven.cc/small/9d/9drg1d.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/9drg1d</guid>
<link>https://wallhaven.cc/w/9drg1d</link>
</item>
</channel>
</rss>

57
tests/platforms/static/rss-top5-old.xml vendored Normal file
View File

@ -0,0 +1,57 @@
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>
<![CDATA[ Hot Wallpapers - wallhaven.cc ]]>
</title>
<link>https://wallhaven.cc/hot</link>
<atom:link href="https://rsshub.app/wallhaven/hot?limit=5" rel="self" type="application/rss+xml"/>
<description>
<![CDATA[ Hot Wallpapers - wallhaven.cc - Made with love by RSSHub(https://github.com/DIYgod/RSSHub) ]]>
</description>
<generator>RSSHub</generator>
<webMaster>i@diygod.me (DIYgod)</webMaster>
<language>zh-cn</language>
<lastBuildDate>Tue, 04 Jul 2023 06:23:09 GMT</lastBuildDate>
<ttl>120</ttl>
<item>
<title>
<![CDATA[ 2y322g.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/2y/2y322g.jpg" src="https://th.wallhaven.cc/small/2y/2y322g.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/2y322g</guid>
<link>https://wallhaven.cc/w/2y322g</link>
</item>
<item>
<title>
<![CDATA[ rrj6qw.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/rr/rrj6qw.jpg" src="https://th.wallhaven.cc/small/rr/rrj6qw.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/rrj6qw</guid>
<link>https://wallhaven.cc/w/rrj6qw</link>
</item>
<item>
<title>
<![CDATA[ 9drg1d.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/9d/9drg1d.jpg" src="https://th.wallhaven.cc/small/9d/9drg1d.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/9drg1d</guid>
<link>https://wallhaven.cc/w/9drg1d</link>
</item>
<item>
<title>
<![CDATA[ vq5eem.jpg ]]>
</title>
<description>
<![CDATA[ <img alt="loading" class="lazyload" data-src="https://th.wallhaven.cc/small/vq/vq5eem.jpg" src="https://th.wallhaven.cc/small/vq/vq5eem.jpg" referrerpolicy="no-referrer"> ]]>
</description>
<guid isPermaLink="false">https://wallhaven.cc/w/vq5eem</guid>
<link>https://wallhaven.cc/w/vq5eem</link>
</item>
</channel>
</rss>

View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"
>
<channel>
<title><![CDATA[Twitter @アークナイツ公式]]></title>
<link>https://twitter.com/ArknightsStaff</link>
<atom:link href="https://rsshub.app/twitter/user/ArknightsStaff" rel="self" type="application/rss+xml" />
<description><![CDATA[YostarとHyperGryphが贈るスマートフォン向け新作ゲーム「#アークナイツ」の公式アカウントです。お問い合わせ⇛ https://t.co/wbdyNLoUaf
TVアニメ アークナイツ【冬隠帰路/PERISH IN FROST】鋭意制作中 - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)]]></description>
<generator>RSSHub</generator>
<webMaster>i@diygod.me (DIYgod)</webMaster>
<language>zh-cn</language>
<image>
<url>https://pbs.twimg.com/profile_images/1260264885771890689/2KcWaF8a.jpg</url>
<title><![CDATA[Twitter @アークナイツ公式]]></title>
<link>https://twitter.com/ArknightsStaff</link>
</image>
<lastBuildDate>Thu, 18 May 2023 11:56:00 GMT</lastBuildDate>
<ttl>1</ttl>
<item>
<title><![CDATA[【#統合戦略】 新テーマ「ミヅキと紺碧の樹」は5月23日16:00より開催 本日は新要素及びシステムの変更点を一部ご紹介します! 詳細は添付の画像をご確認...]]></title>
<description><![CDATA[【#統合戦略】<br>新テーマ「ミヅキと紺碧の樹」は5月23日16:00より開催<br><br>本日は新要素及びシステムの変更点を一部ご紹介します!<br>詳細は添付の画像をご確認ください。<br><br>#アークナイツ https://t.co/o2uULEpA5x<br><img style="" src="https://pbs.twimg.com/media/FwYdt9XaIAEU4gH?format=jpg&amp;name=orig" referrerpolicy="no-referrer"><br><img style="" src="https://pbs.twimg.com/media/FwYdt7LaEAIPntw?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Thu, 18 May 2023 04:00:10 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1659046193090134016</guid>
<link>https://twitter.com/ArknightsStaff/status/1659046193090134016</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【リマインド】 下記コーデの販売期限は、5月18日3:59までです。ご注意ください。 ・リー · お目を拝借 ・アイリス · 鏡からの来訪者 ・カゼマル · 軽...]]></title>
<description><![CDATA[【リマインド】<br>下記コーデの販売期限は、5月18日3:59までです。ご注意ください。<br><br>・リー · お目を拝借<br>・アイリス · 鏡からの来訪者<br>・カゼマル · 軽やかな幻影<br><br>#アークナイツ]]></description>
<pubDate>Wed, 17 May 2023 10:00:23 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658774457123733505</guid>
<link>https://twitter.com/ArknightsStaff/status/1658774457123733505</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【コーデ紹介】 ハニーベリー専用、EPOQUEサブブランドPasseシリーズ「荒野のあとに」。 学生時代の彼女が、臥せる母に語った想像上の未来の自分。 歳に見合わぬ...]]></title>
<description><![CDATA[【コーデ紹介】<br>ハニーベリー専用、EPOQUEサブブランドPasseシリーズ「荒野のあとに」。<br>学生時代の彼女が、臥せる母に語った想像上の未来の自分。<br><br>歳に見合わぬ少女の夢には、残酷な現実が影を落とす。<br>それでも母は娘の心になおも勇気が息づくのを嬉しく思っていた。<br><br>近日入荷予定!<br>#アークナイツ https://t.co/R8eGG1b9r3<br><img style="" src="https://pbs.twimg.com/media/FwT9U0dagAAr_P6?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Wed, 17 May 2023 07:00:10 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658729101069541377</guid>
<link>https://twitter.com/ArknightsStaff/status/1658729101069541377</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【オペレーター紹介】 カニパラートCV: #佐藤元) サーミ地方からやってきたヴァルポの少年。元救助隊員。 外の大地の物事に興味津々で、部族の医療環境を向上...]]></title>
<description><![CDATA[【オペレーター紹介】<br>カニパラートCV: #佐藤元)<br>サーミ地方からやってきたヴァルポの少年。元救助隊員。<br>外の大地の物事に興味津々で、部族の医療環境を向上させるという目的もあってか、特に機械系の扱いには積極的に取り組んでいる。<br><br>「救助開始!」<br><br>#アークナイツ https://t.co/4lmdg5guMy<br><img style="" src="https://pbs.twimg.com/media/FwTUH49aIAAt0Ww?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Wed, 17 May 2023 04:00:09 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658683798253608960</guid>
<link>https://twitter.com/ArknightsStaff/status/1658683798253608960</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【コーデ紹介】 ソラ専用、EPOQUEシリーズ「メロディオーソ」。 シラクーザのミラノ劇場で、ソラが舞台に立つ際に着る衣装。 普段はポップスターだからといって...]]></title>
<description><![CDATA[【コーデ紹介】<br>ソラ専用、EPOQUEシリーズ「メロディオーソ」。<br>シラクーザのミラノ劇場で、ソラが舞台に立つ際に着る衣装。<br><br>普段はポップスターだからといって、由緒正しき異国のクラシックをマスターできないわけではない。<br><br>近日入荷予定!<br>#アークナイツ https://t.co/2zGzighNxk<br><img style="" src="https://pbs.twimg.com/media/FwOzuPEacAA02zd?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Tue, 16 May 2023 07:00:06 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658366698179956736</guid>
<link>https://twitter.com/ArknightsStaff/status/1658366698179956736</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【オペレーター紹介】 クォーツCV: #逢田梨香子) クルビア出身の傭兵。 常日頃から懐中時計を持ち歩き、時間厳守を信条とする彼女は、規則正しい生活を自分に...]]></title>
<description><![CDATA[【オペレーター紹介】<br>クォーツCV: #逢田梨香子)<br>クルビア出身の傭兵。<br>常日頃から懐中時計を持ち歩き、時間厳守を信条とする彼女は、規則正しい生活を自分に課している。しかし、ロドスに来てからは少し心にゆとりが生まれたようだ。<br><br>「力よりも、攻撃のタイミングが重要だ。」<br><br>#アークナイツ https://t.co/cX8kLz8oQh<br><img style="" src="https://pbs.twimg.com/media/FwOYRGQaQAAdGOS?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Tue, 16 May 2023 05:00:09 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658336510670954497</guid>
<link>https://twitter.com/ArknightsStaff/status/1658336510670954497</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【アップデートのお知らせ】 下記日時にメンテナンスとアップデートをあわせて実施します。 ▼実施期間 5月23日10:00 ~ 16:00 ▼詳細内容 https://arknigh...]]></title>
<description><![CDATA[【アップデートのお知らせ】<br>下記日時にメンテナンスとアップデートをあわせて実施します。<br><br>▼実施期間<br>5月23日10:00 ~ 16:00<br><br>▼詳細内容<br>https://arknights.jp/news/328<br><br>アプリの更新が必要なため、あらかじめデータ連携をお済ませいただきますようお願いします。<br><br>#アークナイツ https://t.co/XEqAzM0XOu<br><img style="" src="https://pbs.twimg.com/media/FwN9RmZacAEQ599?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Tue, 16 May 2023 03:02:13 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658306831960412166</guid>
<link>https://twitter.com/ArknightsStaff/status/1658306831960412166</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
</channel>
</rss>

View File

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"
>
<channel>
<title><![CDATA[Twitter @アークナイツ公式]]></title>
<link>https://twitter.com/ArknightsStaff</link>
<atom:link href="https://rsshub.app/twitter/user/ArknightsStaff" rel="self" type="application/rss+xml" />
<description><![CDATA[YostarとHyperGryphが贈るスマートフォン向け新作ゲーム「#アークナイツ」の公式アカウントです。お問い合わせ⇛ https://t.co/wbdyNLoUaf
TVアニメ アークナイツ【冬隠帰路/PERISH IN FROST】鋭意制作中 - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)]]></description>
<generator>RSSHub</generator>
<webMaster>i@diygod.me (DIYgod)</webMaster>
<language>zh-cn</language>
<image>
<url>https://pbs.twimg.com/profile_images/1260264885771890689/2KcWaF8a.jpg</url>
<title><![CDATA[Twitter @アークナイツ公式]]></title>
<link>https://twitter.com/ArknightsStaff</link>
</image>
<lastBuildDate>Thu, 18 May 2023 11:56:00 GMT</lastBuildDate>
<ttl>1</ttl>
<item>
<title><![CDATA[【#統合戦略】 引き続き新テーマ「ミヅキと紺碧の樹」の新要素及びシステムの変更点を一部ご紹介します! 今回は「灯火」、「ダイス」、「記号認識」...]]></title>
<description><![CDATA[【#統合戦略】 <br>引き続き新テーマ「ミヅキと紺碧の樹」の新要素及びシステムの変更点を一部ご紹介します! <br><br>今回は「灯火」、「ダイス」、「記号認識」、「鍵」についてです。<br>詳細は添付の画像をご確認ください。<br><br>#アークナイツ https://t.co/ARmptV0Zvu<br><img style="" src="https://pbs.twimg.com/media/FwZG9YAacAIXDw2?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Thu, 18 May 2023 07:00:21 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1659091539023282178</guid>
<link>https://twitter.com/ArknightsStaff/status/1659091539023282178</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【#統合戦略】 新テーマ「ミヅキと紺碧の樹」は5月23日16:00より開催 本日は新要素及びシステムの変更点を一部ご紹介します! 詳細は添付の画像をご確認...]]></title>
<description><![CDATA[【#統合戦略】<br>新テーマ「ミヅキと紺碧の樹」は5月23日16:00より開催<br><br>本日は新要素及びシステムの変更点を一部ご紹介します!<br>詳細は添付の画像をご確認ください。<br><br>#アークナイツ https://t.co/o2uULEpA5x<br><img style="" src="https://pbs.twimg.com/media/FwYdt9XaIAEU4gH?format=jpg&amp;name=orig" referrerpolicy="no-referrer"><br><img style="" src="https://pbs.twimg.com/media/FwYdt7LaEAIPntw?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Thu, 18 May 2023 04:00:10 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1659046193090134016</guid>
<link>https://twitter.com/ArknightsStaff/status/1659046193090134016</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【リマインド】 下記コーデの販売期限は、5月18日3:59までです。ご注意ください。 ・リー · お目を拝借 ・アイリス · 鏡からの来訪者 ・カゼマル · 軽...]]></title>
<description><![CDATA[【リマインド】<br>下記コーデの販売期限は、5月18日3:59までです。ご注意ください。<br><br>・リー · お目を拝借<br>・アイリス · 鏡からの来訪者<br>・カゼマル · 軽やかな幻影<br><br>#アークナイツ]]></description>
<pubDate>Wed, 17 May 2023 10:00:23 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658774457123733505</guid>
<link>https://twitter.com/ArknightsStaff/status/1658774457123733505</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【コーデ紹介】 ハニーベリー専用、EPOQUEサブブランドPasseシリーズ「荒野のあとに」。 学生時代の彼女が、臥せる母に語った想像上の未来の自分。 歳に見合わぬ...]]></title>
<description><![CDATA[【コーデ紹介】<br>ハニーベリー専用、EPOQUEサブブランドPasseシリーズ「荒野のあとに」。<br>学生時代の彼女が、臥せる母に語った想像上の未来の自分。<br><br>歳に見合わぬ少女の夢には、残酷な現実が影を落とす。<br>それでも母は娘の心になおも勇気が息づくのを嬉しく思っていた。<br><br>近日入荷予定!<br>#アークナイツ https://t.co/R8eGG1b9r3<br><img style="" src="https://pbs.twimg.com/media/FwT9U0dagAAr_P6?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Wed, 17 May 2023 07:00:10 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658729101069541377</guid>
<link>https://twitter.com/ArknightsStaff/status/1658729101069541377</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【オペレーター紹介】 カニパラートCV: #佐藤元) サーミ地方からやってきたヴァルポの少年。元救助隊員。 外の大地の物事に興味津々で、部族の医療環境を向上...]]></title>
<description><![CDATA[【オペレーター紹介】<br>カニパラートCV: #佐藤元)<br>サーミ地方からやってきたヴァルポの少年。元救助隊員。<br>外の大地の物事に興味津々で、部族の医療環境を向上させるという目的もあってか、特に機械系の扱いには積極的に取り組んでいる。<br><br>「救助開始!」<br><br>#アークナイツ https://t.co/4lmdg5guMy<br><img style="" src="https://pbs.twimg.com/media/FwTUH49aIAAt0Ww?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Wed, 17 May 2023 04:00:09 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658683798253608960</guid>
<link>https://twitter.com/ArknightsStaff/status/1658683798253608960</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【コーデ紹介】 ソラ専用、EPOQUEシリーズ「メロディオーソ」。 シラクーザのミラノ劇場で、ソラが舞台に立つ際に着る衣装。 普段はポップスターだからといって...]]></title>
<description><![CDATA[【コーデ紹介】<br>ソラ専用、EPOQUEシリーズ「メロディオーソ」。<br>シラクーザのミラノ劇場で、ソラが舞台に立つ際に着る衣装。<br><br>普段はポップスターだからといって、由緒正しき異国のクラシックをマスターできないわけではない。<br><br>近日入荷予定!<br>#アークナイツ https://t.co/2zGzighNxk<br><img style="" src="https://pbs.twimg.com/media/FwOzuPEacAA02zd?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Tue, 16 May 2023 07:00:06 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658366698179956736</guid>
<link>https://twitter.com/ArknightsStaff/status/1658366698179956736</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【オペレーター紹介】 クォーツCV: #逢田梨香子) クルビア出身の傭兵。 常日頃から懐中時計を持ち歩き、時間厳守を信条とする彼女は、規則正しい生活を自分に...]]></title>
<description><![CDATA[【オペレーター紹介】<br>クォーツCV: #逢田梨香子)<br>クルビア出身の傭兵。<br>常日頃から懐中時計を持ち歩き、時間厳守を信条とする彼女は、規則正しい生活を自分に課している。しかし、ロドスに来てからは少し心にゆとりが生まれたようだ。<br><br>「力よりも、攻撃のタイミングが重要だ。」<br><br>#アークナイツ https://t.co/cX8kLz8oQh<br><img style="" src="https://pbs.twimg.com/media/FwOYRGQaQAAdGOS?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Tue, 16 May 2023 05:00:09 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658336510670954497</guid>
<link>https://twitter.com/ArknightsStaff/status/1658336510670954497</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
<item>
<title><![CDATA[【アップデートのお知らせ】 下記日時にメンテナンスとアップデートをあわせて実施します。 ▼実施期間 5月23日10:00 ~ 16:00 ▼詳細内容 https://arknigh...]]></title>
<description><![CDATA[【アップデートのお知らせ】<br>下記日時にメンテナンスとアップデートをあわせて実施します。<br><br>▼実施期間<br>5月23日10:00 ~ 16:00<br><br>▼詳細内容<br>https://arknights.jp/news/328<br><br>アプリの更新が必要なため、あらかじめデータ連携をお済ませいただきますようお願いします。<br><br>#アークナイツ https://t.co/XEqAzM0XOu<br><img style="" src="https://pbs.twimg.com/media/FwN9RmZacAEQ599?format=jpg&amp;name=orig" referrerpolicy="no-referrer">]]></description>
<pubDate>Tue, 16 May 2023 03:02:13 GMT</pubDate>
<guid isPermaLink="false">https://twitter.com/ArknightsStaff/status/1658306831960412166</guid>
<link>https://twitter.com/ArknightsStaff/status/1658306831960412166</link>
<author><![CDATA[アークナイツ公式]]></author>
</item>
</channel>
</rss>

169
tests/platforms/test_rss.py Normal file
View File

@ -0,0 +1,169 @@
import typing
import xml.etree.ElementTree as ET
from datetime import datetime
import pytest
import pytz
import respx
from httpx import AsyncClient, Response
from nonebug.app import App
from .utils import get_file
if typing.TYPE_CHECKING:
from nonebot_bison.platform.rss import Rss
@pytest.fixture
def dummy_user(app: App):
from nonebot_bison.types import User
user = User(123, "group")
return user
@pytest.fixture
def user_info_factory(app: App, dummy_user):
from nonebot_bison.types import UserSubInfo
def _user_info(category_getter, tag_getter):
return UserSubInfo(dummy_user, category_getter, tag_getter)
return _user_info
@pytest.fixture
def rss(app: App):
from nonebot_bison.platform import platform_manager
from nonebot_bison.utils import ProcessContext
return platform_manager["rss"](ProcessContext(), AsyncClient())
@pytest.fixture
def update_time_feed_1():
file = get_file("rss-twitter-ArknightsStaff.xml")
root = ET.fromstring(file)
item = root.find("channel/item")
current_time = datetime.now(pytz.timezone("GMT")).strftime(
"%a, %d %b %Y %H:%M:%S %Z"
)
pubdate_elem = item.find("pubDate")
pubdate_elem.text = current_time
return ET.tostring(root, encoding="unicode")
@pytest.fixture
def update_time_feed_2():
file = get_file("rss-ruanyifeng.xml")
root = ET.fromstring(file)
current_time = datetime.now(pytz.timezone("GMT")).strftime(
"%a, %d %b %Y %H:%M:%S %Z"
)
published_element = root.find(".//{*}published")
published_element.text = current_time
return ET.tostring(root, encoding="unicode")
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new_1(
rss,
user_info_factory,
update_time_feed_1,
):
## 标题重复的情况
rss_router = respx.get("https://rsshub.app/twitter/user/ArknightsStaff")
rss_router.mock(
return_value=Response(200, text=get_file("rss-twitter-ArknightsStaff-0.xml"))
)
target = "https://rsshub.app/twitter/user/ArknightsStaff"
res1 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res1) == 0
rss_router.mock(return_value=Response(200, text=update_time_feed_1))
res2 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res2[0][1]) == 1
post1 = res2[0][1][0]
assert post1.url == "https://twitter.com/ArknightsStaff/status/1659091539023282178"
assert (
post1.text
== "【#統合戦略】 引き続き新テーマ「ミヅキと紺碧の樹」の新要素及びシステムの変更点を一部ご紹介します! 今回は「灯火」、「ダイス」、「記号認識」、「鍵」についてです。詳細は添付の画像をご確認ください。#アークナイツ https://t.co/ARmptV0Zvu"
)
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new_2(
rss,
user_info_factory,
update_time_feed_2,
):
## 标题与正文不重复的情况
rss_router = respx.get("https://www.ruanyifeng.com/blog/atom.xml")
rss_router.mock(return_value=Response(200, text=get_file("rss-ruanyifeng-0.xml")))
target = "https://www.ruanyifeng.com/blog/atom.xml"
res1 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res1) == 0
rss_router.mock(return_value=Response(200, text=update_time_feed_2))
res2 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res2[0][1]) == 1
post1 = res2[0][1][0]
assert post1.url == "http://www.ruanyifeng.com/blog/2023/05/weekly-issue-255.html"
assert post1.text == "科技爱好者周刊(第 255 期):对待 AI 的正确态度\n\n这里记录每周值得分享的科技内容,周五发布。..."
@pytest.fixture
def update_time_feed_3():
file = get_file("rss-github-atom.xml")
root = ET.fromstring(file)
current_time = datetime.now(pytz.timezone("GMT")).strftime(
"%a, %d %b %Y %H:%M:%S %Z"
)
published_element = root.findall(".//{*}updated")[1]
published_element.text = current_time
return ET.tostring(root, encoding="unicode")
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new_3(
rss,
user_info_factory,
update_time_feed_3,
):
## 只有<updated>没有<published>
rss_router = respx.get("https://github.com/R3nzTheCodeGOD/R3nzSkin/releases.atom")
rss_router.mock(return_value=Response(200, text=get_file("rss-github-atom-0.xml")))
target = "https://github.com/R3nzTheCodeGOD/R3nzSkin/releases.atom"
res1 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res1) == 0
rss_router.mock(return_value=Response(200, text=update_time_feed_3))
res2 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res2[0][1]) == 1
post1 = res2[0][1][0]
assert post1.url == "https://github.com/R3nzTheCodeGOD/R3nzSkin/releases/tag/v3.0.9"
assert post1.text == "R3nzSkin\n\nNo content."
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new_4(
rss,
user_info_factory,
):
## 没有日期信息的情况
rss_router = respx.get("https://rsshub.app/wallhaven/hot?limit=5")
rss_router.mock(return_value=Response(200, text=get_file("rss-top5-old.xml")))
target = "https://rsshub.app/wallhaven/hot?limit=5"
res1 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res1) == 0
rss_router.mock(return_value=Response(200, text=get_file("rss-top5-new.xml")))
res2 = await rss.fetch_new_post(target, [user_info_factory([], [])])
assert len(res2[0][1]) == 1
post1 = res2[0][1][0]
assert post1.url == "https://wallhaven.cc/w/85rjej"
assert post1.text == "85rjej.jpg"