diff --git a/catalog/common/__init__.py b/catalog/common/__init__.py index 1abc3ce7..9a0a165b 100644 --- a/catalog/common/__init__.py +++ b/catalog/common/__init__.py @@ -5,4 +5,4 @@ from .scrapers import * from . import jsondata -__all__ = ('IdType', 'Item', 'ExternalResource', 'ResourceContent', 'ParseError', 'AbstractSite', 'SiteList', 'jsondata', 'PrimaryLookupIdDescriptor', 'LookupIdDescriptor', 'get_mock_mode', 'use_local_response', 'RetryDownloader', 'BasicDownloader', 'ProxiedDownloader', 'BasicImageDownloader', 'RESPONSE_OK', 'RESPONSE_NETWORK_ERROR', 'RESPONSE_INVALID_CONTENT', 'RESPONSE_CENSORSHIP') +__all__ = ('IdType', 'Item', 'ExternalResource', 'ResourceContent', 'ParseError', 'AbstractSite', 'SiteList', 'jsondata', 'PrimaryLookupIdDescriptor', 'LookupIdDescriptor', 'get_mock_mode', 'get_mock_file', 'use_local_response', 'RetryDownloader', 'BasicDownloader', 'ProxiedDownloader', 'BasicImageDownloader', 'RESPONSE_OK', 'RESPONSE_NETWORK_ERROR', 'RESPONSE_INVALID_CONTENT', 'RESPONSE_CENSORSHIP') diff --git a/catalog/common/downloaders.py b/catalog/common/downloaders.py index 34bfc50f..f7d1ed83 100644 --- a/catalog/common/downloaders.py +++ b/catalog/common/downloaders.py @@ -42,6 +42,11 @@ def get_mock_mode(): return _mock_mode +def get_mock_file(url): + fn = re.sub(r'[^\w]', '_', url) + return re.sub(r'_key_[A-Za-z0-9]+', '_key_19890604', fn) + + class DownloadError(Exception): def __init__(self, downloader, msg=None): self.url = downloader.url @@ -95,7 +100,7 @@ class BasicDownloader: # TODO cache = get/set from redis resp = requests.get(url, headers=self.headers, timeout=self.get_timeout()) if settings.DOWNLOADER_SAVEDIR: - with open(settings.DOWNLOADER_SAVEDIR + '/' + re.sub(r'[^\w]', '_', url), 'w', encoding='utf-8') as fp: + with open(settings.DOWNLOADER_SAVEDIR + '/' + get_mock_file(url), 'w', encoding='utf-8') as fp: fp.write(resp.text) else: resp = MockResponse(self.url) @@ -191,7 +196,15 @@ class ImageDownloaderMixin: class BasicImageDownloader(ImageDownloaderMixin, BasicDownloader): - pass + @classmethod + def download_image(cls, image_url, page_url): + imgdl = cls(image_url, page_url) + try: + image = imgdl.download().content + image_extention = imgdl.extention + return image, image_extention + except Exception: + return None, None class ProxiedImageDownloader(ImageDownloaderMixin, ProxiedDownloader): @@ -202,13 +215,9 @@ _local_response_path = str(Path(__file__).parent.parent.parent.absolute()) + '/t class MockResponse: - def get_mock_file(self, url): - fn = _local_response_path + re.sub(r'[^\w]', '_', url) - return re.sub(r'_key_[A-Za-z0-9]+', '_key_19890604', fn) - def __init__(self, url): self.url = url - fn = self.get_mock_file(url) + fn = _local_response_path + get_mock_file(url) try: self.content = Path(fn).read_bytes() self.status_code = 200 diff --git a/catalog/game/models.py b/catalog/game/models.py index 1be5ff31..08c07dc3 100644 --- a/catalog/game/models.py +++ b/catalog/game/models.py @@ -2,10 +2,7 @@ from catalog.common import * class Game(Item): - igdb = LookupIdDescriptor(IdType.IGDB) - steam = LookupIdDescriptor(IdType.Steam) - douban_game = LookupIdDescriptor(IdType.DoubanGame) + igdb = PrimaryLookupIdDescriptor(IdType.IGDB) + steam = PrimaryLookupIdDescriptor(IdType.Steam) + douban_game = PrimaryLookupIdDescriptor(IdType.DoubanGame) platforms = jsondata.ArrayField(default=list) - - class Meta: - proxy = True diff --git a/catalog/game/tests.py b/catalog/game/tests.py new file mode 100644 index 00000000..9824cab2 --- /dev/null +++ b/catalog/game/tests.py @@ -0,0 +1,99 @@ +from django.test import TestCase +from catalog.common import * +from catalog.models import * + + +class IGDBTestCase(TestCase): + def test_parse(self): + t_id_type = IdType.IGDB + t_id_value = 'portal-2' + t_url = 'https://www.igdb.com/games/portal-2' + site = SiteList.get_site_by_id_type(t_id_type) + self.assertIsNotNone(site) + self.assertEqual(site.validate_url(t_url), True) + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.url, t_url) + self.assertEqual(site.id_value, t_id_value) + + @use_local_response + def test_scrape(self): + t_url = 'https://www.igdb.com/games/portal-2' + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.ready, False) + site.get_resource_ready() + self.assertEqual(site.ready, True) + self.assertEqual(site.resource.metadata['title'], 'Portal 2') + self.assertIsInstance(site.resource.item, Game) + self.assertEqual(site.resource.item.steam, '620') + + @use_local_response + def test_scrape_non_steam(self): + t_url = 'https://www.igdb.com/games/the-legend-of-zelda-breath-of-the-wild' + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.ready, False) + site.get_resource_ready() + self.assertEqual(site.ready, True) + self.assertEqual(site.resource.metadata['title'], 'The Legend of Zelda: Breath of the Wild') + self.assertIsInstance(site.resource.item, Game) + self.assertEqual(site.resource.item.primary_lookup_id_type, IdType.IGDB) + self.assertEqual(site.resource.item.primary_lookup_id_value, 'the-legend-of-zelda-breath-of-the-wild') + + +class SteamTestCase(TestCase): + def test_parse(self): + t_id_type = IdType.Steam + t_id_value = '620' + t_url = 'https://store.steampowered.com/app/620/Portal_2/' + t_url2 = 'https://store.steampowered.com/app/620' + site = SiteList.get_site_by_id_type(t_id_type) + self.assertIsNotNone(site) + self.assertEqual(site.validate_url(t_url), True) + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.url, t_url2) + self.assertEqual(site.id_value, t_id_value) + + @use_local_response + def test_scrape(self): + t_url = 'https://store.steampowered.com/app/620/Portal_2/' + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.ready, False) + site.get_resource_ready() + self.assertEqual(site.ready, True) + self.assertEqual(site.resource.metadata['title'], 'Portal 2') + self.assertEqual(site.resource.metadata['brief'], '“终身测试计划”现已升级,您可以为您自己或您的好友设计合作谜题!') + self.assertIsInstance(site.resource.item, Game) + self.assertEqual(site.resource.item.steam, '620') + + +class DoubanGameTestCase(TestCase): + def test_parse(self): + t_id_type = IdType.DoubanGame + t_id_value = '10734307' + t_url = 'https://www.douban.com/game/10734307/' + site = SiteList.get_site_by_id_type(t_id_type) + self.assertIsNotNone(site) + self.assertEqual(site.validate_url(t_url), True) + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.url, t_url) + self.assertEqual(site.id_value, t_id_value) + + @use_local_response + def test_scrape(self): + t_url = 'https://www.douban.com/game/10734307/' + site = SiteList.get_site_by_url(t_url) + self.assertEqual(site.ready, False) + site.get_resource_ready() + self.assertEqual(site.ready, True) + self.assertEqual(site.resource.metadata['title'], '传送门2 Portal 2') + self.assertIsInstance(site.resource.item, Game) + self.assertEqual(site.resource.item.douban_game, '10734307') + + +class MultiGameSitesTestCase(TestCase): + @use_local_response + def test_games(self): + url1 = 'https://www.igdb.com/games/portal-2' + url2 = 'https://store.steampowered.com/app/620/Portal_2/' + p1 = SiteList.get_site_by_url(url1).get_resource_ready() + p2 = SiteList.get_site_by_url(url2).get_resource_ready() + self.assertEqual(p1.item.id, p2.item.id) diff --git a/catalog/sites/__init__.py b/catalog/sites/__init__.py index 0ba885d1..01717bde 100644 --- a/catalog/sites/__init__.py +++ b/catalog/sites/__init__.py @@ -3,8 +3,11 @@ from .apple_podcast import ApplePodcast from .douban_book import DoubanBook from .douban_movie import DoubanMovie from .douban_music import DoubanMusic +from .douban_game import DoubanGame from .douban_drama import DoubanDrama from .goodreads import Goodreads from .tmdb import TMDB_Movie from .imdb import IMDB from .spotify import Spotify +from .igdb import IGDB +from .steam import Steam diff --git a/catalog/sites/douban_game.py b/catalog/sites/douban_game.py new file mode 100644 index 00000000..4c50a42e --- /dev/null +++ b/catalog/sites/douban_game.py @@ -0,0 +1,76 @@ +from catalog.common import * +from catalog.models import * +from .douban import DoubanDownloader +import dateparser +import logging + + +_logger = logging.getLogger(__name__) + + +@SiteList.register +class DoubanGame(AbstractSite): + ID_TYPE = IdType.DoubanGame + URL_PATTERNS = [r"\w+://www\.douban\.com/game/(\d+)/{0,1}", r"\w+://m.douban.com/game/subject/(\d+)/{0,1}"] + WIKI_PROPERTY_ID = '' + DEFAULT_MODEL = Game + + @classmethod + def id_to_url(self, id_value): + return "https://www.douban.com/game/" + id_value + "/" + + def scrape(self): + content = DoubanDownloader(self.url).download().html() + + elem = content.xpath("//div[@id='content']/h1/text()") + title = elem[0].strip() if len(elem) else None + if not title: + raise ParseError(self, "title") + + other_title_elem = content.xpath( + "//dl[@class='game-attr']//dt[text()='别名:']/following-sibling::dd[1]/text()") + other_title = other_title_elem[0].strip().split(' / ') if other_title_elem else None + + developer_elem = content.xpath( + "//dl[@class='game-attr']//dt[text()='开发商:']/following-sibling::dd[1]/text()") + developer = developer_elem[0].strip().split(' / ') if developer_elem else None + + publisher_elem = content.xpath( + "//dl[@class='game-attr']//dt[text()='发行商:']/following-sibling::dd[1]/text()") + publisher = publisher_elem[0].strip().split(' / ') if publisher_elem else None + + platform_elem = content.xpath( + "//dl[@class='game-attr']//dt[text()='平台:']/following-sibling::dd[1]/a/text()") + platform = platform_elem if platform_elem else None + + genre_elem = content.xpath( + "//dl[@class='game-attr']//dt[text()='类型:']/following-sibling::dd[1]/a/text()") + genre = None + if genre_elem: + genre = [g for g in genre_elem if g != '游戏'] + + date_elem = content.xpath( + "//dl[@class='game-attr']//dt[text()='发行日期:']/following-sibling::dd[1]/text()") + release_date = dateparser.parse(date_elem[0].strip()).strftime('%Y-%m-%d') if date_elem else None + + brief_elem = content.xpath("//div[@class='mod item-desc']/p/text()") + brief = '\n'.join(brief_elem) if brief_elem else None + + img_url_elem = content.xpath( + "//div[@class='item-subject-info']/div[@class='pic']//img/@src") + img_url = img_url_elem[0].strip() if img_url_elem else None + + pd = ResourceContent(metadata={ + 'title': title, + 'other_title': other_title, + 'developer': developer, + 'publisher': publisher, + 'release_date': release_date, + 'genre': genre, + 'platform': platform, + 'brief': brief, + 'cover_image_url': img_url + }) + if pd.metadata["cover_image_url"]: + pd.cover_image, pd.cover_image_extention = BasicImageDownloader.download_image(pd.metadata['cover_image_url'], self.url) + return pd diff --git a/catalog/sites/igdb.py b/catalog/sites/igdb.py new file mode 100644 index 00000000..fc4eaa35 --- /dev/null +++ b/catalog/sites/igdb.py @@ -0,0 +1,113 @@ +""" +IGDB + +use (e.g. "portal-2") as id, which is different from real id in IGDB API +""" + +from catalog.common import * +from catalog.models import * +from django.conf import settings +from igdb.wrapper import IGDBWrapper +import requests +import datetime +import json +import logging + + +_logger = logging.getLogger(__name__) + + +def _igdb_access_token(): + try: + token = requests.post(f'https://id.twitch.tv/oauth2/token?client_id={settings.IGDB_CLIENT_ID}&client_secret={settings.IGDB_CLIENT_SECRET}&grant_type=client_credentials').json()['access_token'] + except Exception: + _logger.error('unable to obtain IGDB token') + token = '' + return token + + +_wrapper = IGDBWrapper(settings.IGDB_CLIENT_ID, _igdb_access_token()) + + +def search_igdb_by_3p_url(steam_url): + r = IGDB.api_query('websites', f'fields *, game.*; where url = "{steam_url}";') + if not r: + return None + r = sorted(r, key=lambda w: w['game']['id']) + return IGDB(url=r[0]['game']['url']) + + +@SiteList.register +class IGDB(AbstractSite): + ID_TYPE = IdType.IGDB + URL_PATTERNS = [r"\w+://www\.igdb\.com/games/([a-zA-Z0-9\-_]+)"] + WIKI_PROPERTY_ID = '?' + DEFAULT_MODEL = Game + + @classmethod + def id_to_url(self, id_value): + return "https://www.igdb.com/games/" + id_value + + @classmethod + def api_query(cls, p, q): + key = 'igdb:' + p + '/' + q + if get_mock_mode(): + r = BasicDownloader(key).download().json() + else: + r = json.loads(_wrapper.api_request(p, q)) + if settings.DOWNLOADER_SAVEDIR: + with open(settings.DOWNLOADER_SAVEDIR + '/' + get_mock_file(key), 'w', encoding='utf-8') as fp: + fp.write(json.dumps(r)) + return r + + def scrape(self): + fields = '*, cover.url, genres.name, platforms.name, involved_companies.*, involved_companies.company.name' + r = self.api_query('games', f'fields {fields}; where url = "{self.url}";')[0] + brief = r['summary'] if 'summary' in r else '' + brief += "\n\n" + r['storyline'] if 'storyline' in r else '' + developer = None + publisher = None + release_date = None + genre = None + platform = None + if 'involved_companies' in r: + developer = next(iter([c['company']['name'] for c in r['involved_companies'] if c['developer']]), None) + publisher = next(iter([c['company']['name'] for c in r['involved_companies'] if c['publisher']]), None) + if 'platforms' in r: + ps = sorted(r['platforms'], key=lambda p: p['id']) + platform = [(p['name'] if p['id'] != 6 else 'Windows') for p in ps] + if 'first_release_date' in r: + release_date = datetime.datetime.fromtimestamp(r['first_release_date'], datetime.timezone.utc).strftime('%Y-%m-%d') + if 'genres' in r: + genre = [g['name'] for g in r['genres']] + websites = self.api_query('websites', f'fields *; where game.url = "{self.url}";') + steam_url = None + official_site = None + for website in websites: + if website['category'] == 1: + official_site = website['url'] + elif website['category'] == 13: + steam_url = website['url'] + pd = ResourceContent(metadata={ + 'title': r['name'], + 'other_title': None, + 'developer': developer, + 'publisher': publisher, + 'release_date': release_date, + 'genre': genre, + 'platform': platform, + 'brief': brief, + 'official_site': official_site, + 'igdb_id': r['id'], + 'cover_image_url': 'https:' + r['cover']['url'].replace('t_thumb', 't_cover_big'), + }) + if steam_url: + pd.lookup_ids[IdType.Steam] = SiteList.get_site_by_id_type(IdType.Steam).url_to_id(steam_url) + if pd.metadata["cover_image_url"]: + imgdl = BasicImageDownloader(pd.metadata["cover_image_url"], self.url) + try: + pd.cover_image = imgdl.download().content + pd.cover_image_extention = imgdl.extention + except Exception: + _logger.debug(f'failed to download cover for {self.url} from {pd.metadata["cover_image_url"]}') + return pd diff --git a/catalog/sites/steam.py b/catalog/sites/steam.py new file mode 100644 index 00000000..c80bc769 --- /dev/null +++ b/catalog/sites/steam.py @@ -0,0 +1,64 @@ +from catalog.common import * +from catalog.models import * +from .igdb import search_igdb_by_3p_url +import dateparser +import logging + + +_logger = logging.getLogger(__name__) + + +@SiteList.register +class Steam(AbstractSite): + ID_TYPE = IdType.Steam + URL_PATTERNS = [r"\w+://store\.steampowered\.com/app/(\d+)"] + WIKI_PROPERTY_ID = '?' + DEFAULT_MODEL = Game + + @classmethod + def id_to_url(self, id_value): + return "https://store.steampowered.com/app/" + str(id_value) + + def scrape(self): + i = search_igdb_by_3p_url(self.url) + pd = i.scrape() if i else ResourceContent() + + headers = BasicDownloader.headers.copy() + headers['Host'] = 'store.steampowered.com' + headers['Cookie'] = "wants_mature_content=1; birthtime=754700401;" + content = BasicDownloader(self.url, headers=headers).download().html() + + title = content.xpath("//div[@class='apphub_AppName']/text()")[0] + developer = content.xpath("//div[@id='developers_list']/a/text()") + publisher = content.xpath("//div[@class='glance_ctn']//div[@class='dev_row'][2]//a/text()") + release_date = dateparser.parse( + content.xpath( + "//div[@class='release_date']/div[@class='date']/text()")[0] + ).strftime('%Y-%m-%d') + genre = content.xpath( + "//div[@class='details_block']/b[2]/following-sibling::a/text()") + platform = ['PC'] + brief = content.xpath( + "//div[@class='game_description_snippet']/text()")[0].strip() + # try Steam images if no image from IGDB + if pd.cover_image is None: + pd.metadata['cover_image_url'] = content.xpath("//img[@class='game_header_image_full']/@src")[0].replace("header.jpg", "library_600x900.jpg") + pd.cover_image, pd.cover_image_extention = BasicImageDownloader.download_image(pd.metadata['cover_image_url'], self.url) + if pd.cover_image is None: + pd.metadata['cover_image_url'] = content.xpath("//img[@class='game_header_image_full']/@src")[0] + pd.cover_image, pd.cover_image_extention = BasicImageDownloader.download_image(pd.metadata['cover_image_url'], self.url) + # merge data from IGDB, use localized Steam data if available + d = { + 'developer': developer, + 'publisher': publisher, + 'release_date': release_date, + 'genre': genre, + 'platform': platform, + } + d.update(pd.metadata) + pd.metadata = d + if title: + pd.metadata['title'] = title + if brief: + pd.metadata['brief'] = brief + return pd diff --git a/catalog/tests.py b/catalog/tests.py index 5f0aa779..952d0993 100644 --- a/catalog/tests.py +++ b/catalog/tests.py @@ -3,6 +3,7 @@ from catalog.book.tests import * from catalog.movie.tests import * from catalog.tv.tests import * from catalog.music.tests import * +from catalog.game.tests import * from catalog.podcast.tests import * from catalog.performance.tests import * diff --git a/test_data/https___store_steampowered_com_app_620 b/test_data/https___store_steampowered_com_app_620 new file mode 100644 index 00000000..d6d271ad --- /dev/null +++ b/test_data/https___store_steampowered_com_app_620 @@ -0,0 +1,2717 @@ + + + + + + + Steam 上的 Portal 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
+
+
+ + + +
+
+ + +
+
+ +
+ +
+ +
+
+ +
+ + +
+
+ + + + + +
+ + + +
+
+
+ +
+ +
+
+
+
+ + + + + +
+ + + +
+ + 类别 + 类别 + + +
+ + + + 点数商店 + + + + 新闻 + + + + 实验室 + + +
+
+ + +
+ +
+
+
+
+
+
+ + + + + + + + +
+ +
+ + +
+ + +
+ +
+ + + +
+ +
+ +
+
Portal 2
+
+ +
+ +
+ + +
+
+ + +
+ +
+ + + + +
+ +
+
+
+ + + + + + +
+
+ “终身测试计划”现已升级,您可以为您自己或您的好友设计合作谜题!
+ +
+
+ +
+
最近评测:
+
+ 好评如潮 + + (5,809) + + + - 过去 30 天内的 5,809 篇用户评测中有 98% 为好评。 +
+
+ +
+
全部评测:
+
+ 好评如潮 + + (255,776) + + + - 此游戏的 255,776 篇用户评测中有 98% 为好评。 + + + + + + +
+
+
+ +
+
发行日期:
+
2011 年 4 月 18 日
+
+ +
+
开发商:
+
+ Valve
+
+ +
+
发行商:
+
+ Valve
+
+ +
+ +
+ +
标签
+ + + + +
+
+
+
+ +
+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
+

