Compare commits
No commits in common. "e9dd236172217ae7f28485c4027bffbd187729dd" and "6c036a7b2e03db01c2e49ba6b919526019b86acb" have entirely different histories.
e9dd236172
...
6c036a7b2e
9 changed files with 21 additions and 117 deletions
|
|
@ -24,16 +24,15 @@ CONTENTS *cp-contents*
|
||||||
16. Race .......................................................... |cp-race|
|
16. Race .......................................................... |cp-race|
|
||||||
17. Credentials ............................................ |cp-credentials|
|
17. Credentials ............................................ |cp-credentials|
|
||||||
18. Submit ...................................................... |cp-submit|
|
18. Submit ...................................................... |cp-submit|
|
||||||
19. Open ......................................................... |cp-open|
|
19. ANSI Colors ................................................... |cp-ansi|
|
||||||
20. ANSI Colors ................................................... |cp-ansi|
|
20. Highlight Groups ........................................ |cp-highlights|
|
||||||
21. Highlight Groups ........................................ |cp-highlights|
|
21. Terminal Colors .................................... |cp-terminal-colors|
|
||||||
22. Terminal Colors .................................... |cp-terminal-colors|
|
22. Highlight Customization .......................... |cp-highlight-custom|
|
||||||
23. Highlight Customization .......................... |cp-highlight-custom|
|
23. Helpers .................................................... |cp-helpers|
|
||||||
24. Helpers .................................................... |cp-helpers|
|
24. Statusline Integration .................................. |cp-statusline|
|
||||||
25. Statusline Integration .................................. |cp-statusline|
|
25. Panel Keymaps .......................................... |cp-panel-keys|
|
||||||
26. Panel Keymaps .......................................... |cp-panel-keys|
|
26. File Structure ................................................ |cp-files|
|
||||||
27. File Structure ................................................ |cp-files|
|
27. Health Check ................................................ |cp-health|
|
||||||
28. Health Check ................................................ |cp-health|
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
INTRODUCTION *cp.nvim*
|
INTRODUCTION *cp.nvim*
|
||||||
|
|
@ -488,17 +487,6 @@ COMMANDS *cp-commands*
|
||||||
credentials are saved.
|
credentials are saved.
|
||||||
--lang: Submit solution for a specific language.
|
--lang: Submit solution for a specific language.
|
||||||
|
|
||||||
:CP open [problem|contest|standings]
|
|
||||||
Open the URL for the current problem, contest,
|
|
||||||
or standings page in the browser via
|
|
||||||
|vim.ui.open|. Defaults to "problem" if no
|
|
||||||
argument is given. Warns if the URL is not
|
|
||||||
available (e.g. CSES has no standings).
|
|
||||||
Examples: >
|
|
||||||
:CP open
|
|
||||||
:CP open contest
|
|
||||||
:CP open standings
|
|
||||||
<
|
|
||||||
State Restoration ~
|
State Restoration ~
|
||||||
:CP Restore state from current file.
|
:CP Restore state from current file.
|
||||||
Automatically detects platform, contest, problem,
|
Automatically detects platform, contest, problem,
|
||||||
|
|
@ -592,9 +580,6 @@ through the same code path as |:CP|.
|
||||||
*<Plug>(cp-submit)*
|
*<Plug>(cp-submit)*
|
||||||
<Plug>(cp-submit) Submit current solution. Equivalent to :CP submit.
|
<Plug>(cp-submit) Submit current solution. Equivalent to :CP submit.
|
||||||
|
|
||||||
*<Plug>(cp-open)*
|
|
||||||
<Plug>(cp-open) Open current problem URL in browser. Equivalent to :CP open.
|
|
||||||
|
|
||||||
*<Plug>(cp-race-stop)*
|
*<Plug>(cp-race-stop)*
|
||||||
<Plug>(cp-race-stop) Cancel active race countdown. Equivalent to :CP race stop.
|
<Plug>(cp-race-stop) Cancel active race countdown. Equivalent to :CP race stop.
|
||||||
|
|
||||||
|
|
@ -1036,22 +1021,6 @@ Submit the current solution to the online judge.
|
||||||
AtCoder Fully implemented.
|
AtCoder Fully implemented.
|
||||||
Others Not yet implemented.
|
Others Not yet implemented.
|
||||||
|
|
||||||
==============================================================================
|
|
||||||
OPEN *cp-open*
|
|
||||||
|
|
||||||
Open a platform URL for the current contest in the browser.
|
|
||||||
|
|
||||||
:CP open [problem|contest|standings]
|
|
||||||
Open the URL for the active problem, contest page, or standings.
|
|
||||||
Defaults to "problem" if no argument is given. Uses |vim.ui.open|.
|
|
||||||
Warns if the URL is unavailable (e.g. CSES has no standings page).
|
|
||||||
|
|
||||||
Platform support:
|
|
||||||
AtCoder problem, contest, standings
|
|
||||||
Codeforces problem, contest, standings
|
|
||||||
CSES problem, contest (no standings)
|
|
||||||
Others Not yet implemented.
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
ANSI COLORS AND HIGHLIGHTING *cp-ansi*
|
ANSI COLORS AND HIGHLIGHTING *cp-ansi*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field display_name string
|
---@field display_name string
|
||||||
---@field url string
|
---@field url string
|
||||||
---@field contest_url string
|
|
||||||
---@field standings_url string
|
|
||||||
|
|
||||||
---@class ContestSummary
|
---@class ContestSummary
|
||||||
---@field display_name string
|
---@field display_name string
|
||||||
|
|
@ -150,16 +148,12 @@ end
|
||||||
---@param contest_id string
|
---@param contest_id string
|
||||||
---@param problems Problem[]
|
---@param problems Problem[]
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param contest_url string
|
function M.set_contest_data(platform, contest_id, problems, url)
|
||||||
---@param standings_url string
|
|
||||||
function M.set_contest_data(platform, contest_id, problems, url, contest_url, standings_url)
|
|
||||||
vim.validate({
|
vim.validate({
|
||||||
platform = { platform, 'string' },
|
platform = { platform, 'string' },
|
||||||
contest_id = { contest_id, 'string' },
|
contest_id = { contest_id, 'string' },
|
||||||
problems = { problems, 'table' },
|
problems = { problems, 'table' },
|
||||||
url = { url, 'string' },
|
url = { url, 'string' },
|
||||||
contest_url = { contest_url, 'string' },
|
|
||||||
standings_url = { standings_url, 'string' },
|
|
||||||
})
|
})
|
||||||
|
|
||||||
cache_data[platform] = cache_data[platform] or {}
|
cache_data[platform] = cache_data[platform] or {}
|
||||||
|
|
@ -171,8 +165,6 @@ function M.set_contest_data(platform, contest_id, problems, url, contest_url, st
|
||||||
problems = problems,
|
problems = problems,
|
||||||
index_map = {},
|
index_map = {},
|
||||||
url = url,
|
url = url,
|
||||||
contest_url = contest_url,
|
|
||||||
standings_url = standings_url,
|
|
||||||
}
|
}
|
||||||
for i, p in ipairs(out.problems) do
|
for i, p in ipairs(out.problems) do
|
||||||
out.index_map[p.id] = i
|
out.index_map[p.id] = i
|
||||||
|
|
@ -182,25 +174,6 @@ function M.set_contest_data(platform, contest_id, problems, url, contest_url, st
|
||||||
M.save()
|
M.save()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param platform string?
|
|
||||||
---@param contest_id string?
|
|
||||||
---@param problem_id string?
|
|
||||||
---@return { problem: string|nil, contest: string|nil, standings: string|nil }|nil
|
|
||||||
function M.get_open_urls(platform, contest_id, problem_id)
|
|
||||||
if not platform or not contest_id then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
if not cache_data[platform] or not cache_data[platform][contest_id] then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local cd = cache_data[platform][contest_id]
|
|
||||||
return {
|
|
||||||
problem = cd.url ~= '' and problem_id and string.format(cd.url, problem_id) or nil,
|
|
||||||
contest = cd.contest_url ~= '' and cd.contest_url or nil,
|
|
||||||
standings = cd.standings_url ~= '' and cd.standings_url or nil,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param platform string
|
---@param platform string
|
||||||
---@param contest_id string
|
---@param contest_id string
|
||||||
function M.clear_contest_data(platform, contest_id)
|
function M.clear_contest_data(platform, contest_id)
|
||||||
|
|
|
||||||
|
|
@ -245,12 +245,6 @@ local function parse_command(args)
|
||||||
debug = debug,
|
debug = debug,
|
||||||
mode = mode,
|
mode = mode,
|
||||||
}
|
}
|
||||||
elseif first == 'open' then
|
|
||||||
local target = args[2] or 'problem'
|
|
||||||
if not vim.tbl_contains({ 'problem', 'contest', 'standings' }, target) then
|
|
||||||
return { type = 'error', message = 'Usage: :CP open [problem|contest|standings]' }
|
|
||||||
end
|
|
||||||
return { type = 'action', action = 'open', requires_context = true, subcommand = target }
|
|
||||||
elseif first == 'pick' then
|
elseif first == 'pick' then
|
||||||
local language = nil
|
local language = nil
|
||||||
if #args >= 3 and args[2] == '--lang' then
|
if #args >= 3 and args[2] == '--lang' then
|
||||||
|
|
@ -381,20 +375,6 @@ function M.handle_command(opts)
|
||||||
require('cp.race').start(cmd.platform, cmd.contest, cmd.language)
|
require('cp.race').start(cmd.platform, cmd.contest, cmd.language)
|
||||||
elseif cmd.action == 'race_stop' then
|
elseif cmd.action == 'race_stop' then
|
||||||
require('cp.race').stop()
|
require('cp.race').stop()
|
||||||
elseif cmd.action == 'open' then
|
|
||||||
local cache = require('cp.cache')
|
|
||||||
cache.load()
|
|
||||||
local urls =
|
|
||||||
cache.get_open_urls(state.get_platform(), state.get_contest_id(), state.get_problem_id())
|
|
||||||
local url = urls and urls[cmd.subcommand]
|
|
||||||
if not url or url == '' then
|
|
||||||
logger.log(
|
|
||||||
("No URL available for '%s'"):format(cmd.subcommand),
|
|
||||||
{ level = vim.log.levels.WARN }
|
|
||||||
)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
vim.ui.open(url)
|
|
||||||
elseif cmd.action == 'login' then
|
elseif cmd.action == 'login' then
|
||||||
require('cp.credentials').login(cmd.platform)
|
require('cp.credentials').login(cmd.platform)
|
||||||
elseif cmd.action == 'logout' then
|
elseif cmd.action == 'logout' then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ M.ACTIONS = {
|
||||||
'race',
|
'race',
|
||||||
'stress',
|
'stress',
|
||||||
'submit',
|
'submit',
|
||||||
'open',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.PLATFORM_DISPLAY_NAMES = {
|
M.PLATFORM_DISPLAY_NAMES = {
|
||||||
|
|
|
||||||
|
|
@ -243,14 +243,7 @@ function M.setup_contest(platform, contest_id, problem_id, language)
|
||||||
contest_id,
|
contest_id,
|
||||||
vim.schedule_wrap(function(result)
|
vim.schedule_wrap(function(result)
|
||||||
local problems = result.problems or {}
|
local problems = result.problems or {}
|
||||||
cache.set_contest_data(
|
cache.set_contest_data(platform, contest_id, problems, result.url)
|
||||||
platform,
|
|
||||||
contest_id,
|
|
||||||
problems,
|
|
||||||
result.url,
|
|
||||||
result.contest_url or '',
|
|
||||||
result.standings_url or ''
|
|
||||||
)
|
|
||||||
local prov = state.get_provisional()
|
local prov = state.get_provisional()
|
||||||
if not prov or prov.platform ~= platform or prov.contest_id ~= contest_id then
|
if not prov or prov.platform ~= platform or prov.contest_id ~= contest_id then
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -606,8 +606,6 @@ class AtcoderScraper(BaseScraper):
|
||||||
contest_id=contest_id,
|
contest_id=contest_id,
|
||||||
problems=problems,
|
problems=problems,
|
||||||
url=f"https://atcoder.jp/contests/{contest_id}/tasks/{contest_id}_%s",
|
url=f"https://atcoder.jp/contests/{contest_id}/tasks/{contest_id}_%s",
|
||||||
contest_url=f"https://atcoder.jp/contests/{contest_id}",
|
|
||||||
standings_url=f"https://atcoder.jp/contests/{contest_id}/standings",
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._metadata_error(str(e))
|
return self._metadata_error(str(e))
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,6 @@ class CodeforcesScraper(BaseScraper):
|
||||||
contest_id=contest_id,
|
contest_id=contest_id,
|
||||||
problems=problems,
|
problems=problems,
|
||||||
url=f"https://codeforces.com/contest/{contest_id}/problem/%s",
|
url=f"https://codeforces.com/contest/{contest_id}/problem/%s",
|
||||||
contest_url=f"https://codeforces.com/contest/{contest_id}",
|
|
||||||
standings_url=f"https://codeforces.com/contest/{contest_id}/standings",
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._metadata_error(str(e))
|
return self._metadata_error(str(e))
|
||||||
|
|
@ -403,8 +401,7 @@ def _login_headless_cf(credentials: dict[str, str]) -> LoginResult:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
browser_cookies = session.context.cookies()
|
browser_cookies = session.context.cookies()
|
||||||
if any(c.get("name") == "X-User-Handle" for c in browser_cookies):
|
cookie_cache.write_text(json.dumps(browser_cookies))
|
||||||
cookie_cache.write_text(json.dumps(browser_cookies))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -481,7 +478,10 @@ def _submit_headless(
|
||||||
if "/enter" in page.url or "/login" in page.url:
|
if "/enter" in page.url or "/login" in page.url:
|
||||||
needs_relogin = True
|
needs_relogin = True
|
||||||
return
|
return
|
||||||
_solve_turnstile(page)
|
try:
|
||||||
|
_solve_turnstile(page)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
page.select_option(
|
page.select_option(
|
||||||
'select[name="submittedProblemIndex"]',
|
'select[name="submittedProblemIndex"]',
|
||||||
|
|
@ -550,7 +550,7 @@ def _submit_headless(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
browser_cookies = session.context.cookies()
|
browser_cookies = session.context.cookies()
|
||||||
if any(c.get("name") == "X-User-Handle" for c in browser_cookies):
|
if browser_cookies:
|
||||||
cookie_cache.write_text(json.dumps(browser_cookies))
|
cookie_cache.write_text(json.dumps(browser_cookies))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -218,8 +218,6 @@ class CSESScraper(BaseScraper):
|
||||||
contest_id=contest_id,
|
contest_id=contest_id,
|
||||||
problems=problems,
|
problems=problems,
|
||||||
url="https://cses.fi/problemset/task/%s",
|
url="https://cses.fi/problemset/task/%s",
|
||||||
contest_url="https://cses.fi/problemset",
|
|
||||||
standings_url="",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def scrape_contest_list(self) -> ContestListResult:
|
async def scrape_contest_list(self) -> ContestListResult:
|
||||||
|
|
@ -354,12 +352,8 @@ class CSESScraper(BaseScraper):
|
||||||
f"{API_URL}/login", headers=HEADERS, timeout=HTTP_TIMEOUT
|
f"{API_URL}/login", headers=HEADERS, timeout=HTTP_TIMEOUT
|
||||||
)
|
)
|
||||||
api_data = api_resp.json()
|
api_data = api_resp.json()
|
||||||
token: str | None = api_data.get("X-Auth-Token")
|
token: str = api_data["X-Auth-Token"]
|
||||||
auth_url: str | None = api_data.get("authentication_url")
|
auth_url: str = api_data["authentication_url"]
|
||||||
if not token:
|
|
||||||
raise RuntimeError("CSES API login response missing 'X-Auth-Token'")
|
|
||||||
if not auth_url:
|
|
||||||
raise RuntimeError("CSES API login response missing 'authentication_url'")
|
|
||||||
|
|
||||||
auth_page = await client.get(auth_url, headers=HEADERS, timeout=HTTP_TIMEOUT)
|
auth_page = await client.get(auth_url, headers=HEADERS, timeout=HTTP_TIMEOUT)
|
||||||
auth_csrf = re.search(r'name="csrf_token" value="([^"]+)"', auth_page.text)
|
auth_csrf = re.search(r'name="csrf_token" value="([^"]+)"', auth_page.text)
|
||||||
|
|
@ -394,8 +388,8 @@ class CSESScraper(BaseScraper):
|
||||||
timeout=HTTP_TIMEOUT,
|
timeout=HTTP_TIMEOUT,
|
||||||
)
|
)
|
||||||
return r.status_code == 200
|
return r.status_code == 200
|
||||||
except (httpx.ConnectError, httpx.TimeoutException, httpx.NetworkError):
|
except Exception:
|
||||||
raise
|
return False
|
||||||
|
|
||||||
async def submit(
|
async def submit(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,6 @@ class MetadataResult(ScrapingResult):
|
||||||
contest_id: str = ""
|
contest_id: str = ""
|
||||||
problems: list[ProblemSummary] = Field(default_factory=list)
|
problems: list[ProblemSummary] = Field(default_factory=list)
|
||||||
url: str
|
url: str
|
||||||
contest_url: str = ""
|
|
||||||
standings_url: str = ""
|
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue