Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/cp.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -453,13 +453,15 @@ COMMANDS *cp-commands*
any previously saved credentials.
If [platform] is omitted, uses the active platform.
Examples: >
:CP login
:CP login atcoder
:CP login codeforces
<
:CP logout [platform]
Remove stored credentials for a platform.
If [platform] is omitted, uses the active platform.
Examples: >
:CP logout
:CP logout atcoder
<
:CP {platform} signup
Expand Down
2 changes: 2 additions & 0 deletions lua/cp/cache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function M.load()

if vim.fn.filereadable(cache_file) == 0 then
vim.fn.writefile({}, cache_file)
vim.fn.setfperm(cache_file, 'rw-------')
loaded = true
return
end
Expand Down Expand Up @@ -107,6 +108,7 @@ function M.save()
local encoded = vim.json.encode(cache_data)
local lines = vim.split(encoded, '\n')
vim.fn.writefile(lines, cache_file)
vim.fn.setfperm(cache_file, 'rw-------')
end)
end

Expand Down
42 changes: 34 additions & 8 deletions lua/cp/commands/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ local function parse_command(args)
end
end

if (first == 'login' or first == 'logout' or first == 'signup') and #args == 1 then
return { type = 'action', action = first, requires_context = true, platform = nil }
end

if #args == 1 then
return {
type = 'problem_jump',
Expand Down Expand Up @@ -378,6 +382,7 @@ function M.handle_command(opts)
if not restore.restore_from_current_file() then
return
end
vim.cmd.redraw()
end

local setup = require('cp.setup')
Expand Down Expand Up @@ -421,24 +426,45 @@ function M.handle_command(opts)
end
vim.ui.open(url)
elseif cmd.action == 'login' then
if not check_platform_enabled(cmd.platform) then
local p = cmd.platform or state.get_platform()
if not p then
logger.log(
'No platform active. Usage: :CP <platform> login',
{ level = vim.log.levels.ERROR }
)
return
end
require('cp.credentials').login(cmd.platform)
if not check_platform_enabled(p) then
return
end
require('cp.credentials').login(p)
elseif cmd.action == 'logout' then
if not check_platform_enabled(cmd.platform) then
local p = cmd.platform or state.get_platform()
if not p then
logger.log(
'No platform active. Usage: :CP <platform> logout',
{ level = vim.log.levels.ERROR }
)
return
end
require('cp.credentials').logout(cmd.platform)
if not check_platform_enabled(p) then
return
end
require('cp.credentials').logout(p)
elseif cmd.action == 'signup' then
local url = constants.SIGNUP_URLS[cmd.platform]
if not url then
local p = cmd.platform or state.get_platform()
if not p then
logger.log(
("No signup URL available for '%s'"):format(cmd.platform),
{ level = vim.log.levels.WARN }
'No platform active. Usage: :CP <platform> signup',
{ level = vim.log.levels.ERROR }
)
return
end
local url = constants.SIGNUP_URLS[p]
if not url then
logger.log(("No signup URL available for '%s'"):format(p), { level = vim.log.levels.WARN })
return
end
vim.ui.open(url)
end
elseif cmd.type == 'problem_jump' then
Expand Down
79 changes: 42 additions & 37 deletions lua/cp/credentials.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,50 @@ local STATUS_MESSAGES = {
---@param platform string
---@param display string
local function prompt_and_login(platform, display)
vim.ui.input({ prompt = '[cp.nvim]: ' .. display .. ' username: ' }, function(username)
if not username or username == '' then
logger.log('Cancelled', { level = vim.log.levels.WARN })
return
end
vim.fn.inputsave()
local password = vim.fn.inputsecret('[cp.nvim]: ' .. display .. ' password: ')
vim.fn.inputrestore()
if not password or password == '' then
logger.log('Cancelled', { level = vim.log.levels.WARN })
return
end
vim.ui.input(
{ prompt = '[cp.nvim]: ' .. display .. ' username (<Esc> to cancel): ' },
function(username)
if not username or username == '' then
logger.log(display .. ' login cancelled', { level = vim.log.levels.WARN })
return
end
vim.fn.inputsave()
local password = vim.fn.inputsecret('[cp.nvim]: ' .. display .. ' password: ')
vim.fn.inputrestore()
if not password or password == '' then
logger.log(display .. ' login cancelled', { level = vim.log.levels.WARN })
return
end

local credentials = { username = username, password = password }
local credentials = { username = username, password = password }

local scraper = require('cp.scraper')
scraper.login(platform, credentials, function(ev)
vim.schedule(function()
local msg = STATUS_MESSAGES[ev.status] or ev.status
logger.log(display .. ': ' .. msg, { level = vim.log.levels.INFO, override = true })
local scraper = require('cp.scraper')
scraper.login(platform, credentials, function(ev)
vim.schedule(function()
local msg = STATUS_MESSAGES[ev.status] or ev.status
logger.log(display .. ': ' .. msg, { level = vim.log.levels.INFO, override = true })
end)
end, function(result)
vim.schedule(function()
if result.success then
cache.set_credentials(platform, credentials)
logger.log(
display .. ' login successful',
{ level = vim.log.levels.INFO, override = true }
)
else
local err = result.error or 'unknown error'
cache.clear_credentials(platform)
logger.log(
display .. ' login failed: ' .. (constants.LOGIN_ERRORS[err] or err),
{ level = vim.log.levels.ERROR }
)
prompt_and_login(platform, display)
end
end)
end)
end, function(result)
vim.schedule(function()
if result.success then
cache.set_credentials(platform, credentials)
logger.log(
display .. ' login successful',
{ level = vim.log.levels.INFO, override = true }
)
else
local err = result.error or 'unknown error'
cache.clear_credentials(platform)
logger.log(
display .. ' login failed: ' .. (constants.LOGIN_ERRORS[err] or err),
{ level = vim.log.levels.ERROR }
)
end
end)
end)
end)
end
)
end

---@param platform string?
Expand Down Expand Up @@ -117,6 +121,7 @@ function M.logout(platform)
if ok and type(data) == 'table' then
data[platform] = nil
vim.fn.writefile({ vim.fn.json_encode(data) }, cookie_file)
vim.fn.setfperm(cookie_file, 'rw-------')
end
end
logger.log(display .. ' credentials cleared', { level = vim.log.levels.INFO, override = true })
Expand Down
6 changes: 3 additions & 3 deletions lua/cp/scraper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ function M.login(platform, credentials, on_status, callback)
local done = false
run_scraper(platform, 'login', {}, {
ndjson = true,
env_extra = { CP_CREDENTIALS = vim.json.encode(credentials) },
stdin = vim.json.encode(credentials),
on_event = function(ev)
if ev.credentials ~= nil and next(ev.credentials) ~= nil then
require('cp.cache').set_credentials(platform, ev.credentials)
Expand Down Expand Up @@ -392,9 +392,9 @@ function M.submit(
local done = false
run_scraper(platform, 'submit', { contest_id, problem_id, language, source_file }, {
ndjson = true,
env_extra = { CP_CREDENTIALS = vim.json.encode(credentials) },
stdin = vim.json.encode(credentials),
on_event = function(ev)
if ev.credentials ~= nil then
if ev.credentials ~= nil and next(ev.credentials) ~= nil then
require('cp.cache').set_credentials(platform, ev.credentials)
end
if ev.status ~= nil then
Expand Down
56 changes: 34 additions & 22 deletions lua/cp/submit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,27 @@ local function prompt_credentials(platform, callback)
callback(saved)
return
end
vim.ui.input({ prompt = platform .. ' username: ' }, function(username)
if not username or username == '' then
logger.log('Submit cancelled', { level = vim.log.levels.WARN })
return
end
vim.fn.inputsave()
local password = vim.fn.inputsecret(platform .. ' password: ')
vim.fn.inputrestore()
vim.cmd.redraw()
if not password or password == '' then
logger.log('Submit cancelled', { level = vim.log.levels.WARN })
return
local display = constants.PLATFORM_DISPLAY_NAMES[platform] or platform
vim.ui.input(
{ prompt = '[cp.nvim]: ' .. display .. ' username (<Esc> to cancel): ' },
function(username)
if not username or username == '' then
logger.log('Submit cancelled', { level = vim.log.levels.WARN })
return
end
vim.fn.inputsave()
local password = vim.fn.inputsecret('[cp.nvim]: ' .. display .. ' password: ')
vim.fn.inputrestore()
vim.cmd.redraw()
if not password or password == '' then
logger.log('Submit cancelled', { level = vim.log.levels.WARN })
return
end
local creds = { username = username, password = password }
cache.set_credentials(platform, creds)
callback(creds)
end
local creds = { username = username, password = password }
cache.set_credentials(platform, creds)
callback(creds)
end)
)
end

---@param opts { language?: string }?
Expand Down Expand Up @@ -86,7 +90,7 @@ function M.submit(opts)
end
end

prompt_credentials(platform, function(creds)
local function do_submit(creds)
vim.cmd.update()

require('cp.scraper').submit(
Expand All @@ -112,16 +116,24 @@ function M.submit(opts)
local err = result and result.error or 'unknown error'
if err == 'bad_credentials' or err:match('^Login failed') then
cache.clear_credentials(platform)
logger.log(
'Submit failed: ' .. (constants.LOGIN_ERRORS[err] or err),
{ level = vim.log.levels.ERROR }
)
prompt_credentials(platform, do_submit)
else
logger.log(
'Submit failed: ' .. (constants.LOGIN_ERRORS[err] or err),
{ level = vim.log.levels.ERROR }
)
end
logger.log(
'Submit failed: ' .. (constants.LOGIN_ERRORS[err] or err),
{ level = vim.log.levels.ERROR }
)
end
end)
end
)
end)
end

prompt_credentials(platform, do_submit)
end

return M
4 changes: 4 additions & 0 deletions plugin/cp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ end, {
vim.list_extend(candidates, platforms)
table.insert(candidates, 'cache')
table.insert(candidates, 'pick')
if platform then
table.insert(candidates, 'login')
table.insert(candidates, 'logout')
end
if platform and contest_id then
vim.list_extend(
candidates,
Expand Down
6 changes: 4 additions & 2 deletions scrapers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ def save_platform_cookies(platform: str, data: Any) -> None:
existing = {}
existing[platform] = data
_COOKIE_FILE.write_text(json.dumps(existing))
_COOKIE_FILE.chmod(0o600)


def clear_platform_cookies(platform: str) -> None:
try:
existing = json.loads(_COOKIE_FILE.read_text())
existing.pop(platform, None)
_COOKIE_FILE.write_text(json.dumps(existing))
_COOKIE_FILE.chmod(0o600)
except Exception:
pass

Expand Down Expand Up @@ -160,7 +162,7 @@ async def _run_cli_async(self, args: list[str]) -> int:
).model_dump_json()
)
return 1
creds_raw = os.environ.get("CP_CREDENTIALS", "{}")
creds_raw = sys.stdin.read()
try:
credentials = json.loads(creds_raw)
except json.JSONDecodeError:
Expand All @@ -173,7 +175,7 @@ async def _run_cli_async(self, args: list[str]) -> int:
return 0 if result.success else 1

case "login":
creds_raw = os.environ.get("CP_CREDENTIALS", "{}")
creds_raw = sys.stdin.read()
try:
credentials = json.loads(creds_raw)
except json.JSONDecodeError:
Expand Down
2 changes: 0 additions & 2 deletions scrapers/kattis.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,6 @@ async def login(self, credentials: dict[str, str]) -> LoginResult:
return LoginResult(
success=True,
error="",
credentials={"username": username, "password": password},
)

print(json.dumps({"status": "logging_in"}), flush=True)
Expand All @@ -426,7 +425,6 @@ async def login(self, credentials: dict[str, str]) -> LoginResult:
return LoginResult(
success=True,
error="",
credentials={"username": username, "password": password},
)


Expand Down
2 changes: 0 additions & 2 deletions scrapers/usaco.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,6 @@ async def login(self, credentials: dict[str, str]) -> LoginResult:
return LoginResult(
success=True,
error="",
credentials={"username": username, "password": password},
)

print(json.dumps({"status": "logging_in"}), flush=True)
Expand All @@ -549,7 +548,6 @@ async def login(self, credentials: dict[str, str]) -> LoginResult:
return LoginResult(
success=True,
error="",
credentials={"username": username, "password": password},
)


Expand Down
Loading