想要将此项目添加至您的愿望单、关注它或标记为已忽略,请先登录

+
+
+ + + + +
+ + + + + + +
+ + + + + +
+ + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+
+
+ + + + + +
+
+

购买 Portal 2 - The Final Hours

+

Portal 2 - The Final Hours带你深入探索Valve办公室中之最高机密-为您揭开Portal2创作过程的神秘面纱的一本数字图书。现已新增一个免费章节!

+ +
+
+
+ $1.99
+ +
+
+
+
+
+
+
+ + + + + +
+
+

购买 Portal 2

+ +
+
+
+ $9.99
+ +
+
+
+
+
+ +
+
+ + + + +
+
+

+ 购买 Portal Bundle + 捆绑包 (?) + +

+ + +

+ 包含 2 件物品: + + Portal, Portal 2 +

+ +
+ +
+
-25%
-59%
$19.98
$8.23
+ + + + + + + +
+
+
+
+

此游戏的内容浏览所有(1)

+ +
+
+ + + + + +
+ +
+
+ + +
+ +
+ + + + + +
 
+ + + + + + +
+ + + + + + +
+
+
+
+

关于这款游戏

+ Portal 2 用创新的游戏内容、故事以及最初 Portal 那赢得超过 70 项以上业界奖项般的配乐打造出另一个大奖赢家的继承者。

