-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move API requests to the content script (#67)
This is a workaround for CF putting API endpoints behind a Cloudflare human check. Why do they do this? I can only assume they don't know this is happening, or they do but they don't care about API service.
- Loading branch information
Showing
9 changed files
with
143 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,46 @@ | ||
/** | ||
* Utility to fetch data from the Codeforces API. | ||
*/ | ||
export class Api { | ||
constructor(fetchFromContentScript) { | ||
// We fetch from the content script as a workaround for CF putting API | ||
// endpoints behind a Cloudflare human check. The content script should | ||
// have the necessary cookies to get through and receive a response. | ||
this.fetchFromContentScript = fetchFromContentScript; | ||
} | ||
|
||
const API_URL_PREFIX = 'https://codeforces.com/api/'; | ||
|
||
async function apiFetch(path, queryParams) { | ||
const url = new URL(API_URL_PREFIX + path); | ||
for (const [key, value] of Object.entries(queryParams)) { | ||
if (value !== undefined) { | ||
url.searchParams.append(key, value); | ||
async fetch(path, queryParams) { | ||
let queryParamList = []; | ||
for (const [key, value] of Object.entries(queryParams)) { | ||
if (value !== undefined) { | ||
queryParamList.push([key, value]); | ||
} | ||
} | ||
return await this.fetchFromContentScript(path, queryParamList); | ||
} | ||
const resp = await fetch(url); | ||
const text = await resp.text(); | ||
if (resp.status !== 200) { | ||
throw new Error(`CF API: HTTP error ${resp.status}: ${text}`) | ||
} | ||
let json; | ||
try { | ||
json = JSON.parse(text); | ||
} catch (_) { | ||
throw new Error(`CF API: Invalid JSON: ${text}`); | ||
} | ||
if (json.status !== 'OK' || json.result === undefined) { | ||
throw new Error(`CF API: Error: ${text}`); | ||
} | ||
return json.result; | ||
} | ||
|
||
export const contest = { | ||
async list(gym = undefined) { | ||
return await apiFetch('contest.list', { gym: gym }); | ||
}, | ||
async contestList(gym = undefined) { | ||
return await this.fetch('contest.list', { gym }); | ||
} | ||
|
||
async standings( | ||
async contestStandings( | ||
contestId, from = undefined, count = undefined, handles = undefined, room = undefined, | ||
showUnofficial = undefined) { | ||
return await apiFetch('contest.standings', { | ||
contestId: contestId, | ||
from: from, | ||
count: count, | ||
return await this.fetch('contest.standings', { | ||
contestId, | ||
from, | ||
count, | ||
handles: handles && handles.length ? handles.join(';') : undefined, | ||
room: room, | ||
showUnofficial: showUnofficial, | ||
room, | ||
showUnofficial, | ||
}); | ||
}, | ||
} | ||
|
||
async ratingChanges(contestId) { | ||
return await apiFetch('contest.ratingChanges', { contestId: contestId }); | ||
}, | ||
}; | ||
async contestRatingChanges(contestId) { | ||
return await this.fetch('contest.ratingChanges', { contestId }); | ||
} | ||
|
||
export const user = { | ||
async ratedList(activeOnly = undefined) { | ||
return await apiFetch('user.ratedList', { activeOnly: activeOnly }); | ||
}, | ||
}; | ||
async userRatedList(activeOnly = undefined) { | ||
return await this.fetch('user.ratedList', { activeOnly }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
import * as path from 'https://deno.land/[email protected]/path/mod.ts'; | ||
|
||
import { Contestant } from '../src/background/predict.js'; | ||
import * as api from '../src/background/cf-api.js'; | ||
import { Api } from '../src/background/cf-api.js'; | ||
|
||
const DATA_DIR = path.join(path.fromFileUrl(import.meta.url), '../data'); | ||
const DATA_FILE_REGEX = /^(round-.*)-data.json$/; | ||
const API_URL_PREFIX = 'https://codeforces.com/api/' | ||
|
||
export class DataRow { | ||
constructor( | ||
|
@@ -54,10 +55,28 @@ async function main() { | |
Deno.exit(1); | ||
} | ||
|
||
const { rows } = await api.contest.standings(contestId); | ||
const api = new Api( | ||
async (path: string, queryParamList: [string, string][]): Promise<any> => { | ||
const url = new URL(API_URL_PREFIX + path); | ||
for (const [key, value] of queryParamList) { | ||
url.searchParams.append(key, value); | ||
} | ||
const resp = await fetch(url); | ||
if (resp.status !== 200) { | ||
throw new Error(`CF API: HTTP error ${resp.status}`) | ||
} | ||
const json = await resp.json(); | ||
if (json.status !== 'OK') { | ||
throw new Error(`CF API: Error: ${json.status}`); | ||
} | ||
return json.result; | ||
} | ||
); | ||
|
||
const { rows } = await api.contestStandings(contestId); | ||
const rowMap = new Map<string, any>(rows.map((r: any) => [r.party.members[0].handle, r])); | ||
|
||
const changes = await api.contest.ratingChanges(contestId); | ||
const changes = await api.contestRatingChanges(contestId); | ||
|
||
const output = changes.map((c: any) => { | ||
const row = rowMap.get(c.handle); | ||
|