公开API
public_api_add_comment
- 用于为一篇文章添加评论
请求路径: /api/v1/add_comment
请求方式: POST
传入参数 (JSON
):
verify_token
string
: 通过验证后得到的token,由Cloudflare turnsite
或google reCaptcha
生成.article_id
string
: 标识文章唯一ID,用于添加评论的文章id.content
string
: 评论内容,在contentAdvisor_config
为ture且filter_comment
为true时仅支持普通文本,在filter_comment
或contentAdvisor_config
为false时支持HTML DOM元素注入(会导致XSS攻击).author
string
: 作者.email
string
: 邮箱,仅用于记录,不会被渲染进comment模板中.reply_to
string
: 回复的评论ID,为空则不设置为回复.subscribed
bool
: 作者是否订阅评论区,如果为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|500body
json
: 捕获的信息,示例:
{
"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
生成,并加入到rendered
map中,可以在渲染的文件中用{{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
):
token
string
: 加密后的tokenchanges
[]JSON
{cardID
string
: 需要修改的卡片ID
order
int
: 修改后的order
- }
- 注:
changes
为数组对象类型
传出 :
header
: 200|400|403|405|500body
:OK
delete_card
- 此接口用于删除主页卡片
请求路径:/{backendPath}/delete_card
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokencardID
string
: 需要删除的卡片ID
传出 :
header
: 200|400|403|405|500body
:OK
add_card
- 此接口用于添加主页卡片
请求路径:/{backendPath}/add_card
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokencard
map[string]string
: 卡片信息JSON
传出 :
header
: 200|400|403|405|500body
:OK
get_card
- 此接口用于获取主页卡片信息
请求路径:/{backendPath}/get_card
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokencardID
string
: 需要获取的卡片ID
传出 :
header
: 200|400|403|405|500body
map[string]string
: 卡片信息的JSON格式代码
get_all_cards
- 此接口用于获取所有主页卡片信息
请求路径:/{backendPath}/get_all_cards
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的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
):
token
string
: 加密后的tokencard
map[string]string
: 卡片信息的JSON代码
注:card
字段的map中必须含有id
传出 :
header
: 200|400|403|405|500body
:OK
article类
add_article
- 此接口用于添加一篇文章
请求路径:/{backendPath}/add_article
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokenarticle
JSON
{title
string
: 文章标题
content
string
: 文章内容的markdown格式
content_html
string
: 文章内容的HTML渲染后格式
extra_flags
map[string][string]
: 额外文章参数,会被渲染进文章
author
string
: 作者
}
传出 :
header
: 200|400|403|405|500body
JSON
{article_id
: 添加完成后返回生成的文章ID
}
edit_article
- 此接口用于编辑一篇文章
请求路径:/{backendPath}/edit_article
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokenarticle
JSON
{article_id
string
: 文章ID
title
string
: 文章标题
content
string
: 文章内容的markdown格式
content_html
string
: 文章内容的HTML渲染后格式
extra_flags
map[string][string]
: 额外文章参数,会被渲染进文章
author
string
: 作者
}
传出 :
header
: 200|400|403|405|500body
:OK
get_article
- 此接口用于获取一篇文章的信息
请求路径:/{backendPath}/get_article
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokenarticle_id
string
: 需要获取的文章id
传出 :
header
: 200|400|403|405|500body
JSON
{title
string
: 文章标题
content
string
: 文章内容的markdown格式
content_html
string
: 文章内容的HTML渲染后格式
edit_date
string
: 文章的编辑日期
pub_date
string
: 文章的发布日期
author
string
: 作者
extra_flags
map[string][string]
: 额外文章参数,会被渲染进文章
comment
[] 以后再填坑
}
get_all_article_id
- 此接口用于获取所有文章id
请求路径:/{backendPath}/get_all_article_id
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的token
传出 :
header
: 200|400|403|405|500body
[]string
: 所有文章ID的数组 例:["articleID1","articleID2"]
delete_comment
- 此接口用于删除一条评论
请求路径:/{backendPath}/delete_comment
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokenarticle_id
string
: 需要删除的文章idcomment_id
string
: 需要删除的评论id
传出 :
header
: 200|400|403|405|500body
:OK
delete_article
- 此接口用于删除一篇文章
请求路径:/{backendPath}/delete_article
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokenarticle_id
string
: 需要删除的文章id
传出 :
header
: 200|400|403|405|500body
:OK
设置类
get_custom_settings
- 此接口用于获取个性化设置字段
请求路径:/{backendPath}/get_custom_settings
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的token
传出 :
header
: 200|400|403|405|500body
JSON
{custom_script
string
: 当前的自定义脚本
custom_style
string
: 当前的自定义样式
global_settings
map[string]string
: 全局设置项
}
edit_custom_settings
- 此接口用于填写个性化设置字段
请求路径:/{backendPath}/edit_custom_settings
请求方式: POST
传入参数 (JSON
):
token
string
: 加密后的tokencustom_settings
JSON
{global_settings
map[string]string
: 更新的全局设置
custom_script
string
: 更新后的自定义脚本
custom_settings
string
: 更新后的自定义设置
}
传出 :
header
: 200|400|403|405|500body
:OK
其他
login
- 此接口用于登录并获取临时token
请求路径: /{backendPath}/login
请求方式: POST
传入参数(JSON
):
access_token
string
: 加密后的token
传出:
header
: 200|400|403|405|500body
JSON
: 返回的登录信息,例:
{
"token": "123456",
"timeout": 1754671631
}
tips
- 如果遇到明明access path和access key没问题但是依然无法访问api的情况,请校准服务器和本地时间戳,确保时间窗口<=20s