Portal 2 的单人游戏部分带来充满活力的新角色、一大堆新鲜的解谜元素,以及更大、更加曲折的试验室。玩家将可以于前所未见的光圈科技实验室中探索,并且与在原游戏中引导你,偶尔想要把你杀掉的电脑陪伴者 GLaDOS 重逢。

本游戏的双人合作模式将有其独立的战役内容,包含独特的故事、试验室,以及两名新的玩家角色。这项新的游戏模式让玩家必须重新思考所有他们在 Portal 中所了解到的事实。想要过关不只要一起行动,还必须一起思考。

游戏特色
  • 丰富的单人游戏:次世代的游戏内容与令人注目的故事剧情。
  • 完整双人合作模式:多人连线内容具有其额外的故事、角色,以及游戏内容。
  • 高级物理系统:创造出前所未见的新领域,带来更加有趣的挑战,规模更大但不会更难的游戏。
  • 原创音乐。
  • 大作续集:全球超过 30 个媒体杂志将初代 Portal 选为 2007 年度游戏。
  • 编辑工具:Portal 2 编辑工具将会包含在内。
+
+ + + +
+

系统需求

+
+
+ Windows
+
+ macOS
+
+ SteamOS + Linux
+
+
+
+
+
+
    + 最低配置:
    • 操作系统: Windows 7 / Vista / XP
    • 处理器: 3.0 GHz P4, Dual Core 2.0 (or higher) or AMD64X2 (or higher)
    • 内存: 2 GB RAM
    • 显卡: Video card must be 128 MB or more and with support for Pixel Shader 2.0b (ATI Radeon X800 or higher / NVIDIA GeForce 7600 or higher / Intel HD Graphics 2000 or higher).
    • DirectX 版本: 9.0c
    • 存储空间: 需要 8 GB 可用空间
    • 声卡: DirectX 9.0c compatible
