{"_id":"@dchighs/dragoncitygame","_rev":"4-a54190716122c73a5270f8b2afcfdfc8","name":"@dchighs/dragoncitygame","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@dchighs/dragoncitygame","version":"0.1.0","keywords":["marcuth"],"author":{"name":"Marcuth"},"license":"MIT","_id":"@dchighs/dragoncitygame@0.1.0","maintainers":[{"name":"marcuth","email":"marcuth2006@gmail.com"}],"homepage":"https://github.com/DC-Highs/dragoncitygame#readme","bugs":{"url":"https://github.com/DC-Highs/dragoncitygame/issues"},"dist":{"shasum":"c3ad37e36a197c4d3f4b736675e23fe9b7a9601a","tarball":"https://registry.npmjs.org/@dchighs/dragoncitygame/-/dragoncitygame-0.1.0.tgz","fileCount":45,"integrity":"sha512-mqRexCBLrkZc3eM4m3NKLZXtqWte3l8zpRCj8wq+46u7m2/Sqim4qAzI5Dr/c3fKSyoVSQi42i/VuSEuzDoTdg==","signatures":[{"sig":"MEUCIHfP73T0TdaSJcNk3XATnHPtO0DlrsqcL3cws0UvZMzzAiEAngVYRBm7azT4c03TmxyA8JXrOUVcINDz/4hsZoyfl5k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35882},"main":"./dist/index.js","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.js","gitHead":"8fc34b848e3a1823b53f59b5f3ff004485fd830f","scripts":{"dev":"tsx ./src/index.ts","lint":"eslint \"src/**/*.ts\" --fix","test":"vitest","build":"tsc","format":"prettier --write \"src/**/*.ts\"","test:cov":"vitest run --coverage"},"_npmUser":{"name":"marcuth","email":"marcuth2006@gmail.com"},"repository":{"url":"git+https://github.com/DC-Highs/dragoncitygame.git","type":"git"},"_npmVersion":"11.8.0","description":"Official SDK / Scraper for integration with the **Dragon City** official website and store (`dragoncitygame.com`).","directories":{},"_nodeVersion":"24.13.0","dependencies":{"@xcrap/core":"^0.2.3","@xcrap/extractor":"^0.3.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.13","eslint":"^10.9.1","vitest":"^4.1.11","prettier":"^3.9.6","typescript":"^7.0.2","@types/node":"^26.4.1","@vitest/coverage-v8":"^4.1.11","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.6","eslint-plugin-unused-imports":"^4.4.1","@ianvs/prettier-plugin-sort-imports":"^4.7.1"},"_npmOperationalInternal":{"tmp":"tmp/dragoncitygame_0.1.0_1788451278990_0.9924419295157094","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dchighs/dragoncitygame","version":"0.1.1","keywords":["marcuth"],"author":{"name":"Marcuth"},"license":"MIT","_id":"@dchighs/dragoncitygame@0.1.1","maintainers":[{"name":"marcuth","email":"marcuth2006@gmail.com"}],"homepage":"https://github.com/DC-Highs/dragoncitygame#readme","bugs":{"url":"https://github.com/DC-Highs/dragoncitygame/issues"},"dist":{"shasum":"5e6165706bed3f129da7422f896098d40f7fb2c2","tarball":"https://registry.npmjs.org/@dchighs/dragoncitygame/-/dragoncitygame-0.1.1.tgz","fileCount":53,"integrity":"sha512-jfN/p4WNeXurqko8cwoKD46RibHYUxxC4TMUTxtD8Rgagvnb2CPs6S992QNFue7MKzRMFH8sno+kM/6ltX7j5w==","signatures":[{"sig":"MEUCIDcyrxWk34BO4Nux9H7UByTCEKKaRoVo5IZpsrouwglPAiEAk1DHQaeJvm5IsooBdtWxI8WA1b5vigB1lxbH4iW4RTg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCICHd/QYaJMYGjsrwz1Dvj5OhuTuChIa6DRkN/p6IETGlAiAXMuzBFFz5RKyiLmszYTxXiNLQS71jn5ZwM7vmogRK1g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39724},"main":"./dist/index.js","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.js","gitHead":"10aed1bf9d1009a71173e00352fac69540946ccb","scripts":{"dev":"tsx ./src/index.ts","lint":"eslint \"src/**/*.ts\" --fix","test":"vitest","build":"tsc","format":"prettier --write \"src/**/*.ts\"","test:cov":"vitest run --coverage"},"_npmUser":{"name":"marcuth","email":"marcuth2006@gmail.com"},"repository":{"url":"git+https://github.com/DC-Highs/dragoncitygame.git","type":"git"},"_npmVersion":"11.8.0","description":"Official SDK / Scraper for integration with the **Dragon City** official website and store (`dragoncitygame.com`).","directories":{},"_nodeVersion":"24.13.0","dependencies":{"@xcrap/core":"^0.2.3","@xcrap/extractor":"^0.3.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.13","eslint":"^10.9.1","vitest":"^4.1.11","prettier":"^3.9.6","typescript":"^7.0.2","@types/node":"^26.4.1","@vitest/coverage-v8":"^4.1.11","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.6","eslint-plugin-unused-imports":"^4.4.1","@ianvs/prettier-plugin-sort-imports":"^4.7.1"},"_npmOperationalInternal":{"tmp":"tmp/dragoncitygame_0.1.1_1788452169877_0.4489732083540685","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@dchighs/dragoncitygame","version":"0.1.2","keywords":["marcuth"],"author":{"name":"Marcuth"},"license":"MIT","_id":"@dchighs/dragoncitygame@0.1.2","maintainers":[{"name":"marcuth","email":"marcuth2006@gmail.com"}],"homepage":"https://github.com/DC-Highs/dragoncitygame#readme","bugs":{"url":"https://github.com/DC-Highs/dragoncitygame/issues"},"dist":{"shasum":"2bf8c65288c882a28808f7eaef01bc5c0d885985","tarball":"https://registry.npmjs.org/@dchighs/dragoncitygame/-/dragoncitygame-0.1.2.tgz","fileCount":53,"integrity":"sha512-Ni+hzEFTiApoRX/aRntoe8q3RUdxFNxJLiDa95kw1qHJnBzGVV7VjtHhQR1/rZewJvd/egD+rgQDkhMbYIKF1g==","signatures":[{"sig":"MEUCIQDwzSEPTKkiJepF9iMTAHA+Yq1Wgl4zqyaJff2xPwwDVwIgWhEZA/iSAAR0qmJ6ze8O42l+OC/k+rIY4JMFlQaRlu0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIH4bgbY5GHop32/1cpDNp/QPBzp0QDhmczGIE2sx74pCAiApThhzg7Y0oN3h1pmrXq8I+lWw7S8xtOoPEdlxk04qfA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42569},"main":"./dist/index.js","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.js","gitHead":"49f6524f4a7f1d84ff0a65b49aae4c31c46c6d5d","scripts":{"dev":"tsx ./src/index.ts","lint":"eslint \"src/**/*.ts\" --fix","test":"vitest","build":"tsc","format":"prettier --write \"src/**/*.ts\"","test:cov":"vitest run --coverage"},"_npmUser":{"name":"marcuth","email":"marcuth2006@gmail.com"},"repository":{"url":"git+https://github.com/DC-Highs/dragoncitygame.git","type":"git"},"_npmVersion":"11.8.0","description":"Official SDK / Scraper for integration with the **Dragon City** official website and store (`dragoncitygame.com`).","directories":{},"_nodeVersion":"24.13.0","dependencies":{"@xcrap/core":"^0.2.3","@xcrap/extractor":"^0.3.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.13","eslint":"^10.9.1","vitest":"^4.1.11","prettier":"^3.9.6","typescript":"^7.0.2","@types/node":"^26.4.1","@vitest/coverage-v8":"^4.1.11","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.6","eslint-plugin-unused-imports":"^4.4.1","@ianvs/prettier-plugin-sort-imports":"^4.7.1"},"_npmOperationalInternal":{"tmp":"tmp/dragoncitygame_0.1.2_1788452882650_0.8332699527887402","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@dchighs/dragoncitygame@0.2.0","bugs":{"url":"https://github.com/DC-Highs/dragoncitygame/issues"},"dist":{"shasum":"61d27b6169dbe312db9124b1b925ff9649f0088d","tarball":"https://registry.npmjs.org/@dchighs/dragoncitygame/-/dragoncitygame-0.2.0.tgz","fileCount":57,"integrity":"sha512-YH7+cnHgSvNbXLZ92uL3ESSEHJ9khOgtdgeVvipB+VyRqIl9HTYv5GY9eH4XyKuCM9AZR7/YGYzrMMaaufAmCg==","signatures":[{"sig":"MEUCIQC5nY03dVMfiJbqfRdTq+SwcxkW0v4Whp1aQ3j5PZXigAIgDb31qc4fItsrmyvoVmxx16aZ0PnUZgwumaXK9W+3Wus=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDkmh27mHTybApHdazEXXI5O3X1PA4KqL4gx6tEQ33Q7AIhAMm+IsxVoouo+/WuKkmN5EU9BYYX+WHLl/tAxrJVsqM5"}],"unpackedSize":95404},"main":"./dist/index.js","name":"@dchighs/dragoncitygame","type":"commonjs","types":"./dist/index.d.ts","author":{"name":"Marcuth"},"module":"./dist/index.js","gitHead":"661b4b59df86cbb568628006cd93e0ff6cb51e1c","license":"MIT","scripts":{"dev":"tsx ./src/index.ts","lint":"eslint \"src/**/*.ts\" --fix","test":"vitest","build":"tsc","format":"prettier --write \"src/**/*.ts\"","test:cov":"vitest run --coverage"},"version":"0.2.0","_npmUser":{"name":"marcuth","email":"marcuth2006@gmail.com"},"homepage":"https://github.com/DC-Highs/dragoncitygame#readme","keywords":["marcuth"],"repository":{"url":"git+https://github.com/DC-Highs/dragoncitygame.git","type":"git"},"_npmVersion":"11.8.0","description":"Comprehensive TypeScript SDK and automation client for Dragon City's official website and webstore ([dragoncitygame.com](https://www.dragoncitygame.com)).","directories":{},"maintainers":[{"name":"marcuth","email":"marcuth2006@gmail.com"}],"_nodeVersion":"24.13.0","dependencies":{"puppeteer":"^25.10.0","@xcrap/core":"^0.2.3","@xcrap/extractor":"^0.3.2","@xcrap/puppeteer-client":"^0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.13","eslint":"^10.9.1","vitest":"^4.1.11","prettier":"^3.9.6","typescript":"^7.0.2","@types/node":"^26.4.1","@vitest/coverage-v8":"^4.1.11","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.6","eslint-plugin-unused-imports":"^4.4.1","@ianvs/prettier-plugin-sort-imports":"^4.7.1"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dragoncitygame_0.2.0_1788721742215_0.1549393274657589"}}},"time":{"created":"2026-09-03T16:01:18.864Z","modified":"2026-09-06T19:09:02.492Z","0.1.0":"2026-09-03T16:01:19.139Z","0.1.1":"2026-09-03T16:16:09.963Z","0.1.2":"2026-09-03T16:28:02.718Z","0.2.0":"2026-09-06T19:09:02.299Z"},"bugs":{"url":"https://github.com/DC-Highs/dragoncitygame/issues"},"author":{"name":"Marcuth"},"license":"MIT","homepage":"https://github.com/DC-Highs/dragoncitygame#readme","keywords":["marcuth"],"repository":{"url":"git+https://github.com/DC-Highs/dragoncitygame.git","type":"git"},"description":"Comprehensive TypeScript SDK and automation client for Dragon City's official website and webstore ([dragoncitygame.com](https://www.dragoncitygame.com)).","maintainers":[{"name":"marcuth","email":"marcuth2006@gmail.com"}],"readme":"# @dchighs/dragoncitygame\n\nComprehensive TypeScript SDK and automation client for Dragon City's official website and webstore ([dragoncitygame.com](https://www.dragoncitygame.com)).\n\nFeatures high-performance news extraction with Contentful-to-Markdown conversion, official webstore product catalog parsing, automated reward claims (daily gifts & streaks), promo code redemption with multi-lingual error normalization, and session persistence with an **observer pattern** for automatic token renewals.\n\n---\n\n## 📋 Table of Contents\n\n- [Features](#-features)\n- [Installation](#-installation)\n- [Quick Start](#-quick-start)\n- [Authentication & Session Management](#-authentication--session-management)\n  - [Cookie Format](#cookie-format)\n  - [Token Validation](#token-validation)\n  - [Cookie Observer Pattern (`onCookiesChange`)](#cookie-observer-pattern-oncookieschange)\n  - [Token Renewal (`refreshToken`)](#token-renewal-refreshtoken)\n- [Store Module (`store`)](#-store-module-store)\n  - [Redeem Promo Code (`redeemCode`)](#redeem-promo-code-redeemcode)\n  - [Claim Daily Gift (`claimDailyGift`)](#claim-daily-gift-claimdailygift)\n  - [Claim Daily Streak (`claimDailyStreak`)](#claim-daily-streak-claimdailystreak)\n  - [Fetch Store Products (`getProducts`)](#fetch-store-products-getproducts)\n  - [Closing Browser Resources (`close`)](#closing-browser-resources-close)\n- [News Module (`news`)](#-news-module-news)\n  - [Get Paginated Articles (`getManyArticles`)](#get-paginated-articles-getmanyarticles)\n  - [Get Article in Markdown (`getOneArticle`)](#get-article-in-markdown-getonearticle)\n  - [Get Latest Upcoming Events Calendar (`getLastUpcomingEventsArticle`)](#get-latest-upcoming-events-calendar-getlastupcomingeventsarticle)\n- [Error Handling](#-error-handling)\n  - [Store & Authentication Errors](#store--authentication-errors)\n  - [Redeem Code Error Types](#redeem-code-error-types)\n  - [News Scraping Errors](#news-scraping-errors)\n- [Supported Languages](#-supported-languages)\n- [License](#-license)\n\n---\n\n## ✨ Features\n\n- ⚡ **Lightweight & Fast**: Uses `@xcrap/core` HTTP client for instant data extraction without overhead.\n- 🤖 **Smart Headless Automation**: Browser actions powered by `@xcrap/puppeteer-client` for seamless interaction with dynamic Next.js components.\n- 🎁 **Store Rewards**: Redeem promo codes, claim daily free gifts, and claim daily login streaks.\n- 🔄 **Automatic Token Refresh**: Built-in detection and renewal via `/api/refreshtoken`.\n- 👁️ **Cookie Observer**: Hook into token changes (`onCookiesChange`) to synchronize and persist updated cookies to disk or database automatically.\n- 🌐 **Multi-Language Error Normalization**: Standardizes promo code errors (invalid, expired, already redeemed) across English, Portuguese, Spanish, French, German, Italian, and more.\n- 📝 **Rich Contentful Markdown**: Converts news rich-text structures into clean, ready-to-use Markdown.\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install @dchighs/dragoncitygame\n```\n\nOr using yarn, pnpm, or bun:\n\n```bash\nyarn add @dchighs/dragoncitygame\n# or\npnpm add @dchighs/dragoncitygame\n# or\nbun add @dchighs/dragoncitygame\n```\n\n> **Note**: `@dchighs/dragoncitygame` requires `@xcrap/puppeteer-client` and `puppeteer` (installed automatically as dependencies) for automation tasks such as code redemption and gift claiming.\n\n---\n\n## 🚀 Quick Start\n\n### Basic Initialization (Public Data)\n\n```typescript\nimport { DragonCityGame, LanguagePrefix } from \"@dchighs/dragoncitygame\"\n\nconst game = new DragonCityGame({\n    languagePrefix: LanguagePrefix.English,\n})\n\n// Fetch current news articles\nconst { data: articles } = await game.news.getManyArticles({ page: 1 })\nconsole.log(articles[0].title)\n\n// Fetch store products\nconst products = await game.store.getProducts()\nconsole.log(`Found ${products.length} products on webstore`)\n```\n\n---\n\n## 🔐 Authentication & Session Management\n\nActions such as **redeeming codes**, **claiming daily gifts**, and **claiming streaks** require an active Dragon City web session. The session is identified by the `__Host-AccessToken` JWT cookie.\n\n### Cookie Format\n\nYou can pass cookies as an array of objects (exported from browser DevTools / cookie extensions) or as a raw JSON string:\n\n```typescript\nimport fs from \"node:fs\"\nimport { DragonCityGame, LanguagePrefix } from \"@dchighs/dragoncitygame\"\n\n// Read cookies from file\nconst cookies = JSON.parse(fs.readFileSync(\"./cookies/dragoncitygame.json\", \"utf-8\"))\n\nconst game = new DragonCityGame({\n    languagePrefix: LanguagePrefix.English,\n    cookies,\n    autoRefreshToken: true, // Default: true (automatically renews before expiration)\n})\n```\n\n### Token Validation\n\nThe SDK verifies that essential authentication tokens exist, are well-formed JWTs, have not expired, and contain valid claims (`sub`, `iss`):\n\n```typescript\ntry {\n    game.store.validateTokens()\n    console.log(\"Tokens are valid and active!\")\n} catch (error) {\n    console.error(\"Token error:\", error.message)\n}\n```\n\n### Cookie Observer Pattern (`onCookiesChange`)\n\nWeb tokens and cookies frequently change or renew. To avoid losing the updated session, register a listener with `onCookiesChange`. Whenever cookies are synchronized from the browser or renewed via `/api/refreshtoken`, your listener is notified immediately:\n\n```typescript\nimport fs from \"node:fs/promises\"\n\n// Register an observer to persist updated cookies\nconst unsubscribe = game.onCookiesChange(async (updatedCookies, context) => {\n    console.log(`[Cookies Updated] Reason: ${context.reason}`)\n    console.log(`[Cookies Updated] Changed: ${context.changedCookieNames.join(\", \")}`)\n\n    // Save updated cookies to file or database\n    await fs.writeFile(\n        \"./cookies/dragoncitygame.json\",\n        JSON.stringify(updatedCookies, null, 2),\n        \"utf-8\",\n    )\n})\n\n// Later, if you want to stop listening:\n// unsubscribe()\n```\n\n#### Change Context Reasons:\n- `\"browser_sync\"`: Cookies were updated during page interactions inside Puppeteer.\n- `\"token_refresh\"`: Tokens were refreshed through the API endpoint.\n- `\"manual\"`: Cookies were updated programmatically.\n\n### Token Renewal (`refreshToken`)\n\nYou can renew the JWT access token at any time without opening a browser:\n\n```typescript\nconst result = await game.refreshToken()\n\nconsole.log(\"Refresh successful:\", result.success)\nconsole.log(\"New Token:\", result.token)\n// Listeners registered via onCookiesChange will also be triggered automatically\n```\n\n---\n\n## 🛒 Store Module (`store`)\n\n### Redeem Promo Code (`redeemCode`)\n\nRedeems a promotional code on `dragoncitygame.com`. All errors from the underlying Take2Games API and UI are normalized into the `RedeemCodeErrorType` enum regardless of the website's language.\n\n```typescript\nimport { StoreRedeemCodeError, RedeemCodeErrorType } from \"@dchighs/dragoncitygame\"\n\ntry {\n    const result = await game.store.redeemCode(\"DRAGONCITY\")\n    console.log(\"Success:\", result.success)\n    console.log(\"Promotion ID:\", result.promotionId)\n} catch (error) {\n    if (error instanceof StoreRedeemCodeError) {\n        console.error(\"Code:\", error.code)\n        console.error(\"Normalized Error Type:\", error.errorType)\n        console.error(\"Message:\", error.normalizedMessage)\n\n        if (error.errorType === RedeemCodeErrorType.INVALID_CODE) {\n            console.log(\"The code entered does not exist.\")\n        } else if (error.errorType === RedeemCodeErrorType.ALREADY_REDEEMED) {\n            console.log(\"You have already redeemed this code.\")\n        } else if (error.errorType === RedeemCodeErrorType.EXPIRED_OR_INELIGIBLE) {\n            console.log(\"This code has expired or your account is ineligible.\")\n        }\n    }\n}\n```\n\n### Claim Daily Gift (`claimDailyGift`)\n\nClaims the daily free gift available in the webstore:\n\n```typescript\nconst result = await game.store.claimDailyGift()\n\nif (result.alreadyClaimed) {\n    console.log(\"Daily gift has already been claimed today!\")\n} else if (result.success) {\n    console.log(\"Daily gift successfully claimed! Reward:\", result.rewardTitle)\n}\n```\n\n### Claim Daily Streak (`claimDailyStreak`)\n\nClaims the daily consecutive login streak reward:\n\n```typescript\nconst result = await game.store.claimDailyStreak()\n\nif (result.alreadyClaimed) {\n    console.log(\"Daily streak already claimed for today!\")\n} else if (result.success) {\n    console.log(\"Daily streak successfully claimed! Reward:\", result.rewardTitle)\n}\n```\n\n### Fetch Store Products (`getProducts`)\n\nFetches all active offers, gem packs, dragon bundles, and items from the webstore:\n\n```typescript\nconst products = await game.store.getProducts()\n\nfor (const product of products) {\n    console.log(`[${product.category}] ${product.title}`)\n    console.log(`Price: ${product.currency} ${product.salePrice ?? product.originalPrice}`)\n    console.log(`Purchasable: ${product.purchasable}`)\n    console.log(\"Items:\", product.items.map((item) => `${item.quantity}x ${item.name}`))\n    console.log(\"---\")\n}\n```\n\n### Closing Browser Resources (`close`)\n\nWhen you are done performing browser automation tasks, close the browser instance to release system resources:\n\n```typescript\nawait game.close()\n```\n\n---\n\n## 📰 News Module (`news`)\n\n### Get Paginated Articles (`getManyArticles`)\n\nRetrieves article previews along with pagination metadata:\n\n```typescript\nconst { meta, data: articles } = await game.news.getManyArticles({ page: 1 })\n\nconsole.log(meta)\n// Output:\n// {\n//   total: 113,\n//   lastPage: 3,\n//   currentPage: 1,\n//   prevPage: null,\n//   nextPage: 2\n// }\n\nconsole.log(articles[0])\n// Output:\n// {\n//   slug: 'upcoming-events-september-2026',\n//   createdAt: '2026-09-01T00:00:00.000+02:00',\n//   title: 'UPCOMING EVENTS: SEPTEMBER 2026',\n//   thumbnailUrl: 'https://images.ctfassets.net/...'\n// }\n```\n\n### Get Article in Markdown (`getOneArticle`)\n\nFetches the full article by its `slug` and converts Contentful rich-text into clean Markdown:\n\n```typescript\nconst article = await game.news.getOneArticle(\"upcoming-events-september-2026\")\n\nconsole.log(article.title)\nconsole.log(article.body) // Markdown content\n```\n\n### Get Latest Upcoming Events Calendar (`getLastUpcomingEventsArticle`)\n\nSearches across pages to automatically find the latest upcoming events calendar post:\n\n```typescript\nconst lastEventArticle = await game.news.getLastUpcomingEventsArticle()\n\nconsole.log(\"Calendar:\", lastEventArticle.title)\nconsole.log(\"Content:\\n\", lastEventArticle.body)\n```\n\n---\n\n## ⚠️ Error Handling\n\n### Store & Authentication Errors\n\n| Error Class | Description |\n| :--- | :--- |\n| `StoreAuthenticationError` | Base error for any authentication/session failure. |\n| `StoreTokenMissingError` | Thrown when `__Host-AccessToken` is missing from the cookie list. |\n| `StoreTokenExpiredError` | Thrown when the JWT access token has expired (contains `expirationDate`). |\n| `StoreInvalidTokenError` | Thrown when the JWT structure is malformed or invalid. |\n| `StoreRedeemCodeError` | Thrown when promo code redemption fails (contains `code`, `errorType`, `normalizedMessage`, `rawMessage`). |\n\n### Redeem Code Error Types\n\nThe `RedeemCodeErrorType` enum standardizes redemption errors regardless of website language:\n\n```typescript\nexport enum RedeemCodeErrorType {\n    INVALID_CODE = \"INVALID_CODE\",                   // Code does not exist or was typed incorrectly\n    ALREADY_REDEEMED = \"ALREADY_REDEEMED\",           // Code has already been redeemed on this account\n    EXPIRED_OR_INELIGIBLE = \"EXPIRED_OR_INELIGIBLE\", // Promotion period ended or account ineligible\n    WRONG_FORMAT = \"WRONG_FORMAT\",                   // Code format is invalid\n    GENERAL_ERROR = \"GENERAL_ERROR\",                 // Server or network error\n    UNKNOWN = \"UNKNOWN\",                             // Unrecognized response\n}\n```\n\n### News Scraping Errors\n\n| Error Class | Description |\n| :--- | :--- |\n| `ArticleNotFoundError` | Thrown when an article with the given slug cannot be found. |\n| `NewsArticlesCollectionNotFoundError` | Thrown if news articles collection is missing from page data. |\n| `UpcomingEventsArticleNotFoundError` | Thrown if no upcoming events article is found. |\n| `NextDataScriptNotFoundError` | Thrown if the `__NEXT_DATA__` script tag is missing from the page. |\n\n---\n\n## 🌐 Supported Languages\n\nPass the `languagePrefix` in `DragonCityGame` options to localize API requests and URLs:\n\n```typescript\nimport { LanguagePrefix } from \"@dchighs/dragoncitygame\"\n```\n\n| Enum Member | Path Prefix | Language |\n| :--- | :--- | :--- |\n| `LanguagePrefix.English` | `\"\"` | English (Default) |\n| `LanguagePrefix.BrazilianPortuguese` | `\"/pt-BR\"` | Português (Brasil) |\n| `LanguagePrefix.Spanish` | `\"/es\"` | Español |\n| `LanguagePrefix.French` | `\"/fr\"` | Français |\n| `LanguagePrefix.German` | `\"/de\"` | Deutsch |\n| `LanguagePrefix.Italian` | `\"/it\"` | Italiano |\n| `LanguagePrefix.Japanese` | `\"/ja\"` | 日本語 |\n| `LanguagePrefix.Russian` | `\"/ru\"` | Русский |\n| `LanguagePrefix.Korean` | `\"/ko\"` | 한국어 |\n| `LanguagePrefix.Turkish` | `\"/tr\"` | Türkçe |\n\n---\n\n## 📜 License\n\nDistributed under the [MIT](./LICENSE) License.","readmeFilename":"README.md"}