diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index f2ed947..361f7f9 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/get/README.md b/get/README.md index 2fb1d8e..7edc31d 100644 --- a/get/README.md +++ b/get/README.md @@ -22,8 +22,4 @@ for await (const shift of getShiftCodes()) { console.log(shift.code); } -``` - -## Source Attribution - -- https://shift.orcicorn.com/shift/ +``` \ No newline at end of file diff --git a/get/package.json b/get/package.json index 172b444..482ea15 100644 --- a/get/package.json +++ b/get/package.json @@ -18,11 +18,13 @@ "branch": "master" }, "devDependencies": { + "@types/cheerio": "^0.22.35", "@types/node": "^22.18.6", "@types/node-fetch": "2.6.4", "typescript": "^5.9.2" }, "dependencies": { + "cheerio": "^1.1.2", "node-fetch": "^2.7.0", "stream-json": "^1.9.1" }, diff --git a/get/src/codes.ts b/get/src/codes.ts index 0be8f30..099671d 100644 --- a/get/src/codes.ts +++ b/get/src/codes.ts @@ -1,7 +1,7 @@ -import { getOrcicornShiftCodes } from './source/orcicorn'; import { getCodeVaultShiftCodes } from './source/codevault'; +import { getMentalMarsShiftCodes } from './source/mentalmars'; export async function * getShiftCodes() { yield * getCodeVaultShiftCodes(); - yield * getOrcicornShiftCodes(); + yield * getMentalMarsShiftCodes(); } diff --git a/get/src/source/mentalmars/bl1.ts b/get/src/source/mentalmars/bl1.ts new file mode 100644 index 0000000..8df0375 --- /dev/null +++ b/get/src/source/mentalmars/bl1.ts @@ -0,0 +1,31 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/borderlands-golden-keys/'; + +export async function * getMentalMarsBL1ShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + yield * getHeadingShiftCodes($, 'h-2026-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2025-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2024-shift-codes'); + yield * getHeadingShiftCodes($, 'h-legacy-codes-that-work-in-2025'); +} + +export async function * getHeadingShiftCodes($: cheerio.Root, heading: string, platform: string = 'Universal'): AsyncGenerator { + const figure = $(`#${heading} ~ figure`).first(); + + const parseRow = parseTableRow($, 'Borderlands', platform); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/mentalmars/bl2.ts b/get/src/source/mentalmars/bl2.ts new file mode 100644 index 0000000..2d8eb0b --- /dev/null +++ b/get/src/source/mentalmars/bl2.ts @@ -0,0 +1,34 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/borderlands-2-golden-keys/'; + +export async function * getMentalMarsBL2ShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + yield * getHeadingShiftCodes($, 'h-2026-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2025-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2024-shift-codes'); + yield * getHeadingShiftCodes($, 'h-pc-codes-with-no-expiration-date', 'Steam'); + yield * getHeadingShiftCodes($, 'h-xbox-codes-with-no-expiration-date', 'Xbox'); + yield * getHeadingShiftCodes($, 'h-playstation-codes-with-no-expiration-date', 'Playstation'); + yield * getHeadingShiftCodes($, 'h-special-event-codes'); +} + +export async function * getHeadingShiftCodes($: cheerio.Root, heading: string, platform: string = 'Universal'): AsyncGenerator { + const figure = $(`#${heading} ~ figure`).first(); + + const parseRow = parseTableRow($, 'Borderlands 2', platform); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/mentalmars/bl3.ts b/get/src/source/mentalmars/bl3.ts new file mode 100644 index 0000000..2fc3ae7 --- /dev/null +++ b/get/src/source/mentalmars/bl3.ts @@ -0,0 +1,33 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/borderlands-3-golden-keys/'; + +export async function * getMentalMarsBL3ShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + yield * getHeadingShiftCodes($, 'h-2026-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2025-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2024-shift-codes'); + yield * getHeadingShiftCodes($, 'h-borderlands-3-cosmetic-shift-code-rewards'); + yield * getHeadingShiftCodes($, 'h-borderlands-3-diamond-key-shift-codes'); + yield * getHeadingShiftCodes($, 'h-borderlands-3-permanent-shift-keys'); +} + +export async function * getHeadingShiftCodes($: cheerio.Root, heading: string, platform: string = 'Universal'): AsyncGenerator { + const figure = $(`#${heading} ~ figure`).first(); + + const parseRow = parseTableRow($, 'Borderlands 3', platform); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/mentalmars/bl4.ts b/get/src/source/mentalmars/bl4.ts new file mode 100644 index 0000000..a5873e6 --- /dev/null +++ b/get/src/source/mentalmars/bl4.ts @@ -0,0 +1,24 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/borderlands-4-shift-codes/'; + +export async function * getMentalMarsBL4ShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + const figure = $('#h-every-borderlands-4-shift-code-for-golden-keys ~ figure').first(); + + const parseRow = parseTableRow($, 'Borderlands 4', 'Universal'); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/mentalmars/godfall.ts b/get/src/source/mentalmars/godfall.ts new file mode 100644 index 0000000..76560f0 --- /dev/null +++ b/get/src/source/mentalmars/godfall.ts @@ -0,0 +1,38 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { SHIFT_CODE_REGEX } from '../../const'; +import { parseDate } from '../../utils/parseDate'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/godfall-shift-codes/'; + +export async function * getMentalMarsGodfallShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + const figure = $('#h-new-godfall-shift-codes ~ figure').first(); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const cells = $(row).find('td'); + + const reward = $(cells.get(0)).text(); + const created = $(cells.get(1)).text(); + const code = $(cells.get(2)).find('code').text().match(SHIFT_CODE_REGEX)?.[0]; + + if (!code) continue; + + const shiftCode: ShiftCode = { + code, + game: 'Godfall', + platform: 'Universal', + reward, + created: parseDate(created), + }; + + yield shiftCode; + } +} diff --git a/get/src/source/mentalmars/index.ts b/get/src/source/mentalmars/index.ts new file mode 100644 index 0000000..eba875c --- /dev/null +++ b/get/src/source/mentalmars/index.ts @@ -0,0 +1,20 @@ +import { ShiftCode } from "../../types"; +import { getMentalMarsBL1ShiftCodes } from "./bl1"; +import { getMentalMarsBL2ShiftCodes } from "./bl2"; +import { getMentalMarsBL3ShiftCodes } from "./bl3"; +import { getMentalMarsBL4ShiftCodes } from "./bl4"; +import { getMentalMarsTTWShiftCodes } from "./ttw"; +import { getMentalMarsTPSShiftCodes } from "./tps"; +import { getMentalMarsNTBShiftCodes } from "./ntb"; +import { getMentalMarsGodfallShiftCodes } from "./godfall"; + +export async function * getMentalMarsShiftCodes(): AsyncGenerator { + yield* getMentalMarsBL4ShiftCodes(); + yield* getMentalMarsBL3ShiftCodes(); + yield* getMentalMarsBL2ShiftCodes(); + yield* getMentalMarsBL1ShiftCodes(); + yield* getMentalMarsTTWShiftCodes(); + yield* getMentalMarsTPSShiftCodes(); + yield* getMentalMarsNTBShiftCodes(); + yield* getMentalMarsGodfallShiftCodes(); +} diff --git a/get/src/source/mentalmars/ntb.ts b/get/src/source/mentalmars/ntb.ts new file mode 100644 index 0000000..99f82dd --- /dev/null +++ b/get/src/source/mentalmars/ntb.ts @@ -0,0 +1,26 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { SHIFT_CODE_REGEX } from '../../const'; +import { parseDate } from '../../utils/parseDate'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/new-tales-from-the-borderlands-shift-codes/'; + +export async function * getMentalMarsNTBShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + const figure = $('figure').first(); + + const parseRow = parseTableRow($, 'New Tales of the Borderlands', 'Universal'); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/mentalmars/shared.ts b/get/src/source/mentalmars/shared.ts new file mode 100644 index 0000000..53445ab --- /dev/null +++ b/get/src/source/mentalmars/shared.ts @@ -0,0 +1,34 @@ +import * as cheerio from 'cheerio'; + +import { SHIFT_CODE_REGEX } from "../../const"; +import { ShiftCode } from "../../types"; +import { parseDate } from "../../utils/parseDate"; + +export const parseTableRow = ($: cheerio.Root, game: string, platform: string) => (row: cheerio.Element): ShiftCode | null => { + const cells = $(row).find('td'); + + let reward: string = 'Unknown'; + let expired: string | undefined; + let code: string | undefined; + + if (cells.length === 3) { + reward = $(cells.get(0)).text(); + expired = $(cells.get(1)).text().replace('Expires:', '').trim(); + code = $(cells.get(2)).find('code').text().match(SHIFT_CODE_REGEX)?.[0]; + } else if (cells.length === 2) { + reward = $(cells.get(0)).text(); + code = $(cells.get(1)).find('code').text().match(SHIFT_CODE_REGEX)?.[0]; + } + + if (!code) return null; + + const shiftCode: ShiftCode = { + code, + game, + platform, + reward, + expired: expired ? parseDate(expired) : undefined + }; + + return shiftCode; +} diff --git a/get/src/source/mentalmars/tps.ts b/get/src/source/mentalmars/tps.ts new file mode 100644 index 0000000..be8fe8e --- /dev/null +++ b/get/src/source/mentalmars/tps.ts @@ -0,0 +1,34 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/bltps-golden-keys/'; + +export async function * getMentalMarsTPSShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + yield * getHeadingShiftCodes($, 'h-2026-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2025-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2024-shift-codes'); + yield * getHeadingShiftCodes($, 'h-permanent-bltps-shift-codes-for-golden-keys'); + yield * getHeadingShiftCodes($, 'h-bltps-pc-shift-codes', 'Steam'); + yield * getHeadingShiftCodes($, 'h-bltps-playstation-shift-codes', 'Playstation'); + yield * getHeadingShiftCodes($, 'h-bltps-xbox-shift-codes', 'Xbox'); +} + +export async function * getHeadingShiftCodes($: cheerio.Root, heading: string, platform: string = 'Universal'): AsyncGenerator { + const figure = $(`#${heading} ~ figure`).first(); + + const parseRow = parseTableRow($, 'Borderlands: The Pre-Sequel', platform); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/mentalmars/ttw.ts b/get/src/source/mentalmars/ttw.ts new file mode 100644 index 0000000..2c4d35c --- /dev/null +++ b/get/src/source/mentalmars/ttw.ts @@ -0,0 +1,31 @@ +import fetch from 'node-fetch'; +import * as cheerio from 'cheerio'; + +import { ShiftCode } from '../../types'; +import { parseTableRow } from './shared'; + +const SHIFT_CODES_URL = 'https://mentalmars.com/game-news/tiny-tinas-wonderlands-shift-codes/'; + +export async function * getMentalMarsTTWShiftCodes(): AsyncGenerator { + const response = await fetch(SHIFT_CODES_URL); + + const text = await response.text(); + const $ = cheerio.load(text); + + yield * getHeadingShiftCodes($, 'h-2026-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2025-shift-codes'); + yield * getHeadingShiftCodes($, 'h-2024-shift-codes'); + yield * getHeadingShiftCodes($, 'h-permanent-ttwl-shift-codes'); +} + +export async function * getHeadingShiftCodes($: cheerio.Root, heading: string, platform: string = 'Universal'): AsyncGenerator { + const figure = $(`#${heading} ~ figure`).first(); + + const parseRow = parseTableRow($, 'Tiny Tina\'s Wonderlands', platform); + + const rows = figure.find('tbody tr'); + for (const row of rows) { + const code = parseRow(row); + if (code) yield code; + } +} diff --git a/get/src/source/orcicorn.ts b/get/src/source/orcicorn.ts deleted file mode 100644 index bcb3ee3..0000000 --- a/get/src/source/orcicorn.ts +++ /dev/null @@ -1,32 +0,0 @@ -import fetch from 'node-fetch'; -const Pick = require('stream-json/filters/Pick'); -const {streamValues} = require('stream-json/streamers/StreamValues'); - -import { ShiftCode } from '../types'; -import { parseDate } from '../utils/parseDate'; - -const SHIFT_CODES_URL = 'https://shift.orcicorn.com/shift-code/index.json'; - -export async function * getOrcicornShiftCodes(): AsyncGenerator { - const response = await fetch(SHIFT_CODES_URL); - - const stream = response.body! - .pipe(Pick.withParser({filter: /^0.codes.\d+/})) - .pipe(streamValues()); - - for await (const {value} of stream) { - const created = parseDate(value.archived); - const expired = parseDate(value.expires); - - const code: ShiftCode = { - code: value.code, - game: value.game, - platform: value.platform, - reward: value.reward, - created, - expired - } - - yield code; - } -} diff --git a/yarn.lock b/yarn.lock index 1177f17..0fc103f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -825,8 +825,10 @@ __metadata: version: 0.0.0-use.local resolution: "@shift-code/get@workspace:get" dependencies: + "@types/cheerio": "npm:^0.22.35" "@types/node": "npm:^22.18.6" "@types/node-fetch": "npm:2.6.4" + cheerio: "npm:^1.1.2" node-fetch: "npm:^2.7.0" stream-json: "npm:^1.9.1" typescript: "npm:^5.9.2"