+
+
+
+
+
+
    + 最低配置:
    • 操作系统: MAC OS X 10.6.7 or higher
    • 处理器: Intel Core Duo Processor (2GHz or better)
    • 内存: 2 GB RAM
    • 显卡: ATI Radeon 2400 or higher / NVIDIA 8600M or higher / Intel HD Graphics 3000
    • 存储空间: 需要 8 GB 可用空间
+
+
+
+
+
+
    + 最低配置:
    • 操作系统: Ubuntu 12.04
    • 处理器: Dual core from Intel or AMD at 2.8 GHz
    • 内存: 2 GB RAM
    • 显卡: nVidia GeForce 8600/9600GT, ATI/AMD Radeon HD2600/3600 (Graphic Drivers: nVidia 310, AMD 12.11), OpenGL 2.1
    • 存储空间: 需要 8 GB 可用空间
    • 声卡: OpenAL Compatible Sound Card
+
+
+
+
+
+ + + +
+
+ +

社区自制模组 针对此游戏

+
+ +
+ + + + + + + + + +
+
+ +

鉴赏家点评

+
+ 4,057 名鉴赏家评测了这款产品。点击这里阅读。
+
+
+ + + +
+ +
+
+ + +
+
+ +
+

消费者评测

+ + + + + + + + +
+
+ + +
+
+
评测结果
+
+
+ +
+ +
+ + +
+
+
+
+
购得方式
+
+
+ +
+ +
+ + +
+
+
+
+
语言
+
+
+ +
+ +
+ +
+
+
+
+
日期范围
+
+
+
+ 要查看某个日期范围内的评测,请在上方图表中点击并拖动选定的范围或是点击某特定时间柱。

+ 显示图表 +
+ +
+ +
+ +
+
+
+
+
+
游戏时间
+
+
+ + + +
+ 按用户的游戏时间筛选此评测撰写时的评测:
+ + +
+ +
+ +
+ +
+ 无最低限制无最高限制 +
+ + +
+
+
+
+
+ 显示: + +
+ + + +
+ 显示图表
 
+ 隐藏图表
 
+
+ +
+
+ +
+
+
筛选条件
+ + +
+
+
+
+
排除跑题评测活动
+
游戏时间:
+
+ +
+ +
+ +
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
无其他评测符合上述筛选条件
+
调整上方筛选条件以查看其它评测
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
加载评测中…
+
+
+
+ +
+
+
+ + + + +
+ + +
+ +
+ + + + + + +
+ + + + + + + + + +
+ + + +
+ +
+ + \ No newline at end of file diff --git a/test_data/https___www_douban_com_game_10734307_ b/test_data/https___www_douban_com_game_10734307_ new file mode 100644 index 00000000..2d007b57 --- /dev/null +++ b/test_data/https___www_douban_com_game_10734307_ @@ -0,0 +1,2044 @@ + + + + + + + + + 传送门2 Portal 2 (豆瓣) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +

传送门2 Portal 2

+ +
+ + +
+ + + +
+
+ + + + + + + + +
+
类型:
+
+ 游戏 + / + 第一人称射击 + / + 益智 + / + 射击 + / + 动作 + +
+
平台:
+
+ + PC + / + Mac + / + PS3 + / + Xbox 360 + / + Linux + +
+
开发商:
+
+ Valve Corporation +
+
发行商:
+
+ Valve Corporation / Electronic Arts +
+
发行日期:
+
+ 2011-04-19 +
+
+ +
+
+ + + + + +
+
+ +
+ 9.5 + +
+
+ +
+
+ + + + + + 5星 + + + +
+ + 81.3% +
+ + + + 4星 + + + +
+ + 15.1% +
+ + + + 3星 + + + +
+ + 3.0% +
+ + + + 2星 + + + +
+ + 0.4% +
+ + + + 1星 + + + +
+ + 0.2% +
+
+ +
+ + +
+ + + + + + + + + +
+ + +
+ + + 评价: + + + + + + + + + + + + +
+
+ + + + +
+ 写文字 + 写攻略 + + + + + + 分享 +    + + + + + + + + + + + + + + +
+ + + + + +
+ + +
+
+ + + + + + +
+ +

+ 传送门2 的游戏视频 +  · · · · · · +  ( + + 全部 1 个 · 添加视频 + ) +

+ + +
+ + + + + +
+ +

+ 传送门2 的游戏图片 +  · · · · · · +  ( + + 全部82张 · 添加图片 + ) +

+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + +
+ + +

+ 传送门2 的短评 +  · · · · · · +  ( + + 全部2407条 + ) +

+ +
+ + + + + + + +
+ 热门 /  + 最新 /  + +
+
+
+ + + + +
    +
  • +
    + + + + +172 + 有用 + + + +

    + + 靠玩这个双人模式把了个妹。。 +

    +
    +
  • +
  • +
    + + + + +30 + 有用 + + + +

    + + 真实地被一款游戏震撼。每个关卡都经典到不行。对啊,我只剩下短暂而可悲的一生了。 +

    +
    +
  • +
  • +
    + + + + +77 + 有用 + + + +

    + + 神作,真的是神作,这他妈就是游戏中的《2001太空漫游》。游戏性,沉浸感和场景配乐设计上的美学都达到了极致,玩过《传送门2》,你再也不用玩任何一款单机了 +

    +
    +
  • +
  • +
    + + + + +26 + 有用 + + + +

    + + 论脑洞方面,v社真乱杀 +

    +
    +
  • +
  • +
    + + + + +14 + 有用 + + + +

    + + 我被GLaDOS大妈pua了,她真可爱 +

    +
    +
  • + + + +
