Skip to content
This repository has been archived by the owner on Jun 3, 2021. It is now read-only.

Commit

Permalink
feat: rewrite login & config, impl cookie helper
Browse files Browse the repository at this point in the history
  • Loading branch information
beetcb committed May 23, 2021
1 parent f0e3d93 commit a35759c
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 50 deletions.
28 changes: 25 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!/usr/bin/env node
import { loadConfFromToml, promptToGetConf, getSchoolInfos } from './conf'
import { sstore } from './index'
;(async () => {
const argv = process.argv[2] || ''
const argv2 = process.argv[3]
Expand All @@ -18,18 +20,38 @@ All Commands:
break
}
case 'user': {
break
}
case 'school': {
const config = await promptToGetConf()
if (config) {
const schoolInfos = getSchoolInfos(config)
if (schoolInfos) {
sstore.set('schools', schoolInfos)
}
sstore.set('users', config)
}
break
}
case 'rm': {
sstore.delete(argv2)
break
}
case 'sign': {
// get cookie
// load plugin
// close cea
break
}
case 'load': {
const config = loadConfFromToml()
if (config) {
const schoolInfos = getSchoolInfos(config)
if (schoolInfos) {
sstore.set('schools', schoolInfos)
}
sstore.set('users', config)
sstore.set('config', config)
}
}
}

sstore.close()
})()
Empty file removed src/compatibility/api.ts
Empty file.
52 changes: 52 additions & 0 deletions src/compatibility/edge-case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const schoolEdgeCases = {
宁波大学: {
formIdx: 0,
rememberMe: 'on',
cookiePath: '/',
checkCaptchaPath: '/needCaptcha.html',
getCaptchaPath: '/captcha.html',
},
}

// we will using proxy to get the default properties
const defaultProps = {
rememberMe: true,
getCaptchaPath: '/getCaptcha.htl',
checkCaptchaPath: '/checkNeedCaptcha.htl',
cookiePath: '/authserver',
formIdx: 2,
pwdEncrypt: true,
}

const iapDefaultProps = {
lt: '/security/lt',
rememberMe: true,
checkCaptchaPath: '/checkNeedCaptcha',
getCaptchaPath: '/generateCaptcha',
}

export type EdgeCasesSchools = keyof typeof schoolEdgeCases
type NoIapDefaultProps = typeof defaultProps
type IapDefaultProps = typeof iapDefaultProps

export type DefaultProps = NoIapDefaultProps & IapDefaultProps

/**
* handle edge cases, proxy default properties
*/
export default (schoolName: EdgeCasesSchools, isIap: boolean) =>
schoolName
? (new Proxy(schoolEdgeCases[schoolName] || {}, {
get(target, prop, receiver) {
if (
target[prop as keyof NoIapDefaultProps & keyof IapDefaultProps] ===
undefined
) {
return isIap
? iapDefaultProps[prop as keyof IapDefaultProps]
: defaultProps[prop as keyof NoIapDefaultProps]
}
return Reflect.get(target, prop, receiver)
},
}) as unknown as DefaultProps)
: ({} as DefaultProps)
86 changes: 51 additions & 35 deletions src/conf.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,27 @@
import { UsersConf, UserConfOpts } from './types/conf'
import { UserAction } from './constants'
import { UsersConf, UserConfOpts, SchoolConf } from './types/conf'

import { parse } from '@iarna/toml'
import { prompt } from 'enquirer'
import { resolve } from 'path'
import { AnyObject } from './types/helper'
import { UserAction } from './constants'

import fetch, { Response } from 'node-fetch'
import log from './utils/logger'
import fs from 'fs'

export function loadConfFromToml(): UsersConf | null {
const path = resolve('./conf.toml')
if (fs.existsSync(path)) {
const usersConf = parse(fs.readFileSync(path, 'utf8')) as UsersConf
const usersConf = parse(fs.readFileSync(path, 'utf8'))!.users as UsersConf
return usersConf
}
return null
}

export function loadConfFromEnv({
users,
}: {
users: string
}): UsersConf | null {
if (users) {
const loadedUsers = users.split('\n').map((user) => {
let addr: string | Array<string> = user.split('home ')[1]
addr = addr ? addr.split(' ') : ['']

const [school, username, password, alias] = user.split(' ')
const userConfOpts: UserConfOpts = {
school,
username,
password,
alias,
addr,
}
return userConfOpts
})
return { users: loadedUsers }
} else {
return null
}
}

