公开API
public_api_add_comment
- 用于为一篇文章添加评论
请求路径: /api/v1/add_comment
请求方式: POST
传入参数 (JSON):
verify_tokenstring: 通过验证后得到的token,由Cloudflare turnsite或google reCaptcha生成.article_idstring: 标识文章唯一ID,用于添加评论的文章id.contentstring: 评论内容,在contentAdvisor_config为ture且filter_comment为true时仅支持普通文本,在filter_comment或contentAdvisor_config为false时支持HTML DOM元素注入(会导致XSS攻击).authorstring: 作者.emailstring: 邮箱,仅用于记录,不会被渲染进comment模板中.reply_tostring: 回复的评论ID,为空则不设置为回复.subscribedbool: 作者是否订阅评论区,如果为true且Config:notify_config:trigger中启用了subscribed_comment_reply,那么在收到新回复时就会向作者email发送通知回复邮件.
传出:
header:200|500|403|404|405|429body:OK
示例:
> POST /api/v1/add_comment HTTP/1.1
>
> {
> "verify_token":"token",
> "article_id":"13cc99f4328b70cff5c7c1adea30c89f",
> "author":"Unicode",
> "email":"unicode@un1c0de.com",
> "content":"unicodeAddCommentTest"
> }
>
< HTTP/1.1 200 OK
<
< OK
public_api_get_sniffer_info
- 用于获取服务端捕获到的信息 请求路径:
/api/v1+Config.SnifferCfg.PublicProvider; 默认为/api/v1/sniffer
请求方法: POST
请求参数:
path: 需要获取的路径信息,如/index.html
请求示例:
curl https://example.com/api/v1/sniffer?path=/index.html
空请求体
响应:
header: 200|403|405|500bodyjson: 捕获的信息,示例:{ "count": 100, // 被正确调用了100次 "all_requests": 1000 // 服务器所有处理的请求数为1000 }
后端API
在和后端进行通信时,需要获取三个参数
backend_path: 后端通信url,即此后用于和后端通信的path地址encryptKey: 加密密钥access_token: 通讯密钥或登录后获取的token
其中 backend_url和token由用户输入,enncryptKey由liteblog根据backend_path和access_token生成,并加入到renderedmap中,可以在渲染的文件中用{{rendered:token_encrypt_key}}获取
后续所有API中的Token 均为加密后的token
token不可逆生成函数 (2025.6.11更新):
传入用户token,生成和后端通信token
js version
function generateEncryptToken(token) { var encryptKey = `{{rendered:token_encrypt_key}}`; const timestamp = parseInt((new Date().getTime())/10000); // 时间梯度10s // console.log(timestamp); const timestampB64 = btoa(timestamp.toString()); // console.log(timestampB64); encryptKey = encryptKey + timestampB64; let tokenArray = Array.from(btoa(token + "|" + encryptKey)); let XorshiftSeed = 2166136261 >>> 0; for (let i = 0; i < tokenArray.length; i++) { XorshiftSeed = Math.imul(XorshiftSeed, 16777619); XorshiftSeed = (XorshiftSeed ^ tokenArray[i].charCodeAt(0)) >>> 0; } // console.log("XorshiftSeed: " + XorshiftSeed); const xorshift = new Xorshift32(XorshiftSeed); const getRandomChar = (seed) => String.fromCharCode(33 + ((seed+xorshift.next()) % 94)); for (let i = 0; i < encryptKey.length; i++) { const charCode = encryptKey.charCodeAt(i); const operation = charCode % 5; switch (operation) { case 0: tokenArray.unshift(getRandomChar(charCode + i)); break; case 1: if (tokenArray.length > 0) { const pos = (charCode * i) % tokenArray.length; tokenArray[pos] = getRandomChar(charCode ^ tokenArray[pos].charCodeAt(0)); } break; case 2: mod = xorshift.next() % (tokenArray.length+1); if (mod == 0) { mod = 1; } insertPos = charCode % mod // console.log("insertPos: " + insertPos); tokenArray.splice(insertPos, 0, getRandomChar(charCode), getRandomChar(charCode + 997) ); break; case 3: if (tokenArray.length > 1) { const pos1 = charCode % tokenArray.length; const pos2 = tokenArray.length - 1 - pos1; [tokenArray[pos1], tokenArray[pos2]] = [tokenArray[pos2], tokenArray[pos1]]; } break; default: const pseudo = ['==', '=', '=A', 'B='][charCode % 4]; tokenArray.push(...Array.from(pseudo)); } } const finalShuffle = []; while (tokenArray.length > 0) { const randIndex = xorshift.next() % tokenArray.length; finalShuffle.push(tokenArray.splice(randIndex, 1)[0]); } return finalShuffle.join(''); } class Xorshift32 { constructor(seed) { if (seed === 0) throw new Error("Seed cannot be zero"); this.state = seed >>> 0; } next() { let x = this.state; x ^= x << 13; x ^= x >>> 17; x ^= x << 5; this.state = x >>> 0; return this.state; } random() { return this.next() / 0x100000000; } }go version
注意: 以下的golang示例是后端示例,用于生成用于验证的token,所以需要传入三个参数,用于生成当前时间±20s的token(5个),进行对比.
func generateEncryptToken(token, encryptKey string, timestampBase64 string) string {
encryptKey = encryptKey + timestampBase64
data := []byte(token + "|" + encryptKey)
encoded := base64.StdEncoding.EncodeToString(data)
// fmt.Printf("encoded: %s\n", encoded)
tokenArray := []byte(encoded)
xorshiftSeed := uint32(2166136261) // FNV偏移基础值
for _, b := range tokenArray {
xorshiftSeed = (xorshiftSeed * 16777619) ^ uint32(b)
}
xorshift, _ := NewXorshift32(xorshiftSeed)
// fmt.Printf("xorshiftSeed: %d\n", xorshiftSeed)
getRandomChar := func(seed int) byte {
return byte(33 + ((seed + int(xorshift.Next())) % 94))
}
for i := 0; i < len(encryptKey); i++ {
charCode := int(encryptKey[i])
operation := charCode % 5
switch operation {
case 0:
tokenArray = append([]byte{getRandomChar(charCode + i)}, tokenArray...)
case 1:
if len(tokenArray) > 0 {
pos := (charCode * i) % len(tokenArray)
tokenArray[pos] = getRandomChar(charCode ^ int(tokenArray[pos]))
}
case 2:
mod := xorshift.Next() % uint32(len(tokenArray)+1)
if mod == 0 {
mod = 1
}
insertPos := uint32(charCode) % mod
// fmt.Printf("insertPos: %d\n", insertPos)
char1 := getRandomChar(charCode)
char2 := getRandomChar(charCode + 997)
tokenArray = append(tokenArray[:insertPos], append([]byte{char1, char2}, tokenArray[insertPos:]...)...)
case 3:
if len(tokenArray) > 1 {
pos1 := charCode % len(tokenArray)
pos2 := len(tokenArray) - 1 - pos1
tokenArray[pos1], tokenArray[pos2] = tokenArray[pos2], tokenArray[pos1]
}
default:
pseudo := [...]string{"==", "=", "=A", "B="}[charCode%4]
tokenArray = append(tokenArray, []byte(pseudo)...)
}
}
finalShuffle := make([]byte, 0, len(tokenArray))
for len(tokenArray) > 0 {
randIndex := int(xorshift.Next()) % len(tokenArray)
finalShuffle = append(finalShuffle, tokenArray[randIndex])
tokenArray = append(tokenArray[:randIndex], tokenArray[randIndex+1:]...)
}
return string(finalShuffle)
}
type Xorshift32 struct {
state uint32
}
func NewXorshift32(seed uint32) (*Xorshift32, error) {
if seed == 0 {
return nil, fmt.Errorf("seed cannot be zero")
}
return &Xorshift32{state: seed}, nil
}
func (x *Xorshift32) Next() uint32 {
x.state ^= x.state << 13
x.state ^= x.state >> 17
x.state ^= x.state << 5
return x.state
}
func (x *Xorshift32) Random() float64 {
return float64(x.Next()) / float64(1<<32)
}
card类
edit_order
- 此接口用于编辑主页卡片
order(顺序)字段
请求路径:/{backendPath}/edit_order
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokenchanges[]JSON{cardIDstring: 需要修改的卡片ID
orderint: 修改后的order
- }
- 注:
changes为数组对象类型
传出 :
header: 200|400|403|405|500body:OK
delete_card
- 此接口用于删除主页卡片
请求路径:/{backendPath}/delete_card
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokencardIDstring: 需要删除的卡片ID
传出 :
header: 200|400|403|405|500body:OK
add_card
- 此接口用于添加主页卡片
请求路径:/{backendPath}/add_card
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokencardmap[string]string: 卡片信息JSON
传出 :
header: 200|400|403|405|500body:OK
get_card
- 此接口用于获取主页卡片信息
请求路径:/{backendPath}/get_card
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokencardIDstring: 需要获取的卡片ID
传出 :
header: 200|400|403|405|500bodymap[string]string: 卡片信息的JSON格式代码
get_all_cards
- 此接口用于获取所有主页卡片信息
请求路径:/{backendPath}/get_all_cards
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的token
传出 :
header: 200|400|403|405|500body[]card: 卡片信息的JSON数组格式代码 例:[{"id": "1","order": "1","tags": "go tag1 tag2","template": "card_template_classical"}]
edit_card
- 此接口用于编辑卡片信息
请求路径:/{backendPath}/edit_card
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokencardmap[string]string: 卡片信息的JSON代码
注:card字段的map中必须含有id
传出 :
header: 200|400|403|405|500body:OK
article类
add_article
- 此接口用于添加一篇文章
请求路径:/{backendPath}/add_article
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokenarticleJSON{titlestring: 文章标题
contentstring: 文章内容的markdown格式
content_htmlstring: 文章内容的HTML渲染后格式
extra_flagsmap[string][string]: 额外文章参数,会被渲染进文章
authorstring: 作者
}
传出 :
header: 200|400|403|405|500bodyJSON{article_id: 添加完成后返回生成的文章ID
}
edit_article
- 此接口用于编辑一篇文章
请求路径:/{backendPath}/edit_article
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokenarticleJSON{article_idstring: 文章ID
titlestring: 文章标题
contentstring: 文章内容的markdown格式
content_htmlstring: 文章内容的HTML渲染后格式
extra_flagsmap[string][string]: 额外文章参数,会被渲染进文章
authorstring: 作者
}
传出 :
header: 200|400|403|405|500body:OK
get_article
- 此接口用于获取一篇文章的信息
请求路径:/{backendPath}/get_article
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokenarticle_idstring: 需要获取的文章id
传出 :
header: 200|400|403|405|500bodyJSON{titlestring: 文章标题
contentstring: 文章内容的markdown格式
content_htmlstring: 文章内容的HTML渲染后格式
edit_datestring: 文章的编辑日期
pub_datestring: 文章的发布日期
authorstring: 作者
extra_flagsmap[string][string]: 额外文章参数,会被渲染进文章
comment[] 以后再填坑
}
get_all_article_id
- 此接口用于获取所有文章id
请求路径:/{backendPath}/get_all_article_id
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的token
传出 :
header: 200|400|403|405|500body[]string: 所有文章ID的数组 例:["articleID1","articleID2"]
delete_comment
- 此接口用于删除一条评论
请求路径:/{backendPath}/delete_comment
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokenarticle_idstring: 需要删除的文章idcomment_idstring: 需要删除的评论id
传出 :
header: 200|400|403|405|500body:OK
delete_article
- 此接口用于删除一篇文章
请求路径:/{backendPath}/delete_article
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokenarticle_idstring: 需要删除的文章id
传出 :
header: 200|400|403|405|500body:OK
设置类
get_custom_settings
- 此接口用于获取个性化设置字段
请求路径:/{backendPath}/get_custom_settings
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的token
传出 :
header: 200|400|403|405|500bodyJSON{custom_scriptstring: 当前的自定义脚本
custom_stylestring: 当前的自定义样式
global_settingsmap[string]string: 全局设置项
}
edit_custom_settings
- 此接口用于填写个性化设置字段
请求路径:/{backendPath}/edit_custom_settings
请求方式: POST
传入参数 (JSON):
tokenstring: 加密后的tokencustom_settingsJSON{global_settingsmap[string]string: 更新的全局设置
custom_scriptstring: 更新后的自定义脚本
custom_settingsstring: 更新后的自定义设置
}
传出 :
header: 200|400|403|405|500body:OK
其他
login
- 此接口用于登录并获取临时token
请求路径: /{backendPath}/login
请求方式: POST
传入参数(JSON):
access_tokenstring: 加密后的token
传出:
header: 200|400|403|405|500bodyJSON: 返回的登录信息,例:{ "token": "123456", "timeout": 1754671631 }
upload_file
- 此接口用于上传本地文件至服务器
请求路径: /{backendPath}/upload_file
请求方式: POST
传入参数(FORM):
expiry_daysstring: 过期时间,单位:天, never为永不过期filebyte[]: 二进制文件tokenstring: 验证token
传出:
header: 200|400|403|405|500bodyJSON: {filenamestring: 哈希后的文件名original_namestring: 源文件名sizeint: 文件大小(B)urlstring: 相对url, 如/upload/abcdefg.jpghashstring: 哈希值expiry_timeint: 过期时间
}
tips
- 如果遇到明明access path和access key没问题但是依然无法访问api的情况,请校准服务器和本地时间戳,确保时间窗口<=20s