+ + +
+
+
+
+ + > 全部2407条 + + + + +
+
+ + + + + + + + + +
+
+ + 我要写攻略 + +

+ 传送门2 Portal 2的攻略 · · · · · · + (全部 1 条) +

+
+ + + + + +
+ + + + + +
+
+ + + +
+ + + + + 巨牙海民 + + + + 2017-03-01 17:57:56 + + +
+ + +
+ +

+ +
+
+ + 没攻略我就写一个吧,这个游戏你唯一应该看攻略的地方就是:别犹豫了,买吧 这就像你小时候的某个幻想,然后有一群神帮你把它还原了。不是电影,是游戏,你可以参与其中的游戏。 如果你觉得纪念碑谷类的高分解密游戏其实在侮辱你的智商,那你一定要玩。 速度和加速度在门之间都... + +  (展开) +
+
+ + + + +
+
+
+ + + + + + + + + + +
+ + + + + + + + +
+ +
+
+ + + 我要写文字 + +

+ 传送门2 Portal 2的文字 · · · · · · + + ( 全部 24 条 ) +

+
+ +
+ 热门 / + 最新 / + 好友 +
+ + + + + + + +
+ + + + + +
+
+ + + +
+ + + + + 萨尔斯堡的树枝 + + + + 2022-02-04 21:19:01 + + +
+ + +
+ +

双人游戏的精髓不一定是爱情