export async function promptToGetConf(): Promise<UsersConf | null> {
const toml = loadConfFromToml()
const loadedUsers = toml ? toml.users : []
const loadedUsers = toml ? toml : []

const actionNaire = {
type: 'list',
Expand Down Expand Up @@ -85,7 +63,7 @@ export async function promptToGetConf(): Promise<UsersConf | null> {
{
name: 'school',
message:
'学校的英文简称(推荐,仅部分学校支持使用简称)\n请参阅 https://github.com/beetcb/cea/blob/master/docs/abbrList.sh 自行判断\n或中文全称(备用选项,所有学校均支持):',
'学校的英文简称(推荐,仅部分学校支持使用简称)\n其它学校请参阅 https://github.com/beetcb/cea/blob/master/docs/abbrList.sh 寻找简称',
initial: 'whu',
},
{
Expand All @@ -97,7 +75,7 @@ export async function promptToGetConf(): Promise<UsersConf | null> {
],
}
const { addUser } = (await prompt([form])) as { addUser: UserConfOpts }
return { users: [...loadedUsers, addUser] }
return [...loadedUsers, addUser]
}
case UserAction.DELETE: {
const deleteUserNaire = {
Expand All @@ -107,12 +85,50 @@ export async function promptToGetConf(): Promise<UsersConf | null> {
choices: loadedUsers.map((e) => e.alias),
}
const res = (await prompt([deleteUserNaire])) as { deleteUser: string }
return {
users: [...loadedUsers.filter((val) => val.alias !== res.deleteUser)],
}
return [...loadedUsers.filter((val) => val.alias !== res.deleteUser)]
}
case UserAction.CANCEL: {
return null
}
}
}

export async function getSchoolInfos(
users: UsersConf
): Promise<SchoolConf | null> {
let res: Response,
schoolInfos = {} as SchoolConf
const schoolNamesSet = new Set(...users.map((e) => e.school))
for (const abbreviation in schoolNamesSet) {
res = (await fetch(
`https://mobile.campushoy.com/v6/config/guest/tenant/info?ids=${abbreviation}`
).catch((err) => log.error(err))) as Response

const data = JSON.parse(
(await res.text().catch((err) => log.error(err))) as string
).data[0] as AnyObject

let origin = new URL(data.ampUrl).origin
const casOrigin = data.idsUrl

// fall back to ampUrl2 when campusphere not included in the `origin`
if (!origin.includes('campusphere')) {
origin = new URL(data.ampUrl2).origin
}

schoolInfos[abbreviation] = {
loginStartEndpoint: `${origin}/iap/login?service=${encodeURIComponent(
`${origin}/portal/login`
)}`,
swms: new URL(casOrigin),
chineseName: data.name,
campusphere: new URL(origin),
isIap: data.joinType !== 'NOTCLOUD',
}

log.success({ message: `你的学校 ${data.name} 已完成设定` })
return schoolInfos
}
log.error('未配置学校信息')
return null
}
54 changes: 54 additions & 0 deletions src/crawler/capcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createWorker } from 'tesseract.js'
import fetch from 'node-fetch'
import fs from 'fs'
const tessdataPath = '/tmp/eng.traineddata.gz'

async function downloadTessdata() {
process.env.TESSDATA_PREFIX = '/tmp'
// check folder exists
if (!fs.existsSync('/tmp')) {
fs.mkdirSync('/tmp')
} else {
// check file exists
if (fs.existsSync(tessdataPath)) {
return
}
}
download(
'https://beetcb.gitee.io/filetransfer/tmp/eng.traineddata.gz',
tessdataPath
)
}

async function download(url: string, filename: string): string {
const stream = fs.createWriteStream(filename)
const res = await fetch(url)
const result = await new Promise((resolve, reject) => {
res.body.pipe(stream)
res.body.on('error', reject)
stream.on('close', () => resolve(`Downloaded tess data as ${filename}`))
})
return result as string
}

async function ocr(captchaUrl: string) {
await downloadTessdata()
const worker = createWorker({
langPath: '/tmp',
cachePath: '/tmp',
})
await worker.load()
await worker.loadLanguage('eng')
await worker.initialize('eng')
await worker.setParameters({
tessedit_char_whitelist:
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890',
})
const {
data: { text },
} = await worker.recognize(captchaUrl)
await worker.terminate()
return text
}

export default ocr
Loading

0 comments on commit a35759c

Please sign in to comment.