diff --git a/lncrawl/core/browser.py b/lncrawl/core/browser.py index aa608f379..bfca3bbc6 100644 --- a/lncrawl/core/browser.py +++ b/lncrawl/core/browser.py @@ -21,6 +21,7 @@ def __init__( timeout: Optional[int] = 120, options: Optional["ChromeOptions"] = None, cookie_store: Optional[RequestsCookieJar] = None, + browser_storage: Optional[dict] = None, soup_maker: Optional[SoupMaker] = None, ) -> None: """ @@ -31,6 +32,7 @@ def __init__( - timeout (Optional[int], optional): Maximum wait duration in seconds for an element to be available. Default: 120. - options (Optional["ChromeOptions"], optional): Webdriver options. Default: None. - cookie_store (Optional[RequestsCookieJar], optional): A cookie store to synchronize cookies. Default: None. + - browser_storage (Optional[dict], optional): A Storage to save some user info that is saved in your Browser storage. Default: None. - soup_parser (Optional[str], optional): Parser for page content. Default: None. """ self._driver: WebDriver = None @@ -38,6 +40,7 @@ def __init__( self.timeout = timeout self.headless = headless self.cookie_store = cookie_store + self.browser_storage = browser_storage self.soup_maker = soup_maker or SoupMaker() def __del__(self): @@ -72,36 +75,52 @@ def _init_browser(self): def _apply_cookies(self): if not self._driver: return - if not isinstance(self.cookie_store, RequestsCookieJar): - return - for cookie in self.cookie_store: - self._driver.add_cookie( - { - "name": cookie.name, - "value": cookie.value, - "path": cookie.path, - "domain": cookie.domain, - "secure": cookie.secure, - "expiry": cookie.expires, - } - ) - logger.debug("Cookies applied: %s", self._driver.get_cookies()) + if isinstance(self.cookie_store, RequestsCookieJar): + for cookie in self.cookie_store: + self._driver.add_cookie( + { + "name": cookie.name, + "value": cookie.value, + "path": cookie.path, + "domain": cookie.domain, + "secure": cookie.secure, + "expiry": cookie.expires, + } + ) + logger.debug("Cookies applied: %s", self._driver.get_cookies()) + if isinstance(self.browser_storage, dict): + for key, value in self.browser_storage['localStorage'].items(): + self._driver.execute_script("window.localStorage.setItem(arguments[0], arguments[1]);", key, value) + for key, value in self.browser_storage['sessionStorage'].items(): + self._driver.execute_script("window.sessionStorage.setItem(arguments[0], arguments[1]);", key, value) + logger.debug("Storage applied: %s", self.browser_storage) def _restore_cookies(self): if not self._driver: return - if not isinstance(self.cookie_store, RequestsCookieJar): - return - for cookie in self._driver.get_cookies(): - self.cookie_store.set( - name=cookie.get("name"), - value=cookie.get("value"), - path=cookie.get("path"), - domain=cookie.get("domain"), - secure=cookie.get("secure"), - expires=cookie.get("expiry"), - ) - logger.debug("Cookies retrieved: %s", self.cookie_store) + if isinstance(self.cookie_store, RequestsCookieJar): + for cookie in self._driver.get_cookies(): + self.cookie_store.set( + name=cookie.get("name"), + value=cookie.get("value"), + path=cookie.get("path"), + domain=cookie.get("domain"), + secure=cookie.get("secure"), + expires=cookie.get("expiry"), + ) + logger.debug("Cookies retrieved: %s", self.cookie_store) + if isinstance(self.browser_storage, dict): + self.browser_storage['localStorage'] = self._driver.execute_script( + "var ls = window.localStorage, items = {}; " + "for (var i = 0, k; i < ls.length; ++i) " + " items[k = ls.key(i)] = ls.getItem(k); " + "return items; ") + self.browser_storage['sessionStorage'] = self._driver.execute_script( + "var ls = window.sessionStorage, items = {}; " + "for (var i = 0, k; i < ls.length; ++i) " + " items[k = ls.key(i)] = ls.getItem(k); " + "return items; ") + logger.debug("Storage retrieved: %s", self.browser_storage) @property def active(self): diff --git a/sources/en/w/wuxiacom.py b/sources/en/w/wuxiacom.py index 952d3c091..630d58ad2 100644 --- a/sources/en/w/wuxiacom.py +++ b/sources/en/w/wuxiacom.py @@ -19,7 +19,7 @@ class WuxiaComCrawler(BasicBrowserTemplate): ] def initialize(self): - #self.headless = True + # self.headless = True self.api_url = "https://api2.wuxiaworld.com" self.grpc = RpcSession.from_descriptor(WUXIWORLD_PROTO) self.grpc._session = self.scraper @@ -28,11 +28,14 @@ def initialize(self): self.bearer_token = None self.cleaner.unchanged_tags.update(["span"]) self.start_download_chapter_body_in_browser = False - self.localstorageuser = 'oidc.user:https://identity.wuxiaworld.com:wuxiaworld_spa' + self.localstorageuser = 'oidc.user:' \ + 'https://identity.wuxiaworld.com' \ + ':wuxiaworld_spa' def login(self, email: str, password: str) -> None: # Login now will use Bearer Token if supplied as main login method, - # and if username used it will exctract Bearer Token and use it for further login process + # and if username used it will exctract Bearer Token + # and use it for further login process if email == 'Bearer': logger.debug("login type: %s", email) self.bearer_token = email + " " + password @@ -48,11 +51,14 @@ def login(self, email: str, password: str) -> None: self.browser.find("button").click() try: # Testing if logging has succeeded - self.browser.wait("//h2[normalize-space()='Your Profile']", By.XPATH, 10) - self.browser.find("//h2[normalize-space()='Your Profile']", By.XPATH) + self.browser.wait("//h2[normalize-space()='Your Profile']", + By.XPATH, 10) + self.browser.find("//h2[normalize-space()='Your Profile']", + By.XPATH) storage = LocalStorage(self.browser._driver) if storage.has(self.localstorageuser): - self.bearer_token = '{token_type} {access_token}'.format(**json.loads(storage[self.localstorageuser])) + self.bearer_token = '{token_type} {access_token}'.format( + **json.loads(storage[self.localstorageuser])) except Exception as e: logger.debug("login Email: Failed", e) @@ -209,8 +215,10 @@ def read_novel_info_in_browser(self) -> None: if author_tag: self.novel_author = author_tag.text.strip() - # Open chapters menu (note: the order of tabs in novel info change whether if you are logged in or not) - if len(self.browser.find_all('//*[starts-with(@id, "full-width-tab-")]', By.XPATH)) == 3: + # Open chapters menu (note: the order of tabs in novel info + # change whether if you are logged in or not) + if len(self.browser.find_all('//*[starts-with(@id, "full-width-tab-")]', + By.XPATH)) == 3: self.browser.click("#novel-tabs #full-width-tab-0") self.browser.wait("#full-width-tabpanel-0 .MuiAccordion-root") else: @@ -295,13 +303,13 @@ def download_chapter_body_in_browser(self, chapter: Chapter) -> str: # Class for reading localStorage from Browser class LocalStorage: - def __init__(self, driver) : + def __init__(self, driver): self.driver = driver def __len__(self): return self.driver.execute_script("return window.localStorage.length;") - def items(self) : + def items(self): return self.driver.execute_script( "var ls = window.localStorage, items = {}; " "for (var i = 0, k; i < ls.length; ++i) " @@ -309,7 +317,7 @@ def items(self) : "return items; " ) - def keys(self) : + def keys(self): return self.driver.execute_script( "var ls = window.localStorage, keys = []; " "for (var i = 0; i < ls.length; ++i) " @@ -318,21 +326,25 @@ def keys(self) : ) def get(self, key): - return self.driver.execute_script("return window.localStorage.getItem(arguments[0]);", key) + return self.driver.execute_script( + "return window.localStorage.getItem(arguments[0]);", key) def set(self, key, value): - self.driver.execute_script("window.localStorage.setItem(arguments[0], arguments[1]);", key, value) + self.driver.execute_script( + "window.localStorage.setItem(arguments[0], arguments[1]);", + key, value) def has(self, key): return key in self.keys() def remove(self, key): - self.driver.execute_script("window.localStorage.removeItem(arguments[0]);", key) + self.driver.execute_script( + "window.localStorage.removeItem(arguments[0]);", key) def clear(self): self.driver.execute_script("window.localStorage.clear();") - def __getitem__(self, key) : + def __getitem__(self, key): value = self.get(key) if value is None: raise KeyError(key) @@ -352,7 +364,226 @@ def __repr__(self): WUXIWORLD_PROTO = json.loads( -""" -{"file": [{"name": "google/protobuf/wrappers.proto", "package": "google.protobuf", "messageType": [{"name": "DoubleValue", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_DOUBLE", "jsonName": "value"}]}, {"name": "FloatValue", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_FLOAT", "jsonName": "value"}]}, {"name": "Int64Value", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT64", "jsonName": "value"}]}, {"name": "UInt64Value", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_UINT64", "jsonName": "value"}]}, {"name": "Int32Value", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "value"}]}, {"name": "UInt32Value", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_UINT32", "jsonName": "value"}]}, {"name": "BoolValue", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "value"}]}, {"name": "StringValue", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "value"}]}, {"name": "BytesValue", "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_BYTES", "jsonName": "value"}]}], "options": {"javaPackage": "com.google.protobuf", "javaOuterClassname": "WrappersProto", "javaMultipleFiles": true, "goPackage": "google.golang.org/protobuf/types/known/wrapperspb", "ccEnableArenas": true, "objcClassPrefix": "GPB", "csharpNamespace": "Google.Protobuf.WellKnownTypes"}, "syntax": "proto3"}, {"name": "google/protobuf/timestamp.proto", "package": "google.protobuf", "messageType": [{"name": "Timestamp", "field": [{"name": "seconds", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT64", "jsonName": "seconds"}, {"name": "nanos", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "nanos"}]}], "options": {"javaPackage": "com.google.protobuf", "javaOuterClassname": "TimestampProto", "javaMultipleFiles": true, "goPackage": "google.golang.org/protobuf/types/known/timestamppb", "ccEnableArenas": true, "objcClassPrefix": "GPB", "csharpNamespace": "Google.Protobuf.WellKnownTypes"}, "syntax": "proto3"}, {"name": "wuxia.proto", "package": "wuxiaworld.api.v2", "dependency": ["google/protobuf/wrappers.proto", "google/protobuf/timestamp.proto"], "messageType": [{"name": "RelatedChapterUserInfo", "field": [{"name": "isChapterUnlocked", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "jsonName": "isChapterUnlocked"}, {"name": "isNovelUnlocked", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "jsonName": "isNovelUnlocked"}, {"name": "isChapterFavorite", "number": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "jsonName": "isChapterFavorite"}, {"name": "isNovelOwned", "number": 4, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "jsonName": "isNovelOwned"}, {"name": "isChapterOwned", "number": 5, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "jsonName": "isChapterOwned"}]}, {"name": "ChapterSponsor", "field": [{"name": "advanceChapter", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "advanceChapter"}, {"name": "advanceChapterNumber", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.Int32Value", "jsonName": "advanceChapterNumber"}, {"name": "plans", "number": 3, "label": "LABEL_REPEATED", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterSponsor.AdvanceChapterPlan", "jsonName": "plans"}], "nestedType": [{"name": "AdvanceChapterPlan", "field": [{"name": "name", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "name"}, {"name": "advanceChapterCount", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "advanceChapterCount"}]}]}, {"name": "ChapterPricing", "field": [{"name": "isFree", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "isFree"}, {"name": "isLastHoldback", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "isLastHoldback"}]}, {"name": "ChapterItem", "field": [{"name": "entityId", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "entityId"}, {"name": "name", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "name"}, {"name": "slug", "number": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "slug"}, {"name": "content", "number": 5, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.StringValue", "jsonName": "content"}, {"name": "novelId", "number": 6, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "novelId"}, {"name": "visible", "number": 7, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "visible"}, {"name": "isTeaser", "number": 8, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "isTeaser"}, {"name": "spoilerTitle", "number": 10, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "spoilerTitle"}, {"name": "sponsorInfo", "number": 15, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterSponsor", "jsonName": "sponsorInfo"}, {"name": "relatedUserInfo", "number": 16, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.RelatedChapterUserInfo", "jsonName": "relatedUserInfo"}, {"name": "publishedAt", "number": 18, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.Timestamp", "jsonName": "publishedAt"}, {"name": "translatorThoughts", "number": 19, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.StringValue", "jsonName": "translatorThoughts"}, {"name": "pricingInfo", "number": 20, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterPricing", "jsonName": "pricingInfo"}]}, {"name": "ChapterGroupItem", "field": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"name": "title", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "title"}, {"name": "order", "number": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "order"}, {"name": "chapterList", "number": 6, "label": "LABEL_REPEATED", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterItem", "jsonName": "chapterList"}]}, {"name": "SponsorPlanItem", "field": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"name": "name", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "name"}, {"name": "enabled", "number": 4, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "enabled"}, {"name": "visible", "number": 5, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "visible"}, {"name": "advanceChapterCount", "number": 6, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "advanceChapterCount"}, {"name": "paused", "number": 10, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "paused"}]}, {"name": "NovelItem", "field": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"name": "name", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "name"}, {"name": "coverUrl", "number": 10, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.StringValue", "jsonName": "coverUrl"}, {"name": "translatorName", "number": 11, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.StringValue", "jsonName": "translatorName"}, {"name": "authorName", "number": 13, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.StringValue", "jsonName": "authorName"}, {"name": "isSneakPeek", "number": 18, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "isSneakPeek"}]}, {"name": "UnlockedItem", "field": [{"name": "novelId", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "oneofIndex": 0, "jsonName": "novelId"}, {"name": "chapterId", "number": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "oneofIndex": 0, "jsonName": "chapterId"}], "oneofDecl": [{"name": "id"}]}, {"name": "VipItem", "field": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"name": "name", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "name"}, {"name": "enabled", "number": 7, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "enabled"}, {"name": "visible", "number": 8, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "visible"}]}, {"name": "SubscriptionItem", "field": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"name": "active", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "active"}, {"name": "plan", "number": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.SubscriptionItem.Plan", "jsonName": "plan"}], "nestedType": [{"name": "Plan", "field": [{"name": "vip", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.VipItem", "oneofIndex": 0, "jsonName": "vip"}, {"name": "sponsor", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.SponsorPlanItem", "oneofIndex": 0, "jsonName": "sponsor"}], "oneofDecl": [{"name": "plan"}]}]}, {"name": "GetChapterByProperty", "field": [{"name": "chapterId", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "oneofIndex": 0, "jsonName": "chapterId"}, {"name": "slugs", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.GetChapterByProperty.ByNovelAndChapterSlug", "oneofIndex": 0, "jsonName": "slugs"}], "nestedType": [{"name": "ByNovelAndChapterSlug", "field": [{"name": "novelSlug", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "novelSlug"}, {"name": "chapterSlug", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "chapterSlug"}]}], "oneofDecl": [{"name": "byProperty"}]}, {"name": "GetNovelRequest", "field": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "oneofIndex": 0, "jsonName": "id"}, {"name": "slug", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "oneofIndex": 0, "jsonName": "slug"}], "oneofDecl": [{"name": "selector"}]}, {"name": "GetNovelResponse", "field": [{"name": "item", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.NovelItem", "jsonName": "item"}]}, {"name": "GetChapterListRequest", "field": [{"name": "novelId", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "novelId"}, {"name": "filter", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.GetChapterListRequest.FilterChapters", "jsonName": "filter"}], "nestedType": [{"name": "FilterChapters", "field": [{"name": "chapterGroupId", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.Int32Value", "jsonName": "chapterGroupId"}, {"name": "isAdvanceChapter", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "jsonName": "isAdvanceChapter"}]}]}, {"name": "GetChapterListResponse", "field": [{"name": "items", "number": 1, "label": "LABEL_REPEATED", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterGroupItem", "jsonName": "items"}, {"name": "novelInfo", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.NovelItem", "jsonName": "novelInfo"}]}, {"name": "GetChapterRequest", "field": [{"name": "chapterProperty", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.GetChapterByProperty", "jsonName": "chapterProperty"}]}, {"name": "GetChapterResponse", "field": [{"name": "item", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterItem", "jsonName": "item"}]}, {"name": "UnlockItemRequest", "field": [{"name": "unlockMethod", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_ENUM", "typeName": ".wuxiaworld.api.v2.UnlockItemMethod", "jsonName": "unlockMethod"}, {"name": "item", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.UnlockedItem", "jsonName": "item"}]}, {"name": "UnlockItemResponse", "field": [{"name": "unlockedItem", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.UnlockedItem", "jsonName": "unlockedItem"}]}, {"name": "GetSubscriptionsRequest", "field": [{"name": "novelId", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "novelId"}]}, {"name": "GetSubscriptionsResponse", "field": [{"name": "items", "number": 1, "label": "LABEL_REPEATED", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.SubscriptionItem", "jsonName": "items"}]}], "enumType": [{"name": "UnlockItemMethod", "value": [{"name": "UnlockMethodNone", "number": 0}, {"name": "UnlockMethodKarma", "number": 1}, {"name": "UnlockMethodVip", "number": 2}, {"name": "UnlockMethodSponsor", "number": 3}]}], "service": [{"name": "Novels", "method": [{"name": "GetNovel", "inputType": ".wuxiaworld.api.v2.GetNovelRequest", "outputType": ".wuxiaworld.api.v2.GetNovelResponse"}]}, {"name": "Chapters", "method": [{"name": "GetChapterList", "inputType": ".wuxiaworld.api.v2.GetChapterListRequest", "outputType": ".wuxiaworld.api.v2.GetChapterListResponse"}, {"name": "GetChapter", "inputType": ".wuxiaworld.api.v2.GetChapterRequest", "outputType": ".wuxiaworld.api.v2.GetChapterResponse"}]}, {"name": "Unlocks", "method": [{"name": "UnlockItem", "inputType": ".wuxiaworld.api.v2.UnlockItemRequest", "outputType": ".wuxiaworld.api.v2.UnlockItemResponse"}]}, {"name": "Subscriptions", "method": [{"name": "GetSubscriptions", "inputType": ".wuxiaworld.api.v2.GetSubscriptionsRequest", "outputType": ".wuxiaworld.api.v2.GetSubscriptionsResponse"}]}], "publicDependency": [0, 1], "syntax": "proto3"}]} -""" # noqa: E501 -) + '{"file": [{"name": "google/protobuf/wrappers.proto", "package": "\ +google.protobuf", "messageType": [{"name": "DoubleValue", "field"\ +: [{"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "typ\ +e": "TYPE_DOUBLE", "jsonName": "value"}]}, {"name": "FloatValue",\ + "field": [{"name": "value", "number": 1, "label": "LABEL_OPTIONA\ +L", "type": "TYPE_FLOAT", "jsonName": "value"}]}, {"name": "Int64\ +Value", "field": [{"name": "value", "number": 1, "label": "LABEL_\ +OPTIONAL", "type": "TYPE_INT64", "jsonName": "value"}]}, {"name":\ + "UInt64Value", "field": [{"name": "value", "number": 1, "label":\ + "LABEL_OPTIONAL", "type": "TYPE_UINT64", "jsonName": "value"}]},\ + {"name": "Int32Value", "field": [{"name": "value", "number": 1, \ +"label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "val\ +ue"}]}, {"name": "UInt32Value", "field": [{"name": "value", "numb\ +er": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_UINT32", "jsonNa\ +me": "value"}]}, {"name": "BoolValue", "field": [{"name": "value"\ +, "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "j\ +sonName": "value"}]}, {"name": "StringValue", "field": [{"name": \ +"value", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_ST\ +RING", "jsonName": "value"}]}, {"name": "BytesValue", "field": [{\ +"name": "value", "number": 1, "label": "LABEL_OPTIONAL", "type": \ +"TYPE_BYTES", "jsonName": "value"}]}], "options": {"javaPackage":\ + "com.google.protobuf", "javaOuterClassname": "WrappersProto", "j\ +avaMultipleFiles": true, "goPackage": "google.golang.org/protobuf\ +/types/known/wrapperspb", "ccEnableArenas": true, "objcClassPrefi\ +x": "GPB", "csharpNamespace": "Google.Protobuf.WellKnownTypes"}, \ +"syntax": "proto3"}, {"name": "google/protobuf/timestamp.proto", \ +"package": "google.protobuf", "messageType": [{"name": "Timestamp\ +", "field": [{"name": "seconds", "number": 1, "label": "LABEL_OPT\ +IONAL", "type": "TYPE_INT64", "jsonName": "seconds"}, {"name": "n\ +anos", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_INT3\ +2", "jsonName": "nanos"}]}], "options": {"javaPackage": "com.goog\ +le.protobuf", "javaOuterClassname": "TimestampProto", "javaMultip\ +leFiles": true, "goPackage": "google.golang.org/protobuf/types/kn\ +own/timestamppb", "ccEnableArenas": true, "objcClassPrefix": "GPB\ +", "csharpNamespace": "Google.Protobuf.WellKnownTypes"}, "syntax"\ +: "proto3"}, {"name": "wuxia.proto", "package": "wuxiaworld.api.v\ +2", "dependency": ["google/protobuf/wrappers.proto", "google/prot\ +obuf/timestamp.proto"], "messageType": [{"name": "RelatedChapterU\ +serInfo", "field": [{"name": "isChapterUnlocked", "number": 1, "l\ +abel": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".go\ +ogle.protobuf.BoolValue", "jsonName": "isChapterUnlocked"}, {"nam\ +e": "isNovelUnlocked", "number": 2, "label": "LABEL_OPTIONAL", "t\ +ype": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolValue", "\ +jsonName": "isNovelUnlocked"}, {"name": "isChapterFavorite", "num\ +ber": 3, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "type\ +Name": ".google.protobuf.BoolValue", "jsonName": "isChapterFavori\ +te"}, {"name": "isNovelOwned", "number": 4, "label": "LABEL_OPTIO\ +NAL", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.BoolV\ +alue", "jsonName": "isNovelOwned"}, {"name": "isChapterOwned", "n\ +umber": 5, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "ty\ +peName": ".google.protobuf.BoolValue", "jsonName": "isChapterOwne\ +d"}]}, {"name": "ChapterSponsor", "field": [{"name": "advanceChap\ +ter", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL"\ +, "jsonName": "advanceChapter"}, {"name": "advanceChapterNumber",\ + "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", \ +"typeName": ".google.protobuf.Int32Value", "jsonName": "advanceCh\ +apterNumber"}, {"name": "plans", "number": 3, "label": "LABEL_REP\ +EATED", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.C\ +hapterSponsor.AdvanceChapterPlan", "jsonName": "plans"}], "nested\ +Type": [{"name": "AdvanceChapterPlan", "field": [{"name": "name",\ + "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "\ +jsonName": "name"}, {"name": "advanceChapterCount", "number": 2, \ +"label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "adv\ +anceChapterCount"}]}]}, {"name": "ChapterPricing", "field": [{"na\ +me": "isFree", "number": 1, "label": "LABEL_OPTIONAL", "type": "T\ +YPE_BOOL", "jsonName": "isFree"}, {"name": "isLastHoldback", "num\ +ber": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonNam\ +e": "isLastHoldback"}]}, {"name": "ChapterItem", "field": [{"name\ +": "entityId", "number": 1, "label": "LABEL_OPTIONAL", "type": "T\ +YPE_INT32", "jsonName": "entityId"}, {"name": "name", "number": 2\ +, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "\ +name"}, {"name": "slug", "number": 3, "label": "LABEL_OPTIONAL", \ +"type": "TYPE_STRING", "jsonName": "slug"}, {"name": "content", "\ +number": 5, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "t\ +ypeName": ".google.protobuf.StringValue", "jsonName": "content"},\ + {"name": "novelId", "number": 6, "label": "LABEL_OPTIONAL", "typ\ +e": "TYPE_INT32", "jsonName": "novelId"}, {"name": "visible", "nu\ +mber": 7, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonNa\ +me": "visible"}, {"name": "isTeaser", "number": 8, "label": "LABE\ +L_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "isTeaser"}, {"name\ +": "spoilerTitle", "number": 10, "label": "LABEL_OPTIONAL", "type\ +": "TYPE_BOOL", "jsonName": "spoilerTitle"}, {"name": "sponsorInf\ +o", "number": 15, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAG\ +E", "typeName": ".wuxiaworld.api.v2.ChapterSponsor", "jsonName": \ +"sponsorInfo"}, {"name": "relatedUserInfo", "number": 16, "label"\ +: "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiawo\ +rld.api.v2.RelatedChapterUserInfo", "jsonName": "relatedUserInfo"\ +}, {"name": "publishedAt", "number": 18, "label": "LABEL_OPTIONAL\ +", "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.Timestam\ +p", "jsonName": "publishedAt"}, {"name": "translatorThoughts", "n\ +umber": 19, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "t\ +ypeName": ".google.protobuf.StringValue", "jsonName": "translator\ +Thoughts"}, {"name": "pricingInfo", "number": 20, "label": "LABEL\ +_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.\ +v2.ChapterPricing", "jsonName": "pricingInfo"}]}, {"name": "Chapt\ +erGroupItem", "field": [{"name": "id", "number": 1, "label": "LAB\ +EL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"name": "\ +title", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STR\ +ING", "jsonName": "title"}, {"name": "order", "number": 3, "label\ +": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "order"}, \ +{"name": "chapterList", "number": 6, "label": "LABEL_REPEATED", "\ +type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterIte\ +m", "jsonName": "chapterList"}]}, {"name": "SponsorPlanItem", "fi\ +eld": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "ty\ +pe": "TYPE_INT32", "jsonName": "id"}, {"name": "name", "number": \ +2, "label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": \ +"name"}, {"name": "enabled", "number": 4, "label": "LABEL_OPTIONA\ +L", "type": "TYPE_BOOL", "jsonName": "enabled"}, {"name": "visibl\ +e", "number": 5, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", \ +"jsonName": "visible"}, {"name": "advanceChapterCount", "number":\ + 6, "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": \ +"advanceChapterCount"}, {"name": "paused", "number": 10, "label":\ + "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "paused"}]}, \ +{"name": "NovelItem", "field": [{"name": "id", "number": 1, "labe\ +l": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "id"}, {"\ +name": "name", "number": 2, "label": "LABEL_OPTIONAL", "type": "T\ +YPE_STRING", "jsonName": "name"}, {"name": "coverUrl", "number": \ +10, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName"\ +: ".google.protobuf.StringValue", "jsonName": "coverUrl"}, {"name\ +": "translatorName", "number": 11, "label": "LABEL_OPTIONAL", "ty\ +pe": "TYPE_MESSAGE", "typeName": ".google.protobuf.StringValue", \ +"jsonName": "translatorName"}, {"name": "authorName", "number": 1\ +3, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName":\ + ".google.protobuf.StringValue", "jsonName": "authorName"}, {"nam\ +e": "isSneakPeek", "number": 18, "label": "LABEL_OPTIONAL", "type\ +": "TYPE_BOOL", "jsonName": "isSneakPeek"}]}, {"name": "UnlockedI\ +tem", "field": [{"name": "novelId", "number": 2, "label": "LABEL_\ +OPTIONAL", "type": "TYPE_INT32", "oneofIndex": 0, "jsonName": "no\ +velId"}, {"name": "chapterId", "number": 3, "label": "LABEL_OPTIO\ +NAL", "type": "TYPE_INT32", "oneofIndex": 0, "jsonName": "chapter\ +Id"}], "oneofDecl": [{"name": "id"}]}, {"name": "VipItem", "field\ +": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "type"\ +: "TYPE_INT32", "jsonName": "id"}, {"name": "name", "number": 2, \ +"label": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "na\ +me"}, {"name": "enabled", "number": 7, "label": "LABEL_OPTIONAL",\ + "type": "TYPE_BOOL", "jsonName": "enabled"}, {"name": "visible",\ + "number": 8, "label": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "js\ +onName": "visible"}]}, {"name": "SubscriptionItem", "field": [{"n\ +ame": "id", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE\ +_INT32", "jsonName": "id"}, {"name": "active", "number": 2, "labe\ +l": "LABEL_OPTIONAL", "type": "TYPE_BOOL", "jsonName": "active"},\ + {"name": "plan", "number": 3, "label": "LABEL_OPTIONAL", "type":\ + "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.SubscriptionItem\ +.Plan", "jsonName": "plan"}], "nestedType": [{"name": "Plan", "fi\ +eld": [{"name": "vip", "number": 1, "label": "LABEL_OPTIONAL", "t\ +ype": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.VipItem", "\ +oneofIndex": 0, "jsonName": "vip"}, {"name": "sponsor", "number":\ + 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName"\ +: ".wuxiaworld.api.v2.SponsorPlanItem", "oneofIndex": 0, "jsonNam\ +e": "sponsor"}], "oneofDecl": [{"name": "plan"}]}]}, {"name": "Ge\ +tChapterByProperty", "field": [{"name": "chapterId", "number": 1,\ + "label": "LABEL_OPTIONAL", "type": "TYPE_INT32", "oneofIndex": 0\ +, "jsonName": "chapterId"}, {"name": "slugs", "number": 2, "label\ +": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaw\ +orld.api.v2.GetChapterByProperty.ByNovelAndChapterSlug", "oneofIn\ +dex": 0, "jsonName": "slugs"}], "nestedType": [{"name": "ByNovelA\ +ndChapterSlug", "field": [{"name": "novelSlug", "number": 1, "lab\ +el": "LABEL_OPTIONAL", "type": "TYPE_STRING", "jsonName": "novelS\ +lug"}, {"name": "chapterSlug", "number": 2, "label": "LABEL_OPTIO\ +NAL", "type": "TYPE_STRING", "jsonName": "chapterSlug"}]}], "oneo\ +fDecl": [{"name": "byProperty"}]}, {"name": "GetNovelRequest", "f\ +ield": [{"name": "id", "number": 1, "label": "LABEL_OPTIONAL", "t\ +ype": "TYPE_INT32", "oneofIndex": 0, "jsonName": "id"}, {"name": \ +"slug", "number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_STR\ +ING", "oneofIndex": 0, "jsonName": "slug"}], "oneofDecl": [{"name\ +": "selector"}]}, {"name": "GetNovelResponse", "field": [{"name":\ + "item", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_ME\ +SSAGE", "typeName": ".wuxiaworld.api.v2.NovelItem", "jsonName": "\ +item"}]}, {"name": "GetChapterListRequest", "field": [{"name": "n\ +ovelId", "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_IN\ +T32", "jsonName": "novelId"}, {"name": "filter", "number": 2, "la\ +bel": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wux\ +iaworld.api.v2.GetChapterListRequest.FilterChapters", "jsonName":\ + "filter"}], "nestedType": [{"name": "FilterChapters", "field": [\ +{"name": "chapterGroupId", "number": 1, "label": "LABEL_OPTIONAL"\ +, "type": "TYPE_MESSAGE", "typeName": ".google.protobuf.Int32Valu\ +e", "jsonName": "chapterGroupId"}, {"name": "isAdvanceChapter", "\ +number": 2, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "t\ +ypeName": ".google.protobuf.BoolValue", "jsonName": "isAdvanceCha\ +pter"}]}]}, {"name": "GetChapterListResponse", "field": [{"name":\ + "items", "number": 1, "label": "LABEL_REPEATED", "type": "TYPE_M\ +ESSAGE", "typeName": ".wuxiaworld.api.v2.ChapterGroupItem", "json\ +Name": "items"}, {"name": "novelInfo", "number": 2, "label": "LAB\ +EL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.ap\ +i.v2.NovelItem", "jsonName": "novelInfo"}]}, {"name": "GetChapter\ +Request", "field": [{"name": "chapterProperty", "number": 1, "lab\ +el": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxi\ +aworld.api.v2.GetChapterByProperty", "jsonName": "chapterProperty\ +"}]}, {"name": "GetChapterResponse", "field": [{"name": "item", "\ +number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_MESSAGE", "t\ +ypeName": ".wuxiaworld.api.v2.ChapterItem", "jsonName": "item"}]}\ +, {"name": "UnlockItemRequest", "field": [{"name": "unlockMethod"\ +, "number": 1, "label": "LABEL_OPTIONAL", "type": "TYPE_ENUM", "t\ +ypeName": ".wuxiaworld.api.v2.UnlockItemMethod", "jsonName": "unl\ +ockMethod"}, {"name": "item", "number": 2, "label": "LABEL_OPTION\ +AL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2.Unlo\ +ckedItem", "jsonName": "item"}]}, {"name": "UnlockItemResponse", \ +"field": [{"name": "unlockedItem", "number": 1, "label": "LABEL_O\ +PTIONAL", "type": "TYPE_MESSAGE", "typeName": ".wuxiaworld.api.v2\ +.UnlockedItem", "jsonName": "unlockedItem"}]}, {"name": "GetSubsc\ +riptionsRequest", "field": [{"name": "novelId", "number": 2, "lab\ +el": "LABEL_OPTIONAL", "type": "TYPE_INT32", "jsonName": "novelId\ +"}]}, {"name": "GetSubscriptionsResponse", "field": [{"name": "it\ +ems", "number": 1, "label": "LABEL_REPEATED", "type": "TYPE_MESSA\ +GE", "typeName": ".wuxiaworld.api.v2.SubscriptionItem", "jsonName\ +": "items"}]}], "enumType": [{"name": "UnlockItemMethod", "value"\ +: [{"name": "UnlockMethodNone", "number": 0}, {"name": "UnlockMet\ +hodKarma", "number": 1}, {"name": "UnlockMethodVip", "number": 2}\ +, {"name": "UnlockMethodSponsor", "number": 3}]}], "service": [{"\ +name": "Novels", "method": [{"name": "GetNovel", "inputType": ".w\ +uxiaworld.api.v2.GetNovelRequest", "outputType": ".wuxiaworld.api\ +.v2.GetNovelResponse"}]}, {"name": "Chapters", "method": [{"name"\ +: "GetChapterList", "inputType": ".wuxiaworld.api.v2.GetChapterLi\ +stRequest", "outputType": ".wuxiaworld.api.v2.GetChapterListRespo\ +nse"}, {"name": "GetChapter", "inputType": ".wuxiaworld.api.v2.Ge\ +tChapterRequest", "outputType": ".wuxiaworld.api.v2.GetChapterRes\ +ponse"}]}, {"name": "Unlocks", "method": [{"name": "UnlockItem", \ +"inputType": ".wuxiaworld.api.v2.UnlockItemRequest", "outputType"\ +: ".wuxiaworld.api.v2.UnlockItemResponse"}]}, {"name": "Subscript\ +ions", "method": [{"name": "GetSubscriptions", "inputType": ".wux\ +iaworld.api.v2.GetSubscriptionsRequest", "outputType": ".wuxiawor\ +ld.api.v2.GetSubscriptionsResponse"}]}], "publicDependency": [0, \ +1], "syntax": "proto3"}]}') # noqa: E501