diff --git a/catalog/book/models.py b/catalog/book/models.py index 323c5b5e..866e0a32 100644 --- a/catalog/book/models.py +++ b/catalog/book/models.py @@ -234,6 +234,7 @@ class Edition(Item): IdType.Goodreads, IdType.GoogleBooks, IdType.Qidian, + IdType.JJWXC, ] return [(i.value, i.label) for i in id_types] diff --git a/catalog/book/tests.py b/catalog/book/tests.py index 5cd57d8d..1155b71f 100644 --- a/catalog/book/tests.py +++ b/catalog/book/tests.py @@ -423,6 +423,25 @@ class QidianTestCase(TestCase): self.assertEqual(site.resource.item.author[0], "爱潜水的乌贼") +class JinJiangTestCase(TestCase): + databases = "__all__" + + @use_local_response + def test_scrape(self): + t_url = "https://www.jjwxc.net/onebook.php?novelid=5833245" + site = SiteManager.get_site_by_url(t_url) + self.assertEqual(site.ready, False) + site.get_resource_ready() + self.assertEqual(site.ready, True) + self.assertEqual(site.resource.site_name, SiteName.JJWXC) + self.assertEqual(site.resource.id_type, IdType.JJWXC) + self.assertEqual(site.resource.id_value, "5833245") + self.assertEqual( + site.resource.item.display_title, "穿进赛博游戏后干掉BOSS成功上位" + ) + self.assertEqual(site.resource.item.author[0], "桉柏") + + class YpshuoTestCase(TestCase): databases = "__all__" diff --git a/catalog/common/models.py b/catalog/common/models.py index 1e94196b..47085c6f 100644 --- a/catalog/common/models.py +++ b/catalog/common/models.py @@ -53,6 +53,7 @@ class SiteName(models.TextChoices): Qidian = "qidian", _("Qidian") # type:ignore[reportCallIssue] Ypshuo = "ypshuo", _("Ypshuo") # type:ignore[reportCallIssue] AO3 = "ao3", _("Archive of Our Own") # type:ignore[reportCallIssue] + JJWXC = "jjwxc", _("JinJiang") # type:ignore[reportCallIssue] class IdType(models.TextChoices): @@ -117,6 +118,7 @@ class IdType(models.TextChoices): Qidian = "qidian", _("Qidian") # type:ignore[reportCallIssue] Ypshuo = "ypshuo", _("Ypshuo") # type:ignore[reportCallIssue] AO3 = "ao3", _("Archive of Our Own") # type:ignore[reportCallIssue] + JJWXC = "jjwxc", _("JinJiang") # type:ignore[reportCallIssue] IdealIdTypes = [ diff --git a/catalog/sites/__init__.py b/catalog/sites/__init__.py index 38904089..28e1beaf 100644 --- a/catalog/sites/__init__.py +++ b/catalog/sites/__init__.py @@ -16,6 +16,7 @@ from .goodreads import Goodreads from .google_books import GoogleBooks from .igdb import IGDB from .imdb import IMDB +from .jjwxc import JJWXC from .qidian import Qidian from .rss import RSS from .spotify import Spotify diff --git a/catalog/sites/jjwxc.py b/catalog/sites/jjwxc.py new file mode 100644 index 00000000..47dc6b29 --- /dev/null +++ b/catalog/sites/jjwxc.py @@ -0,0 +1,39 @@ +import html + +from catalog.common import * +from catalog.models import * + + +@SiteManager.register +class JJWXC(AbstractSite): + SITE_NAME = SiteName.JJWXC + ID_TYPE = IdType.JJWXC + URL_PATTERNS = [ + r"https://www\.jjwxc\.net/onebook\.php\?novelid=(\d+)", + ] + WIKI_PROPERTY_ID = "" + DEFAULT_MODEL = Edition + + @classmethod + def id_to_url(cls, id_value): + return f"https://www.jjwxc.net/onebook.php?novelid={id_value}" + + def scrape(self): + api_url = ( + f"https://app.jjwxc.net/androidapi/novelbasicinfo?novelId={self.id_value}" + ) + o = BasicDownloader(api_url).download().json() + return ResourceContent( + metadata={ + "localized_title": [{"lang": "zh-cn", "text": o["novelName"]}], + "author": [o["authorName"]], + "format": Edition.BookFormat.WEB, + "localized_description": [ + { + "lang": "zh-cn", + "text": html.unescape(o["novelIntro"]).replace("
", "\n"), + } + ], + "cover_image_url": o["novelCover"], + }, + ) diff --git a/catalog/sites/ypshuo.py b/catalog/sites/ypshuo.py index 6c3d2a38..5f7083fc 100644 --- a/catalog/sites/ypshuo.py +++ b/catalog/sites/ypshuo.py @@ -24,8 +24,14 @@ class Ypshuo(AbstractSite): source = json.loads(o["data"]["source"]) lookup_ids = {} for site in source: - if site["siteName"] == "起点中文网": - lookup_ids[IdType.Qidian] = site["bookId"] + match site["siteName"]: + case "起点中文网": + lookup_ids[IdType.Qidian] = site["bookId"] + case "晋江文学城": + lookup_ids[IdType.JJWXC] = site["bookPage"].rsplit("=", maxsplit=1)[ + -1 + ] + return ResourceContent( metadata={ "localized_title": [{"lang": "zh-cn", "text": o["data"]["novel_name"]}], diff --git a/common/static/scss/_sitelabel.scss b/common/static/scss/_sitelabel.scss index 2081a9c6..dedaa934 100644 --- a/common/static/scss/_sitelabel.scss +++ b/common/static/scss/_sitelabel.scss @@ -30,6 +30,12 @@ background-color: #9e252b; } + .jjwxc { + border: none; + color: white; + background-color: #3c812e; + } + .douban { border: none; color: white; diff --git a/test_data/https___app_jjwxc_net_androidapi_novelbasicinfo_novelId_5833245 b/test_data/https___app_jjwxc_net_androidapi_novelbasicinfo_novelId_5833245 new file mode 100644 index 00000000..e4098838 --- /dev/null +++ b/test_data/https___app_jjwxc_net_androidapi_novelbasicinfo_novelId_5833245 @@ -0,0 +1 @@ +{"novelId":"5833245","novelName":"穿进赛博游戏后干掉BOSS成功上位","authorId":"2269132","authorName":"桉柏","novelClass":"原创-言情-幻想未来-游戏-女主","novelTags":"穿越时空,系统,正剧,赛博朋克,克苏鲁","novelTagsId":"60,122,263,277,283","novelCover":"http://i5-static.jjwxc.net/tmp/backend/authorspace/s1/23/22692/2269132/20240616005405_200_280.jpg","originalCover":"http://i5-static.jjwxc.net/tmp/backend/authorspace/s1/23/22692/2269132/20240616005405.jpg","novelStep":"2","novelIntro":"【简体出版进度wb@桉柏ANBAI通知。】<br/>【全文完结,捉虫修文期,更新请无视。专栏有免费番外。22.10.05】<br/>【预收《群星即我[虫族末日]》星际文求收藏】<br/><br/><br/>赛博朋克+克系元素的全息游戏《深红之土》即将发售。<br/><br/><br/>隗辛走了狗屎运,被选中成为了《深红之土》的内测玩家。<br/><br/><br/>然而事情朝着诡异的方向一路狂奔,她发现她不是在玩什么全息游戏,而是穿越到了一个真实存在的平行世界。<br/><br/><br/>钢与铁的森林里人们挣扎求生,霓虹灯的色彩下当权者举杯共饮。<br/>财团把持着经济命脉,超级人工智能监视着每个人的一举一动。<br/>超凡者、机械改造人、秘密教团、畸变人登上时代的舞台……<br/><br/><br/>隗辛刚一登录游戏就觉得自己貌似要出大事。<br/><br/><br/>求问:当你发现你在游戏里的身份是联邦一级通缉犯,并且还在官方缉查部门里当卧底该怎么办?<br/><br/><br/>答:最危险的地方就是最安全的地方,演一出我追查我自己,然后找机会死遁。<br/><br/><br/>……<br/>姓名:隗辛<br/>身份:某反叛组织派往联邦缉查部门的卧底。<br/>任务:苟住小命,努力升级。<br/><br/><br/>看过任务后,隗辛觉得不行。<br/>二五仔是个没有前途的职业,光是保住小命努力升级一点都不过瘾,她想要整一票大的。<br/>比如干掉BOSS换她上位什么的,感觉就很过瘾。<br/><br/><br/>……<br/>【食用指南】<br/>1.二刷读者不要剧透。感情线少,为了剧情体验不会透露cp是谁,非买股,正剧剧情向,人物结局随故事发展而定。本文言情,从21.07开文起标签就是言情,非无cp,禁止磕腐。慢热,前期偏向探索解密和成长。<br/>2.世界上有超能力。我流赛博朋克,克系元素占比较小。不懂这两个元素也能看懂,但是建议搜索了解一下这两个元素,会提升不少代入感和看文体验。本文非正统克系,非正统科幻,仅借用部分设定填充基础框架与世界观。<br/>3.本文故事时间线双线并进,只讲述女主在七周内遭遇的事情,开放式结局。非典型第四天灾,玩家们群穿,会在两个世界来回穿梭。<br/>4.平行世界架空,勿代入现实。三观党勿入,对主角有道德要求的勿入。角色观点不代表作者观点。<br/>5.别在我的评论区提别人的文,别在别人的评论区提我的文,互相尊重,多谢。友好交流,和平讨论,禁止人身攻击,希望大家在和谐的氛围中看文。<br/>【科普什么是赛博朋克(来自百度百科)】<br/>赛博朋克(英文:Cyberpunk)是“控制论、神经机械学”与“朋克”的结合词,中文又称作“赛伯朋克”。<br/>赛博朋克类作品背景大都建立于“低端生活与高等科技结合”的基础上,通常拥有先进的科学技术,再以一定程度崩坏的社会结构做对比;拥有五花八门的视觉冲击效果,比如街头的霓虹灯、街排标志性广告以及高楼建筑等,通常搭配色彩是以黑、紫、绿、蓝、红为主。故事框架是以社会秩序受到政|府或财团或秘密组织的高度控制,而主角利用其中的漏洞做出了某种突破。<br/>赛博朋克的情节通常围绕黑客、人工智能及大型企业之间的矛盾而展开,背景设在不远的将来的一个反乌托邦地球,而不是早期赛博朋克的外太空。它实际上标志着针对以往科幻小说不注重信息技术的具体设定的缺点的改善和进步。<br/>【科普什么是克苏鲁神话(来自百度百科)】<br/>克苏鲁神话(Cthulhu Mythos)是以美国作家霍华德·菲利普·洛夫克拉夫特的小说世界为基础,由诸多作者所共同创造的架空文学体系。洛夫克拉夫特作品的共同主题是人类在宇宙中的渺小和以灾难性结局告终的知识探索。人类经常受限于强大的存在或其他宇宙力量。但这些存在与其说是恶意的,不如说是对人类漠不关心。洛夫克拉夫特将这种观点称为“宇宙主|义(Cosmicism)” 最终人类在一个冷漠的宇宙中是孤独的且没有自保能力。在这样一个浩瀚无边、而又毫无理性与目的可言的宇宙中,人类所认识和规定的法则与观念都是毫无意义的。人类是渺小的,而神是不可战胜的,人无法认识到明晰的宇宙,且客观规律不以人的意志为转移。<br\/>\u7acb\u610f:\u6c38\u4e0d\u653e\u5f03\uff0c\u8ffd\u6c42\u81ea\u7531","novelIntroShort":"玩家的赛博游戏人生","isVip":"1","isPackage":"0","novelSize":"1,465,379","novelsizeformat":"146.54万","novelChapterCount":"370","renewDate":"2023-09-12 15:03:17","renewChapterId":"1","renewChapterName":"无光之海01","novelScore":"156.9亿","islock":"0","novelbefavoritedcount":"708062","novelbefavoritedcountformat":"708062","type_id":"1","age":"18","maxChapterId":"370","chapterdateNewest":"2023-09-12 15:03:17","local":"0","localImg":"https://i9-static.jjwxc.net/noveldefaultcover.php?cover=NTNfbm92ZWxkZWZhdWx0Y292ZXI=&version=20230323","novelStyle":"正剧","series":"自由之路","protagonist":"主角:隗辛","costar":"配角:背景板们","other":"其它:全息游戏,克系元素","comment_count":"434,030","nutrition_novel":"2892248","ranking":"\u7b2c345\u540d","novip_clicks":"857,389(\u7ae0\u5747)","vipChapterid":"15","isSign":"1","ILTC":"","mainview":"\u5973\u4e3b","codeUrl":"https:\/\/m.jjwxc.net\/book2\/5833245","novelReviewScore":"9.8\u5206","cover_border":"","qgmt":"1","characters":[{"novelid":"5833245","character_id":"1","character_name":"\u9697\u8f9b","character_masked":"0","character_gender":"2","is_pov":"1","character_type":"1","character_pic":"","dateline":"2024-05-31 04:02:32","character_pic_type":"0"},{"novelid":"5833245","character_id":"2","character_name":"\u80cc\u666f\u677f\u4eec","character_masked":"0","character_gender":"1","is_pov":"0","character_type":"1","character_pic":"","dateline":"2024-05-31 04:02:32","character_pic_type":"0"}],"character_relations":[],"cache_create_date":"2024-11-13 20:08:51","authorsayrule":"1.\u4f5c\u8005\u6709\u8bdd\u8bf4\u7981\u6b62\u4ee5\u7ec4\u5efa\u8bfb\u8005\u7fa4\u4e3a\u76ee\u7684\u5ba3\u4f20\u53d1\u5e03\u79c1\u4eba\u7fa4\u4fe1\u606f\u7684\u884c\u4e3a\u3002\n2.\u7981\u6b62\u5728\u4f5c\u8005\u6709\u8bdd\u8bf4\u4ee5\u4efb\u4f55\u5f62\u5f0f\u5f15\u5bfc\u8bfb\u8005\u53bb\u975e\u664b\u6c5f\u7ad9\u9605\u8bfb\u7684\u884c\u4e3a\u3002\n3.\u7981\u6b62\u5728\u4f5c\u8005\u6709\u8bdd\u8bf4\u660e\u793a\u6216\u8005\u6697\u55bb\u8bfb\u8005\u672c\u6587\u5b58\u5728\u4e0d\u826f\u5185\u5bb9\u7684\u884c\u4e3a\u3002","copystatus":"0","isVipMonth":"0","yellowcard":[],"is_short_package":"0"} \ No newline at end of file