@@ -0,0 +1,218 @@
//[open_source: true]
//[create_at: 2026-05-17 03:00:00]
//[title: 起点读书签到]
//[author: Hermes]
//[service: BOSS]
//[class: 工具类]
//[version: 2.0.0]
//[platform: web,qq,wx,tg,tb]
//[public: false]
//[price: 0]
//[disable: false]
//[description: 起点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,并在机器人内执行签到/查询/导出/删除。签名沿用上游 QDReader 的 api.120399.xyz 签名网关。]
//[rule: ^起点(ck|CK|cookie|Cookie|签到|读书)(.*)$]
//[rule: ^qdreader(.*)$]
//[param: {"required":false,"key":"otto.qdreader_cookie_json","bool":false,"placeholder":"{\"uid\":\"cookie\"}","name":"QDReader Cookie JSON","desc":"机器人交互维护,JSON Map 格式"}]
//[param: {"required":false,"key":"otto.qdreader_sign_api","bool":false,"placeholder":"https://api.120399.xyz","name":"QDReader 签名网关","desc":"默认 https://api.120399.xyz"}]
var STORE _KEY = "qdreader_cookie_json"
var SIGN _API _KEY = "qdreader_sign_api"
var DEFAULT _SIGN _API = "https://api.120399.xyz"
var QIDIAN _BASE = "https://h5.if.qidian.com"
function reply ( msg ) {
if ( typeof sender !== "undefined" && sender . reply ) sender . reply ( msg )
else if ( typeof sendText === "function" ) sendText ( typeof msg === "string" ? msg : JSON . stringify ( msg ) )
else console . log ( typeof msg === "string" ? msg : JSON . stringify ( msg ) )
}
function safeJsonParse ( s , def ) { try { return JSON . parse ( s ) } catch ( e ) { return def } }
function getStore ( ) {
var raw = get ( STORE _KEY ) || "{}"
var obj = safeJsonParse ( raw , { } )
if ( ! obj || typeof obj !== "object" || Array . isArray ( obj ) ) return { }
return obj
}
function setStore ( obj ) { var s = JSON . stringify ( obj ) ; set ( STORE _KEY , s ) ; return s }
function parseCookieString ( cookie ) {
var jar = { }
String ( cookie || "" ) . split ( ";" ) . forEach ( function ( part ) {
var i = part . indexOf ( "=" )
if ( i <= 0 ) return
var k = part . slice ( 0 , i ) . trim ( )
var v = part . slice ( i + 1 ) . trim ( )
if ( k ) jar [ k ] = v
} )
return jar
}
function cookieToString ( jar ) {
var arr = [ ]
for ( var k in jar ) if ( jar [ k ] !== undefined && jar [ k ] !== null && jar [ k ] !== "" ) arr . push ( k + "=" + jar [ k ] )
return arr . join ( "; " )
}
function b64decode ( s ) {
s = String ( s || "" )
try { if ( typeof Buffer !== "undefined" ) return Buffer . from ( s , "base64" ) . toString ( "utf8" ) } catch ( e ) { }
try { return decodeURIComponent ( escape ( atob ( s ) ) ) } catch ( e ) { return "" }
}
function uidFromQDHeader ( qdheader ) { var decoded = b64decode ( qdheader ) ; return decoded ? String ( decoded . split ( "|" ) [ 0 ] || "" ) . trim ( ) : "" }
function extractUid ( cookie ) {
var jar = parseCookieString ( cookie )
var uid = uidFromQDHeader ( jar . QDHeader || jar . qdheader || jar . QDHEADER || "" )
if ( uid ) return uid
if ( jar . uid ) return String ( jar . uid )
return ""
}
function maskCookie ( cookie ) { cookie = String ( cookie || "" ) ; return cookie . length <= 24 ? cookie : cookie . slice ( 0 , 12 ) + "..." + cookie . slice ( - 8 ) }
function contentText ( ) {
var s = ""
try { if ( typeof getContent === "function" ) s = getContent ( ) || "" } catch ( e ) { }
try { if ( ! s && typeof sender !== "undefined" && sender . getContent ) s = sender . getContent ( ) || "" } catch ( e ) { }
try { if ( ! s && typeof message !== "undefined" ) s = String ( message ) } catch ( e ) { }
return String ( s || "" ) . trim ( )
}
function usage ( ) {
return [
"起点读书签到插件" ,
"提交CK:起点ck <QX抓到的cookie>" ,
"签到全部:起点签到" ,
"签到单号:起点签到 <uid>" ,
"查询CK:起点ck 查询" ,
"导出CK:起点ck 导出" ,
"删除CK:起点ck 删除 <uid>" ,
"说明:QX只负责抓cookie;签到由本 Autman 插件执行。"
] . join ( "\n" )
}
function stripCommand ( text ) {
return String ( text || "" )
. replace ( /^起点(?:ck|CK|cookie|Cookie|签到|读书)\s*/i , "" )
. replace ( /^qdreader\s*/i , "" )
. trim ( )
}
function commandName ( text ) {
if ( /^起点(?:签到|读书)(?:\s|$)/i . test ( text ) ) return "sign"
if ( /^qdreader\s+sign(?:\s|$)/i . test ( text ) ) return "sign"
return "ck"
}
function qs ( obj ) {
var parts = [ ]
for ( var k in ( obj || { } ) ) {
var v = obj [ k ]
if ( v === undefined || v === null ) continue
if ( Array . isArray ( v ) ) { for ( var i = 0 ; i < v . length ; i ++ ) parts . push ( encodeURIComponent ( k + "[" + i + "]" ) + "=" + encodeURIComponent ( String ( v [ i ] ) ) ) }
else if ( typeof v === "object" ) { for ( var sk in v ) parts . push ( encodeURIComponent ( k + "[" + sk + "]" ) + "=" + encodeURIComponent ( String ( v [ sk ] ) ) ) }
else parts . push ( encodeURIComponent ( k ) + "=" + encodeURIComponent ( String ( v ) ) )
}
return parts . join ( "&" )
}
function normalizeCookie ( cookie ) {
var jar = parseCookieString ( cookie )
var session = jar . sessionyw || jar . session || jar . sessionKey || ""
if ( session ) jar . sessionyw = session
var appid = jar . appId || jar . appid || ""
if ( appid ) jar . appId = appid
return { jar : jar , cookie : cookieToString ( jar ) , qdh : jar . QDHeader || jar . qdh || "" , qdheader : jar . QDHeader || jar . qdheader || "" , uid : extractUid ( cookie ) , guid : jar . gwguid || jar . guid || "" , qimei : jar . qimei || "" , qimei36 : jar . qimei36 || jar . QIMEI36 || "" }
}
function httpReq ( opt ) {
if ( typeof request === "function" ) return request ( opt )
if ( typeof $request === "function" ) return $request ( opt )
throw new Error ( "当前 Autman 环境没有 request/$request 方法,无法发起签到请求" )
}
function parseResp ( res ) {
var body = res && ( res . body !== undefined ? res . body : ( res . data !== undefined ? res . data : res ) )
if ( typeof body === "string" ) return safeJsonParse ( body , body )
return body
}
function postJson ( url , data ) {
return parseResp ( httpReq ( { url : url , method : "POST" , headers : { "Content-Type" : "application/json" } , body : JSON . stringify ( data ) } ) )
}
function qidianRequest ( path , method , data , cookie ) {
method = String ( method || "GET" ) . toUpperCase ( )
var info = normalizeCookie ( cookie )
var signApi = ( get ( SIGN _API _KEY ) || DEFAULT _SIGN _API ) . replace ( /\/$/ , "" )
var payload = { qdh : info . qdh , qdheader : info . qdheader , url : QIDIAN _BASE + path , method : method , data : data || { } , uid : info . uid , guid : info . guid , qimei : info . qimei , qimei36 : info . qimei36 }
var signed = postJson ( signApi + "/sign" , payload )
if ( typeof signed === "string" ) signed = safeJsonParse ( signed , { } )
if ( ! signed || ( signed . code !== 200 && signed . code !== "200" ) || ! signed . data ) throw new Error ( "签名失败:" + JSON . stringify ( signed ) )
var headers = signed . data
headers . Cookie = info . cookie
if ( ! headers [ "User-Agent" ] ) headers [ "User-Agent" ] = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/QDReaderiOS/5.9.456/728/QDReaderAppstore"
var req = { url : QIDIAN _BASE + path , method : method , headers : headers }
if ( method === "GET" ) {
var query = qs ( data || { } )
if ( query ) req . url += ( req . url . indexOf ( "?" ) >= 0 ? "&" : "?" ) + query
} else {
req . headers [ "Content-Type" ] = req . headers [ "Content-Type" ] || "application/x-www-form-urlencoded"
req . body = qs ( data || { } )
}
var out = parseResp ( httpReq ( req ) )
if ( typeof out === "string" ) out = safeJsonParse ( out , out )
return out
}
function resultOk ( obj ) {
var code = obj && ( obj . Result !== undefined ? obj . Result : obj . Code !== undefined ? obj . Code : obj . code )
return code === undefined || code === 0 || code === "0" || code === 200 || code === "200"
}
function resultMsg ( obj ) { return ( obj && ( obj . Message || obj . Msg || obj . message || obj . msg ) ) || "" }
function doCheckin ( uid , cookie ) {
var res = qidianRequest ( "/argus/api/v3/checkin/checkin" , "POST" , { } , cookie )
var ok = resultOk ( res )
var msg = resultMsg ( res ) || ( ok ? "签到请求成功" : "签到失败" )
return { uid : uid , ok : ok , msg : msg , raw : res }
}
function signAll ( targetUid ) {
var store = getStore ( )
var uids = Object . keys ( store )
if ( targetUid ) uids = uids . filter ( function ( u ) { return u === targetUid } )
if ( ! uids . length ) return targetUid ? ( "未找到 uid: " + targetUid ) : "还没有保存起点读书CK"
var lines = [ "起点读书签到结果:" ]
for ( var i = 0 ; i < uids . length ; i ++ ) {
var uid = uids [ i ]
try {
var r = doCheckin ( uid , store [ uid ] )
lines . push ( ( r . ok ? "✅ " : "❌ " ) + uid + ": " + r . msg )
} catch ( e ) {
lines . push ( "❌ " + uid + ": " + ( e && e . message ? e . message : String ( e ) ) )
}
}
return lines . join ( "\n" )
}
var text = contentText ( )
var cmd = commandName ( text )
var arg = stripCommand ( text ) . replace ( /^sign\s*/i , "" ) . trim ( )
if ( ! arg && cmd !== "sign" ) { reply ( usage ( ) ) ; return }
if ( /^(帮助|help|h)$/i . test ( arg ) ) { reply ( usage ( ) ) ; return }
var store = getStore ( )
if ( cmd === "sign" || /^(签到|sign|run)(?:\s+|$)/i . test ( arg ) ) {
var target = arg . replace ( /^(签到|sign|run)\s*/i , "" ) . trim ( )
reply ( signAll ( target ) )
return
}
if ( /^(查询|状态|status|list)$/i . test ( arg ) ) {
var uids = Object . keys ( store )
reply ( uids . length ? ( "起点读书CK:共 " + uids . length + " 个账号\n" + uids . join ( "\n" ) + "\n发送 起点签到 可执行签到" ) : "还没有保存起点读书CK" )
return
}
if ( /^(导出|export)$/i . test ( arg ) ) { reply ( "QDREADER_COOKIE=" + JSON . stringify ( store ) ) ; return }
var del = arg . match ( /^(删除|delete|del|rm)\s+(\S+)/i )
if ( del ) { var duid = del [ 2 ] ; var existed = ! ! store [ duid ] ; delete store [ duid ] ; setStore ( store ) ; reply ( existed ? ( "已删除 uid: " + duid ) : ( "未找到 uid: " + duid ) ) ; return }
var jsonMatch = arg . match ( /^json\s+([\s\S]+)$/i )
if ( jsonMatch ) {
var obj = safeJsonParse ( jsonMatch [ 1 ] , null )
if ( ! obj || typeof obj !== "object" || Array . isArray ( obj ) ) { reply ( "JSON格式错误,示例:起点ck json {\"uid\":\"cookie\"}" ) ; return }
var count = 0
for ( var uid in obj ) { if ( ! obj [ uid ] ) continue ; store [ uid ] = String ( obj [ uid ] ) ; count ++ }
setStore ( store ) ; reply ( "起点读书CK JSON已导入/更新 " + count + " 个账号,当前共 " + Object . keys ( store ) . length + " 个账号\n发送 起点签到 可执行签到" ) ; return
}
var cookie = arg
if ( cookie . indexOf ( "=" ) < 0 || cookie . indexOf ( ";" ) < 0 ) { reply ( "未识别到 Cookie。请发送:起点ck <QX抓到的完整cookie>" ) ; return }
var uid = extractUid ( cookie )
if ( ! uid ) { reply ( "无法从 Cookie 识别 uid。请确认 QX 抓的是起点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。" ) ; return }
var isNew = ! store [ uid ]
var changed = store [ uid ] !== cookie
store [ uid ] = cookie
setStore ( store )
reply ( [ "起点读书CK" + ( isNew ? "新增" : ( changed ? "更新" : "未变化" ) ) + "成功" , "uid: " + uid , "账号数: " + Object . keys ( store ) . length , "cookie: " + maskCookie ( cookie ) , "签到:发送 起点签到" ] . join ( "\n" ) )