diff --git a/catalog/movie/tests.py b/catalog/movie/tests.py index 8e642903..1197fe4c 100644 --- a/catalog/movie/tests.py +++ b/catalog/movie/tests.py @@ -80,6 +80,18 @@ class IMDBMovieTestCase(TestCase): self.assertEqual(site.resource.item.imdb, "tt1375666") +class BangumiMovieTestCase(TestCase): + @use_local_response + def test_scrape(self): + url = "https://bgm.tv/subject/237" + site = SiteManager.get_site_by_url(url) + self.assertEqual(site.id_value, "237") + site.get_resource_ready() + self.assertEqual(site.resource.metadata["title"], "攻壳机动队") + self.assertEqual(site.resource.item.primary_lookup_id_type, IdType.IMDB) + self.assertEqual(site.resource.item.imdb, "tt0113568") + + class MultiMovieSitesTestCase(TestCase): @use_local_response def test_movies(self): diff --git a/catalog/sites/bangumi.py b/catalog/sites/bangumi.py index 5fbcfaf7..0cf563a8 100644 --- a/catalog/sites/bangumi.py +++ b/catalog/sites/bangumi.py @@ -1,5 +1,6 @@ import logging +from catalog.book.utils import detect_isbn_asin from catalog.common import * from catalog.models import * @@ -12,6 +13,7 @@ class Bangumi(AbstractSite): ID_TYPE = IdType.Bangumi URL_PATTERNS = [ r"https://bgm\.tv/subject/(\d+)", + r"https://bangumi\.tv/subject/(\d+)", ] WIKI_PROPERTY_ID = "" DEFAULT_MODEL = None @@ -21,5 +23,116 @@ class Bangumi(AbstractSite): return f"https://bgm.tv/subject/{id_value}" def scrape(self): - # TODO rewrite with bangumi api https://bangumi.github.io/api/ - pass + api_url = f"https://api.bgm.tv/v0/subjects/{self.id_value}" + o = BasicDownloader(api_url).download().json() + showtime = None + pub_year = None + pub_month = None + year = None + dt = o.get("date") + episodes = o.get("total_episodes", 0) + match o["type"]: + case 1: + model = "Edition" + if dt: + d = dt.split("-") + pub_year = d[0] + pub_month = d[1] + case 2 | 6: + is_series = episodes > 1 + model = "TVSeason" if is_series else "Movie" + if dt: + year = dt.split("-")[0] + showtime = [{"time": dt, "region": "首播日期" if is_series else "发布日期"}] + case 3: + model = "Album" + case 4: + model = "Game" + case _: + raise ValueError( + f"Unknown type {o['type']} for bangumi subject {self.id_value}" + ) + title = o.get("name_cn") or o.get("name") + orig_title = o.get("name") if o.get("name") != title else None + brief = o.get("summary") + + genre = None + platform = None + other_title = [] + imdb_code = None + isbn_type = None + isbn = None + language = None + pub_house = None + authors = None + site = None + director = None + for i in o.get("infobox", []): + k = i["key"] + v = i["value"] + match k: + case "别名": + other_title = [d["v"] for d in v] if type(v) == list else v + case "imdb_id": + imdb_code = v + case "isbn": + isbn_type, isbn = detect_isbn_asin(v) + case "语言": + language = v + case "出版社": + pub_house = v + case "导演": + director = v + case "作者": + authors = [d["v"] for d in v] if type(v) == list else v + case "平台": + platform = [d["v"] for d in v] if type(v) == list else v + case "游戏类型": + genre = v + case "官方网站" | "website": + site = v[0] if type(v) == list else v + + img_url = o["images"].get("large") or o["images"].get("common") + raw_img = None + ext = None + if img_url: + raw_img, ext = BasicImageDownloader.download_image( + img_url, None, headers={} + ) + data = { + "preferred_model": model, + "title": title, + "orig_title": orig_title, + "other_title": other_title or None, + "author": authors, + "genre": genre, + "translator": None, + "director": director, + "language": language, + "platform": platform, + "year": year, + "showtime": showtime, + "imdb_code": imdb_code, + "pub_house": pub_house, + "pub_year": pub_year, + "pub_month": pub_month, + "binding": None, + "episode_count": episodes or None, + "official_site": site, + "site": site, + "isbn": isbn, + "brief": brief, + "cover_image_url": img_url, + "release_date": dt, + } + lookup_ids = {} + if isbn: + lookup_ids[isbn_type] = isbn + if imdb_code: + lookup_ids[IdType.IMDB] = imdb_code + return ResourceContent( + metadata={k: v for k, v in data.items() if v is not None}, + cover_image=raw_img, + cover_image_extention=ext, + lookup_ids=lookup_ids, + ) diff --git a/journal/templates/_sidebar_user_mark_list.html b/journal/templates/_sidebar_user_mark_list.html new file mode 100644 index 00000000..b4aa317d --- /dev/null +++ b/journal/templates/_sidebar_user_mark_list.html @@ -0,0 +1,76 @@ +{% load admin_url %} +{% load duration %} +{% load static %} +{% load i18n %} +
+ +
+ + + +
+ +
diff --git a/test_data/https___api_bgm_tv_v0_subjects_237 b/test_data/https___api_bgm_tv_v0_subjects_237 new file mode 100644 index 00000000..6fc8b772 --- /dev/null +++ b/test_data/https___api_bgm_tv_v0_subjects_237 @@ -0,0 +1 @@ +{"date":"1995-11-18","platform":"剧场版","images":{"small":"https://lain.bgm.tv/r/200/pic/cover/l/53/9f/237_78UdL.jpg","grid":"https://lain.bgm.tv/r/100/pic/cover/l/53/9f/237_78UdL.jpg","large":"https://lain.bgm.tv/pic/cover/l/53/9f/237_78UdL.jpg","medium":"https://lain.bgm.tv/r/800/pic/cover/l/53/9f/237_78UdL.jpg","common":"https://lain.bgm.tv/r/400/pic/cover/l/53/9f/237_78UdL.jpg"},"summary":" 2029年,公安九课在市容很像香港的日本城市“新港市”中执行各种秘密任务。九课因为不顾正当程序的自作主张、强硬行事,往往与政府其他部门起冲突,例如负责外交的公安六课就与九课不合。另一方面,九课之中的重要人物,全身是义体的机械生化人草薙素子,对于自己的现状一直隐隐感到不满。有一天,公安九课因为一件外国政要的刺杀未遂案而开始追查神秘的黑客“傀儡师”。傀儡师能透过网络入侵人脑,植入虚拟记忆,再利用记忆被窜改的对象来达成目的。虽然九课的追查没有成功,藏有傀儡师的“灵魂”的机械生化人却自行来到九课。公安六课随后赶来将傀儡师绑走。草薙素子因为在事前就推测出六课的绑架行动而成功拦截傀儡师。这时发现原来傀儡师并不是人类,而是公安六课设计来非法操纵人类的电脑程式“2501计划”。2501计划在意外中产生自觉意识,决定逃出六课以求生存,并且要求和草薙素子的灵魂“融合”以求进一步发展。草薙素子最后在同事巴特的协助与掩护下完成融合并且脱离政府掌握,下落不明,其意识与整个网络融合。","name":"GHOST IN THE SHELL / 攻殻機動隊","name_cn":"攻壳机动队","tags":[{"name":"押井守","count":2519},{"name":"攻壳机动队","count":1960},{"name":"剧场版","count":1558},{"name":"科幻","count":1370},{"name":"赛博朋克","count":1004},{"name":"Production.IG","count":926},{"name":"1995","count":822},{"name":"士郎正宗","count":556},{"name":"Production.I.G","count":470},{"name":"神作","count":417},{"name":"攻殻機動隊","count":239},{"name":"超现实","count":229},{"name":"ProductionI.G","count":165},{"name":"漫画改","count":136},{"name":"漫改","count":70},{"name":"川井宪次","count":57},{"name":"严肃","count":54},{"name":"经典","count":54},{"name":"脑后插管","count":49},{"name":"日本","count":31},{"name":"川井憲次","count":27},{"name":"90年代","count":25},{"name":"动画电影","count":24},{"name":"SF","count":23},{"name":"菅野洋子","count":22},{"name":"1995年","count":22},{"name":"电影","count":21},{"name":"1995年11月","count":19},{"name":"战斗","count":19},{"name":"Production_I.G","count":18}],"infobox":[{"key":"中文名","value":"攻壳机动队"},{"key":"别名","value":[{"v":"攻殻機動隊"},{"v":"GHOST IN THE SHELL"}]},{"key":"上映年度","value":"1995年11月18日"},{"key":"片长","value":"82分"},{"key":"导演","value":"押井守"},{"key":"脚本","value":"伊藤和典"},{"key":"原作","value":"士郎正宗"},{"key":"分镜","value":"押井守"},{"key":"演出","value":"西久保瑞穂"},{"key":"人物设定","value":"沖浦啓之"},{"key":"作画监督","value":"沖浦啓之、黄瀬和哉"},{"key":"机械设定","value":"河森正治、竹内敦志"},{"key":"音乐","value":"川井憲次"},{"key":"动画制作","value":"Production I.G"},{"key":"话数","value":"1"},{"key":"imdb_id","value":"tt0113568"},{"key":"製作","value":"バンダイビジュアル、講談社、MANGA ENTERTAINMENT"}],"rating":{"rank":7,"total":7633,"count":{"1":15,"2":3,"3":7,"4":4,"5":28,"6":96,"7":404,"8":1573,"9":3044,"10":2459},"score":8.9},"total_episodes":1,"collection":{"on_hold":293,"dropped":70,"wish":3613,"collect":11104,"doing":272},"id":237,"eps":1,"volumes":0,"locked":false,"nsfw":false,"type":2}