Skip to content

Commit a53655b

Browse files
committed
feat: re-add cookie-based login method
1 parent b495812 commit a53655b

File tree

1 file changed

+116
-6
lines changed

1 file changed

+116
-6
lines changed

src/leetCodeManager.ts

+116-6
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import { EventEmitter } from "events";
66
import * as vscode from "vscode";
77
import { leetCodeChannel } from "./leetCodeChannel";
88
import { leetCodeExecutor } from "./leetCodeExecutor";
9-
import { Endpoint, loginArgsMapping, urls, urlsCn, UserStatus } from "./shared";
9+
import { Endpoint, IQuickItemEx, loginArgsMapping, urls, urlsCn, UserStatus } from "./shared";
1010
import { createEnvOption } from "./utils/cpUtils";
1111
import { DialogType, openUrl, promptForOpenOutputChannel } from "./utils/uiUtils";
1212
import * as wsl from "./utils/wslUtils";
1313
import { getLeetCodeEndpoint } from "./commands/plugin";
1414
import { globalState } from "./globalState";
1515
import { queryUserData } from "./request/query-user-data";
16-
import { parseQuery, sleep } from "./utils/toolUtils";
16+
import { parseQuery } from "./utils/toolUtils";
1717

1818
class LeetCodeManager extends EventEmitter {
1919
private currentUser: string | undefined;
@@ -68,8 +68,117 @@ class LeetCodeManager extends EventEmitter {
6868
}
6969
}
7070

71+
public async signInWithCli(loginMethod: string): Promise<void> {
72+
const commandArg: string | undefined = loginArgsMapping.get(loginMethod)
73+
if (!commandArg) {
74+
throw new Error(`The login method "${loginMethod}" is not supported.`)
75+
}
76+
const isByCookie: boolean = loginMethod === 'Cookie'
77+
const inMessage: string = isByCookie ? 'sign in by cookie' : 'sign in'
78+
79+
try {
80+
const userName: string | undefined = await new Promise(
81+
async (resolve: (res: string | undefined) => void, reject: (e: Error) => void): Promise<void> => {
82+
const leetCodeBinaryPath: string = await leetCodeExecutor.getLeetCodeBinaryPath()
83+
84+
const childProc: cp.ChildProcess = wsl.useWsl()
85+
? cp.spawn('wsl', [leetCodeExecutor.node, leetCodeBinaryPath, 'user', commandArg], { shell: true })
86+
: cp.spawn(leetCodeExecutor.node, [leetCodeBinaryPath, 'user', commandArg], {
87+
shell: true,
88+
env: createEnvOption(),
89+
})
90+
91+
childProc.stdout?.on('data', async (data: string | Buffer) => {
92+
data = data.toString()
93+
leetCodeChannel.append(data)
94+
if (data.includes('twoFactorCode')) {
95+
const twoFactor: string | undefined = await vscode.window.showInputBox({
96+
prompt: 'Enter two-factor code.',
97+
ignoreFocusOut: true,
98+
validateInput: (s: string): string | undefined => (s && s.trim() ? undefined : 'The input must not be empty'),
99+
})
100+
if (!twoFactor) {
101+
childProc.kill()
102+
return resolve(undefined)
103+
}
104+
childProc.stdin?.write(`${twoFactor}\n`)
105+
}
106+
const successMatch: RegExpMatchArray | null = data.match(this.successRegex)
107+
if (successMatch && successMatch[1]) {
108+
childProc.stdin?.end()
109+
return resolve(successMatch[1])
110+
} else if (data.match(this.failRegex)) {
111+
childProc.stdin?.end()
112+
return reject(new Error('Faile to login'))
113+
}
114+
})
115+
116+
childProc.stderr?.on('data', (data: string | Buffer) => leetCodeChannel.append(data.toString()))
117+
118+
childProc.on('error', reject)
119+
const name: string | undefined = await vscode.window.showInputBox({
120+
prompt: 'Enter username or E-mail.',
121+
ignoreFocusOut: true,
122+
validateInput: (s: string): string | undefined => (s && s.trim() ? undefined : 'The input must not be empty'),
123+
})
124+
if (!name) {
125+
childProc.kill()
126+
return resolve(undefined)
127+
}
128+
childProc.stdin?.write(`${name}\n`)
129+
const pwd: string | undefined = await vscode.window.showInputBox({
130+
prompt: isByCookie ? 'Enter cookie' : 'Enter password.',
131+
password: true,
132+
ignoreFocusOut: true,
133+
validateInput: (s: string): string | undefined =>
134+
s ? undefined : isByCookie ? 'Cookie must not be empty' : 'Password must not be empty',
135+
})
136+
if (!pwd) {
137+
childProc.kill()
138+
return resolve(undefined)
139+
}
140+
childProc.stdin?.write(`${pwd}\n`)
141+
}
142+
)
143+
if (userName) {
144+
vscode.window.showInformationMessage(`Successfully ${inMessage}.`)
145+
this.currentUser = userName
146+
this.userStatus = UserStatus.SignedIn
147+
this.emit('statusChanged')
148+
}
149+
} catch (error) {
150+
promptForOpenOutputChannel(`Failed to ${inMessage}. Please open the output channel for details`, DialogType.error)
151+
}
152+
}
153+
71154
public async signIn(): Promise<void> {
72-
openUrl(this.getAuthLoginUrl());
155+
const picks: Array<IQuickItemEx<string>> = []
156+
picks.push(
157+
{
158+
label: 'Web Authorization',
159+
detail: 'Open browser to authorize login on the website',
160+
value: 'WebAuth',
161+
description: '[Recommended]'
162+
},
163+
{
164+
label: 'LeetCode Cookie',
165+
detail: 'Use LeetCode cookie copied from browser to login',
166+
value: 'Cookie',
167+
}
168+
)
169+
170+
const choice: IQuickItemEx<string> | undefined = await vscode.window.showQuickPick(picks)
171+
if (!choice) {
172+
return
173+
}
174+
const loginMethod: string = choice.value
175+
176+
if (loginMethod === 'WebAuth') {
177+
openUrl(this.getAuthLoginUrl())
178+
return
179+
}
180+
181+
this.signInWithCli(loginMethod)
73182
}
74183

75184
public async signOut(): Promise<void> {
@@ -136,15 +245,16 @@ class LeetCodeManager extends EventEmitter {
136245
} else if (data.match(this.failRegex)) {
137246
childProc.stdin?.end();
138247
return reject(new Error("Faile to login"));
248+
} else if (data.match(/login: /)){
249+
childProc.stdin?.write(`${name}\n`);
250+
} else if (data.match(/cookie: /)){
251+
childProc.stdin?.write(`${cookie}\n`);
139252
}
140253
});
141254

142255
childProc.stderr?.on("data", (data: string | Buffer) => leetCodeChannel.append(data.toString()));
143256

144257
childProc.on("error", reject);
145-
childProc.stdin?.write(`${name}\n`);
146-
await sleep(800);
147-
childProc.stdin?.write(`${cookie}\n`);
148258
});
149259
}
150260
}

0 commit comments

Comments
 (0)