+ +
+
+ + 失眠的第12个小时 我在很久没出现过的游戏群里问, “最近有啥游戏比较好玩的吗?” 然后根本忘记我提过这个问题, 下一秒打开《黑客帝国》看了起来。 (每年都会重看一遍1-3,本人还是小学生的时候第一次看黑客帝国觉得灵魂都被撼动了,太好看了以至于我觉得我都不配给它写影... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + 留象鸣 + + + + 2015-04-21 22:01:21 + + +
+ + +
+ +

绝境逢生,《传送门2》

+ +
+
+ + “如果把《传送门2》比做是普通成年人难度,那么相比之下移动设备上的《纪念碑谷》简直就是婴儿难度。” 《一代宗师》中宫羽田在他的南方引退仪式上比的不是武功,是想法。 2011年4月Valve公司发行了《传送门》的续作《传送门2》。作为一款另类的射击游戏,《传送门》比的... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + 小彼 + + + + 2015-05-04 01:29:56 + + +
+ + +
+ +

送你的女票一份Portal 2

+ +
+
+ + 正如Valve官方放出的某个宣传视频一样,这款游戏非常适合送给你的女票。当然,你要确保对方不晕3D,否则像这种上上下下飞来飞去的游戏要比普通的FPS更容易让人晕的。恋爱中的两人,尤其是对异地恋来说,没什么可一起做的事情,就一起玩游戏吧。 Portal 2支持双人coop,不多不少... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + Thesharing + + + + 2015-08-31 00:09:13 + + +
+ + +
+ +

当我听到最后炮塔的共鸣时,之前19个小时的解谜都是值得的

+ +
+
+ + 之前听室友说《传送门2》很好玩,在圣诞打折的时候入了正开始玩,玩到一半的时候实在为智商感到捉急,于是弃坑不玩。 寒假的时候闲着无事又捡起来玩,结果一口气通关,看到最后炮塔们共鸣齐响,感觉整个心都跟着它们一起动起来了。然后怒入《传送门》,把剧情补齐。 作为一个... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + Kensnow + + + + 2019-10-21 12:57:09 + + +
+ + +
+ +

我终其一生都在寻找天空

+ +
+
+ + 作为一个剧情狗,我向来比较少打解谜游戏,对科幻和废土朋克题材也不太感兴趣。之所以翻箱倒柜重新装好PS3玩传送门2的原因只有一个:它经常出现在某某游戏排行榜单的前几名,比如这次IGN TOP100中它又排在第3位,引发了我无尽的好奇心 在游戏的第一部分,我和所有萌新一样学习... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + vegue + + + + 2016-01-30 11:09:45 + + +
+ + +
+ +

一个人挑战双人模式的传送门

+ +
+
+ + 传送门2与第一版的一个比较大的区别是增加了双人模式,极大地丰富了游戏的体验。去年买了传送门2的XBox的正版碟,一番苦战后打完单人模式,觉得这游戏特别给力,脑洞够大,关卡设计合理,画面也还不错。而且我很喜欢GlaDOS, EVA这样有点阴谋论的东西。 后来玩双人模式... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + 嘉术2015 + + + + 2020-02-24 17:52:31 + + +
+ + +
+ +

为防除了我之外也有人没看懂,解释一下glados的“悖论攻击”

+ +
+
+ + “这句是错的”前半句就是悖论本身,后半句“别想了,别想了”是glados对自己说的。” 这是很有名的“说谎悖论” “这句话是错的”如果是错的,那它表达的意思就是对的,如果是对的,就又和句子本身的意思相矛盾。 对于只能用逻辑和算法思考的ai,一直深入去想就只能是短路了,... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + ❤INABA❤ + + + + 2020-05-23 14:46:10 + + +
+ + +
+ +

传送门的物理和视觉特性

+ +
+
+ + 游戏的核心是玩家使用名为"光圈科技手持传送门设备"(Aperture Science Handheld Portal Device,简称"传送门枪"),来通过各种难关。所有的关卡目的都很类似,那就是将一个具有重量的物体放在一个圆形的按钮(游戏内名称"光圈科学超大型撞击按钮")上,通常这个物体是一个方块体(... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + ┟西﹎;瓜╃ + + + + 2018-07-01 16:24:37 + + +
+ + +
+ +

传送门:小游戏策划的笔记

+ +
+
+ + 每次玩PORTAL2都会被3D晕到恶心,每通过三四关就要关电脑躺下来,隔段时间才能继续。但是这是一个“通关就能说明我聪明”的游戏,作为一个喜欢向别人炫耀智商的人怎么可能不被通关的成就感吸引呢.... 于是忍着恶心打到了最后,原以为就是一个解谜通关的冰冷游戏但是却被结尾的... + +  (展开) +
+
+ + + + +
+
+
+ + + +
+
+ + + +
+ + + + + 小坑坑 + + + + 2021-12-06 20:41:50 + + +
+ + +
+ +

朴素的游戏设计理念与浪漫主义的基调

+ +
+
+ + 通关传送门系列确实是一次非常独特的美妙的游戏旅程,从一个速通视频入坑,想要趁着秋促看看传送门系列是否打折,却发现其实已在库中。早在起源引擎的csgo里摸爬滚打了很久,却是第一款真正让我有些晕3D的游戏,这大概是不停旋转、重力方向更迭的游戏画面惹的祸吧。尽管如此,... + +  (展开) +
+
+ + + + +
+
+
+ + + + + + + + + + +
+ + + + + + + + +

+ > + + 更多文字 + 24篇 + +

+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

+ 以下豆列推荐 +  · · · · · · +  ( + + 全部 + ) +

+ +
+ +
+ + + +
+ + +
+
+ +

+ 谁玩这部游戏 +  · · · · · · +

+ +
+
+ + + + + + + + +
+
+ +
+
+ +

+ 游戏资料贡献者 +  · · · · · · +  ( + + 全部14人 + ) +

+ +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ +
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test_data/igdb_games_fields____cover_url__genres_name__platforms_name__involved_companies____involved_companies_company_name__where_url____https___www_igdb_com_games_portal_2__ b/test_data/igdb_games_fields____cover_url__genres_name__platforms_name__involved_companies____involved_companies_company_name__where_url____https___www_igdb_com_games_portal_2__ new file mode 100644 index 00000000..3ea6fa21 --- /dev/null +++ b/test_data/igdb_games_fields____cover_url__genres_name__platforms_name__involved_companies____involved_companies_company_name__where_url____https___www_igdb_com_games_portal_2__ @@ -0,0 +1 @@ +[{"id": 72, "age_ratings": [11721, 32022, 47683, 47684, 47685, 47686, 47687, 91785], "aggregated_rating": 92.4444444444444, "aggregated_rating_count": 13, "alternative_names": [50135], "artworks": [36972], "bundles": [55025, 191406], "category": 0, "collection": 87, "cover": {"id": 82660, "url": "//images.igdb.com/igdb/image/upload/t_thumb/co1rs4.jpg"}, "created_at": 1297956069, "dlcs": [99969, 114140], "external_games": [15150, 73156, 81867, 92870, 92979, 137388, 189642, 214010, 245334, 403070, 1303428, 1929756, 1931953, 2082680, 2161690, 2590310, 2600814], "first_release_date": 1303171200, "follows": 971, "franchises": [1724], "game_engines": [3], "game_modes": [1, 2, 3, 4], "genres": [{"id": 5, "name": "Shooter"}, {"id": 8, "name": "Platform"}, {"id": 9, "name": "Puzzle"}, {"id": 31, "name": "Adventure"}], "involved_companies": [{"id": 106733, "company": {"id": 56, "name": "Valve Corporation"}, "created_at": 1598486400, "developer": true, "game": 72, "porting": false, "publisher": true, "supporting": false, "updated_at": 1598486400, "checksum": "fa403088-a40a-1d83-16be-a68849472a6d"}, {"id": 106734, "company": {"id": 1, "name": "Electronic Arts"}, "created_at": 1598486400, "developer": false, "game": 72, "porting": false, "publisher": true, "supporting": false, "updated_at": 1598486400, "checksum": "53e59e19-f746-1195-c4e7-2b388e621317"}], "keywords": [350, 453, 575, 592, 1026, 1158, 1181, 1293, 1440, 1559, 1761, 2071, 2800, 3984, 4004, 4134, 4145, 4162, 4266, 4345, 4363, 4428, 4575, 4578, 4617, 4644, 4725, 4888, 4944, 4956, 4974, 5185, 5261, 5633, 5772, 5935, 5938, 5956, 6137, 6326, 6735, 6854, 7079, 7172, 7313, 7535, 7570, 7579, 8141, 8262, 8896, 9814, 10435, 11023, 11208, 12516, 14224, 18139, 18567, 27032], "multiplayer_modes": [11591, 11592, 11593, 11594, 11595], "name": "Portal 2", "platforms": [{"id": 3, "name": "Linux"}, {"id": 6, "name": "PC (Microsoft Windows)"}, {"id": 9, "name": "PlayStation 3"}, {"id": 12, "name": "Xbox 360"}, {"id": 14, "name": "Mac"}], "player_perspectives": [1], "rating": 91.6894220983232, "rating_count": 2765, "release_dates": [104964, 104965, 208203, 208204, 208205, 208206, 208207, 208208], "screenshots": [725, 726, 727, 728, 729], "similar_games": [71, 1877, 7350, 11646, 16992, 22387, 28070, 55038, 55190, 56033], "slug": "portal-2", "storyline": "You lost your memory, you are alone in a world full of danger, and your mission is survive using your mind. The only way to get out from this hell is.....Hi i'm GLAdOS, and welcome to the amazing world of portal 2, here i will expose you to a lot of tests, and try to k.. help Aperture Science envolve in a new era.\nYour job is advance in the levels i propose and get better and better, you will have an portal gun to help you, and remember nothing is impossible if you try, and try again and again and again....\nThe puzzles are waiting for you!", "summary": "Sequel to the acclaimed Portal (2007), Portal 2 pits the protagonist of the original game, Chell, and her new robot friend, Wheatley, against more puzzles conceived by GLaDOS, an A.I. with the sole purpose of testing the Portal Gun's mechanics and taking revenge on Chell for the events of Portal. As a result of several interactions and revelations, Chell once again pushes to escape Aperture Science Labs.", "tags": [1, 18, 27, 268435461, 268435464, 268435465, 268435487, 536871262, 536871365, 536871487, 536871504, 536871938, 536872070, 536872093, 536872205, 536872352, 536872471, 536872673, 536872983, 536873712, 536874896, 536874916, 536875046, 536875057, 536875074, 536875178, 536875257, 536875275, 536875340, 536875487, 536875490, 536875529, 536875556, 536875637, 536875800, 536875856, 536875868, 536875886, 536876097, 536876173, 536876545, 536876684, 536876847, 536876850, 536876868, 536877049, 536877238, 536877647, 536877766, 536877991, 536878084, 536878225, 536878447, 536878482, 536878491, 536879053, 536879174, 536879808, 536880726, 536881347, 536881935, 536882120, 536883428, 536885136, 536889051, 536889479, 536897944], "themes": [1, 18, 27], "total_rating": 92.0669332713838, "total_rating_count": 2778, "updated_at": 1670514780, "url": "https://www.igdb.com/games/portal-2", "videos": [432, 16451, 17844, 17845], "websites": [17869, 17870, 41194, 41195, 150881, 150882, 150883, 296808], "checksum": "bcca1b61-2b30-13b8-a0ec-faf45d2ffdad", "game_localizations": [726]}] \ No newline at end of file diff --git a/test_data/igdb_games_fields____cover_url__genres_name__platforms_name__involved_companies____involved_companies_company_name__where_url____https___www_igdb_com_games_the_legend_of_zelda_breath_of_the_wild__ b/test_data/igdb_games_fields____cover_url__genres_name__platforms_name__involved_companies____involved_companies_company_name__where_url____https___www_igdb_com_games_the_legend_of_zelda_breath_of_the_wild__ new file mode 100644 index 00000000..bae37cd7 --- /dev/null +++ b/test_data/igdb_games_fields____cover_url__genres_name__platforms_name__involved_companies____involved_companies_company_name__where_url____https___www_igdb_com_games_the_legend_of_zelda_breath_of_the_wild__ @@ -0,0 +1 @@ +[{"id": 7346, "age_ratings": [10942, 32771, 47171, 55134, 62638, 91197, 97621], "aggregated_rating": 97.5925925925926, "aggregated_rating_count": 31, "alternative_names": [24387, 50095, 51797, 51798, 71751, 111231], "artworks": [3469, 3470, 3471, 3472, 3473, 3474, 3476, 3477, 3478, 6227, 6228, 6229], "category": 0, "collection": 106, "cover": {"id": 172453, "url": "//images.igdb.com/igdb/image/upload/t_thumb/co3p2d.jpg"}, "created_at": 1402423357, "dlcs": [41825, 41826], "external_games": [37291, 119375, 189477, 221008, 221009, 250912, 1865369, 1865474, 1865566, 1865659, 1865752, 1865856, 1865904, 1928313, 1928447, 1928741, 1929066, 1930805, 1931125, 1931363, 1931396, 1931548, 1932118, 1932469, 1932898, 1934231, 1936310, 1936680, 1937428, 1937711, 2579890, 2595359, 2596353], "first_release_date": 1488499200, "follows": 694, "franchises": [596], "game_engines": [17], "game_modes": [1], "genres": [{"id": 12, "name": "Role-playing (RPG)"}, {"id": 31, "name": "Adventure"}], "hypes": 142, "involved_companies": [{"id": 131267, "company": {"id": 70, "name": "Nintendo"}, "created_at": 1620735418, "developer": true, "game": 7346, "porting": false, "publisher": true, "supporting": false, "updated_at": 1620816049, "checksum": "3afa08b7-684d-4481-9344-17334be3d6fa"}], "keywords": [64, 69, 132, 174, 227, 296, 301, 510, 637, 701, 758, 966, 1033, 1181, 1309, 1381, 1459, 1524, 1632, 1710, 1735, 2030, 2112, 2141, 2154, 2177, 2209, 2229, 2242, 2280, 2324, 2414, 2438, 2446, 2451, 2452, 2472, 2561, 2823, 3433, 4161, 4166, 4169, 4179, 4187, 4221, 4257, 4266, 4285, 4350, 4359, 4392, 4397, 4399, 4408, 4419, 4423, 4438, 4466, 4509, 4523, 4525, 4578, 4717, 4745, 4767, 4843, 4848, 4852, 4866, 4883, 4918, 4928, 4931, 4951, 5000, 5022, 5023, 5095, 5308, 5315, 5320, 5323, 5331, 5349, 5578, 5625, 5643, 5644, 5651, 5655, 5663, 5694, 5896, 5964, 5977, 6079, 6135, 6163, 6202, 6213, 6221, 6403, 6600, 6653, 6795, 6810, 6820, 6870, 7080, 7217, 7218, 7416, 7423, 7611, 7612, 7621, 8969, 9199, 9207, 9212, 9265, 9278, 9380, 9599, 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608, 9609, 9610, 12039, 12078, 12079, 12080, 12081, 12082, 12083, 12084, 12085, 12086, 12087, 12556, 14051, 14052, 14096, 14097, 15074, 15075, 15076, 17426, 23835, 29810, 32559, 32560, 32561, 32562, 32563, 32564, 32565], "name": "The Legend of Zelda: Breath of the Wild", "platforms": [{"id": 41, "name": "Wii U"}, {"id": 130, "name": "Nintendo Switch"}], "player_perspectives": [2], "rating": 92.26694983017181, "rating_count": 1689, "release_dates": [64163, 64164], "screenshots": [30198, 30199, 30200, 30201, 32659, 176250, 176251, 176252, 176253, 176254, 176255, 176256], "similar_games": [359, 472, 534, 1029, 1036, 1864, 1942, 3025, 11156, 19560], "slug": "the-legend-of-zelda-breath-of-the-wild", "storyline": "Link awakes in a mysterious chamber after 100 years of slumber to find that Calamity Ganon has taken over Hyrule Castle and left Hyrule to decay and be taken over by nature.", "summary": "In this 3D open-world entry in the Zelda series, Link is awakened from a deep slumber without his past memories in the post-apocalyptic Kingdom of Hyrule, and sets off on a journey to defeat the ancient evil Calamity Ganon. Link treks, climbs and glides through fields, forests and mountain ranges while meeting and helping friendly folk and defeating enemies in order to gather up the strength to face Ganon.", "tags": [1, 17, 21, 31, 33, 38, 268435468, 268435487, 536870976, 536870981, 536871044, 536871086, 536871139, 536871208, 536871213, 536871422, 536871549, 536871613, 536871670, 536871878, 536871945, 536872093, 536872221, 536872293, 536872371, 536872436, 536872544, 536872622, 536872647, 536872942, 536873024, 536873053, 536873066, 536873089, 536873121, 536873141, 536873154, 536873192, 536873236, 536873326, 536873350, 536873358, 536873363, 536873364, 536873384, 536873473, 536873735, 536874345, 536875073, 536875078, 536875081, 536875091, 536875099, 536875133, 536875169, 536875178, 536875197, 536875262, 536875271, 536875304, 536875309, 536875311, 536875320, 536875331, 536875335, 536875350, 536875378, 536875421, 536875435, 536875437, 536875490, 536875629, 536875657, 536875679, 536875755, 536875760, 536875764, 536875778, 536875795, 536875830, 536875840, 536875843, 536875863, 536875912, 536875934, 536875935, 536876007, 536876220, 536876227, 536876232, 536876235, 536876243, 536876261, 536876490, 536876537, 536876555, 536876556, 536876563, 536876567, 536876575, 536876606, 536876808, 536876876, 536876889, 536876991, 536877047, 536877075, 536877114, 536877125, 536877133, 536877315, 536877512, 536877565, 536877707, 536877722, 536877732, 536877782, 536877992, 536878129, 536878130, 536878328, 536878335, 536878523, 536878524, 536878533, 536879881, 536880111, 536880119, 536880124, 536880177, 536880190, 536880292, 536880511, 536880512, 536880513, 536880514, 536880515, 536880516, 536880517, 536880518, 536880519, 536880520, 536880521, 536880522, 536882951, 536882990, 536882991, 536882992, 536882993, 536882994, 536882995, 536882996, 536882997, 536882998, 536882999, 536883468, 536884963, 536884964, 536885008, 536885009, 536885986, 536885987, 536885988, 536888338, 536894747, 536900722, 536903471, 536903472, 536903473, 536903474, 536903475, 536903476, 536903477], "themes": [1, 17, 21, 31, 33, 38], "total_rating": 94.9297712113822, "total_rating_count": 1720, "updated_at": 1670280136, "url": "https://www.igdb.com/games/the-legend-of-zelda-breath-of-the-wild", "videos": [2613, 8544, 11112, 11648, 45596, 45597], "websites": [12644, 12645, 12646, 65034, 169666, 169667, 169668, 169669, 169670], "checksum": "448bbb33-d5e8-9908-7d2c-47dad47eea89", "game_localizations": [574, 575, 937]}] \ No newline at end of file diff --git a/test_data/igdb_websites_fields____game____where_url____https___store_steampowered_com_app_620__ b/test_data/igdb_websites_fields____game____where_url____https___store_steampowered_com_app_620__ new file mode 100644 index 00000000..6935afe1 --- /dev/null +++ b/test_data/igdb_websites_fields____game____where_url____https___store_steampowered_com_app_620__ @@ -0,0 +1 @@ +[{"id": 17870, "category": 13, "game": {"id": 72, "age_ratings": [11721, 32022, 47683, 47684, 47685, 47686, 47687, 91785], "aggregated_rating": 92.4444444444444, "aggregated_rating_count": 13, "alternative_names": [50135], "artworks": [36972], "bundles": [55025, 191406], "category": 0, "collection": 87, "cover": 82660, "created_at": 1297956069, "dlcs": [99969, 114140], "external_games": [15150, 73156, 81867, 92870, 92979, 137388, 189642, 214010, 245334, 403070, 1303428, 1929756, 1931953, 2082680, 2161690, 2590310, 2600814], "first_release_date": 1303171200, "follows": 971, "franchises": [1724], "game_engines": [3], "game_modes": [1, 2, 3, 4], "genres": [5, 8, 9, 31], "involved_companies": [106733, 106734], "keywords": [350, 453, 575, 592, 1026, 1158, 1181, 1293, 1440, 1559, 1761, 2071, 2800, 3984, 4004, 4134, 4145, 4162, 4266, 4345, 4363, 4428, 4575, 4578, 4617, 4644, 4725, 4888, 4944, 4956, 4974, 5185, 5261, 5633, 5772, 5935, 5938, 5956, 6137, 6326, 6735, 6854, 7079, 7172, 7313, 7535, 7570, 7579, 8141, 8262, 8896, 9814, 10435, 11023, 11208, 12516, 14224, 18139, 18567, 27032], "multiplayer_modes": [11591, 11592, 11593, 11594, 11595], "name": "Portal 2", "platforms": [3, 6, 9, 12, 14], "player_perspectives": [1], "rating": 91.6894220983232, "rating_count": 2765, "release_dates": [104964, 104965, 208203, 208204, 208205, 208206, 208207, 208208], "screenshots": [725, 726, 727, 728, 729], "similar_games": [71, 1877, 7350, 11646, 16992, 22387, 28070, 55038, 55190, 56033], "slug": "portal-2", "storyline": "You lost your memory, you are alone in a world full of danger, and your mission is survive using your mind. The only way to get out from this hell is.....Hi i'm GLAdOS, and welcome to the amazing world of portal 2, here i will expose you to a lot of tests, and try to k.. help Aperture Science envolve in a new era.\nYour job is advance in the levels i propose and get better and better, you will have an portal gun to help you, and remember nothing is impossible if you try, and try again and again and again....\nThe puzzles are waiting for you!", "summary": "Sequel to the acclaimed Portal (2007), Portal 2 pits the protagonist of the original game, Chell, and her new robot friend, Wheatley, against more puzzles conceived by GLaDOS, an A.I. with the sole purpose of testing the Portal Gun's mechanics and taking revenge on Chell for the events of Portal. As a result of several interactions and revelations, Chell once again pushes to escape Aperture Science Labs.", "tags": [1, 18, 27, 268435461, 268435464, 268435465, 268435487, 536871262, 536871365, 536871487, 536871504, 536871938, 536872070, 536872093, 536872205, 536872352, 536872471, 536872673, 536872983, 536873712, 536874896, 536874916, 536875046, 536875057, 536875074, 536875178, 536875257, 536875275, 536875340, 536875487, 536875490, 536875529, 536875556, 536875637, 536875800, 536875856, 536875868, 536875886, 536876097, 536876173, 536876545, 536876684, 536876847, 536876850, 536876868, 536877049, 536877238, 536877647, 536877766, 536877991, 536878084, 536878225, 536878447, 536878482, 536878491, 536879053, 536879174, 536879808, 536880726, 536881347, 536881935, 536882120, 536883428, 536885136, 536889051, 536889479, 536897944], "themes": [1, 18, 27], "total_rating": 92.0669332713838, "total_rating_count": 2778, "updated_at": 1670514780, "url": "https://www.igdb.com/games/portal-2", "videos": [432, 16451, 17844, 17845], "websites": [17869, 17870, 41194, 41195, 150881, 150882, 150883, 296808], "checksum": "bcca1b61-2b30-13b8-a0ec-faf45d2ffdad", "game_localizations": [726]}, "trusted": true, "url": "https://store.steampowered.com/app/620", "checksum": "5281f967-6dfe-7658-96c6-af00ce010bbc"}] \ No newline at end of file diff --git a/test_data/igdb_websites_fields____where_game_url____https___www_igdb_com_games_portal_2__ b/test_data/igdb_websites_fields____where_game_url____https___www_igdb_com_games_portal_2__ new file mode 100644 index 00000000..2e05628c --- /dev/null +++ b/test_data/igdb_websites_fields____where_game_url____https___www_igdb_com_games_portal_2__ @@ -0,0 +1 @@ +[{"id": 17869, "category": 1, "game": 72, "trusted": false, "url": "http://www.thinkwithportals.com/", "checksum": "c40d590f-93bd-b86e-243c-73746c08be3b"}, {"id": 17870, "category": 13, "game": 72, "trusted": true, "url": "https://store.steampowered.com/app/620", "checksum": "5281f967-6dfe-7658-96c6-af00ce010bbc"}, {"id": 41194, "category": 3, "game": 72, "trusted": true, "url": "https://en.wikipedia.org/wiki/Portal_2", "checksum": "7354f471-16d6-5ed9-b4e4-049cceaab562"}, {"id": 41195, "category": 4, "game": 72, "trusted": true, "url": "https://www.facebook.com/Portal", "checksum": "035f6b48-3be1-77d5-1567-cf6fd8116ee7"}, {"id": 150881, "category": 9, "game": 72, "trusted": true, "url": "https://www.youtube.com/user/Valve", "checksum": "c1d4afb9-e96d-02f1-73bd-3384622e6aee"}, {"id": 150882, "category": 5, "game": 72, "trusted": true, "url": "https://twitter.com/valvesoftware", "checksum": "62bb9586-3293-bb01-f675-d65323ae371c"}, {"id": 150883, "category": 2, "game": 72, "trusted": false, "url": "https://theportalwiki.com/wiki/Portal_2", "checksum": "af689276-28c8-b145-7b19-f1d7df878c2a"}, {"id": 296808, "category": 6, "game": 72, "trusted": true, "url": "https://www.twitch.tv/directory/game/Portal%202", "checksum": "65629340-6190-833d-41b1-8eaf31918df3"}] \ No newline at end of file diff --git a/test_data/igdb_websites_fields____where_game_url____https___www_igdb_com_games_the_legend_of_zelda_breath_of_the_wild__ b/test_data/igdb_websites_fields____where_game_url____https___www_igdb_com_games_the_legend_of_zelda_breath_of_the_wild__ new file mode 100644 index 00000000..3303e0b1 --- /dev/null +++ b/test_data/igdb_websites_fields____where_game_url____https___www_igdb_com_games_the_legend_of_zelda_breath_of_the_wild__ @@ -0,0 +1 @@ +[{"id": 12644, "category": 1, "game": 7346, "trusted": false, "url": "http://www.zelda.com/breath-of-the-wild/", "checksum": "3d2ca280-a2d0-5664-c8a5-69eeeaf13558"}, {"id": 12645, "category": 2, "game": 7346, "trusted": false, "url": "http://zelda.wikia.com/wiki/The_Legend_of_Zelda:_Breath_of_the_Wild", "checksum": "d5cb4657-dc8e-9de1-9643-b1ef64812d9f"}, {"id": 12646, "category": 3, "game": 7346, "trusted": true, "url": "https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_Breath_of_the_Wild", "checksum": "c4570c3c-3a04-8d24-399a-0c04a17e7c56"}, {"id": 65034, "category": 14, "game": 7346, "trusted": true, "url": "https://www.reddit.com/r/Breath_of_the_Wild", "checksum": "f60505b3-18a4-3d60-9db2-febe4c6cb492"}, {"id": 169666, "category": 6, "game": 7346, "trusted": true, "url": "https://www.twitch.tv/nintendo", "checksum": "e2b20791-a9c4-76ad-4d76-3e7abc9148bb"}, {"id": 169667, "category": 9, "game": 7346, "trusted": true, "url": "https://www.youtube.com/nintendo", "checksum": "1e1c08ba-8f89-b567-0029-1d8aac22d147"}, {"id": 169668, "category": 4, "game": 7346, "trusted": true, "url": "https://www.facebook.com/Nintendo", "checksum": "046d8c8e-8f1d-8813-1266-c2911f490ba7"}, {"id": 169669, "category": 5, "game": 7346, "trusted": true, "url": "https://twitter.com/NintendoAmerica", "checksum": "e06dd12f-b6c5-ef72-f287-a9cebba12fa1"}, {"id": 169670, "category": 8, "game": 7346, "trusted": true, "url": "https://www.instagram.com/nintendo", "checksum": "dbff9e02-e9c2-f395-7e48-7e70cf58225c"}] \ No newline at end of file