LiteBlog - API文档

公开API

public_api_add_comment

  • 用于为一篇文章添加评论

请求路径: /api/v1/add_comment

请求方式: POST

传入参数 (JSON):

  • verify_token string: 通过验证后得到的token,由Cloudflare turnsitegoogle reCaptcha 生成.
  • article_id string: 标识文章唯一ID,用于添加评论的文章id.
  • content string: 评论内容,在contentAdvisor_config为ture且filter_comment为true时仅支持普通文本,在filter_commentcontentAdvisor_config为false时支持HTML DOM元素注入(会导致XSS攻击).
  • author string: 作者.
  • email string: 邮箱,仅用于记录,不会被渲染进comment模板中.
  • reply_to string: 回复的评论ID,为空则不设置为回复.
  • subscribed bool: 作者是否订阅评论区,如果为trueConfig:notify_config:trigger中启用了subscribed_comment_reply,那么在收到新回复时就会向作者email发送通知回复邮件.

传出:

  • header:200|500|403|404|405|429
  • body: 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|500
  • body json: 捕获的信息,示例:
{
    "count": 100, // 被正确调用了100次
    "all_requests": 1000 // 服务器所有处理的请求数为1000
}

后端API

在和后端进行通信时,需要获取三个参数

  • backend_path: 后端通信url,即此后用于和后端通信的path地址
  • encryptKey: 加密密钥
  • access_token: 通讯密钥或登录后获取的token

其中 backend_urltoken由用户输入,enncryptKey由liteblog根据backend_pathaccess_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):

  • token string: 加密后的token
  • changes []JSON{
    • cardID string: 需要修改的卡片ID
    • order int : 修改后的order
  • }
  • 注: changes为数组对象类型

传出 :

  • header: 200|400|403|405|500
  • body: OK

delete_card

  • 此接口用于删除主页卡片

请求路径:/{backendPath}/delete_card

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • cardID string: 需要删除的卡片ID

传出 :

  • header: 200|400|403|405|500
  • body: OK

add_card

  • 此接口用于添加主页卡片

请求路径:/{backendPath}/add_card

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • card map[string]string: 卡片信息JSON

传出 :

  • header: 200|400|403|405|500
  • body: OK

get_card

  • 此接口用于获取主页卡片信息

请求路径:/{backendPath}/get_card

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • cardID string: 需要获取的卡片ID

传出 :

  • header: 200|400|403|405|500
  • body map[string]string: 卡片信息的JSON格式代码

get_all_cards

  • 此接口用于获取所有主页卡片信息

请求路径:/{backendPath}/get_all_cards

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token

传出 :

  • header: 200|400|403|405|500
  • body []card: 卡片信息的JSON数组格式代码 例:[{"id": "1","order": "1","tags": "go tag1 tag2","template": "card_template_classical"}]

edit_card

  • 此接口用于编辑卡片信息

请求路径:/{backendPath}/edit_card

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • card map[string]string: 卡片信息的JSON代码
    注: card字段的map中必须含有id

传出 :

  • header: 200|400|403|405|500
  • body: OK

article类

add_article

  • 此接口用于添加一篇文章

请求路径:/{backendPath}/add_article

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • article JSON {
    • title string: 文章标题
    • content string: 文章内容的markdown格式
    • content_html string: 文章内容的HTML渲染后格式
    • extra_flags map[string][string]: 额外文章参数,会被渲染进文章
    • author string: 作者
      }

传出 :

  • header: 200|400|403|405|500
  • body JSON {
    • article_id: 添加完成后返回生成的文章ID
      }

edit_article

  • 此接口用于编辑一篇文章

请求路径:/{backendPath}/edit_article

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • article 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|500
  • body: OK

get_article

  • 此接口用于获取一篇文章的信息

请求路径:/{backendPath}/get_article

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • article_id string: 需要获取的文章id

传出 :

  • header: 200|400|403|405|500
  • body 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|500
  • body []string: 所有文章ID的数组 例: ["articleID1","articleID2"]

delete_comment

  • 此接口用于删除一条评论

请求路径:/{backendPath}/delete_comment

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • article_id string: 需要删除的文章id
  • comment_id string: 需要删除的评论id

传出 :

  • header: 200|400|403|405|500
  • body: OK

delete_article

  • 此接口用于删除一篇文章

请求路径:/{backendPath}/delete_article

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • article_id string: 需要删除的文章id

传出 :

  • header: 200|400|403|405|500
  • body: OK

设置类

get_custom_settings

  • 此接口用于获取个性化设置字段

请求路径:/{backendPath}/get_custom_settings

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token

传出 :

  • header: 200|400|403|405|500
  • body JSON {
    • custom_script string: 当前的自定义脚本
    • custom_style string: 当前的自定义样式
    • global_settings map[string]string: 全局设置项
      }

edit_custom_settings

  • 此接口用于填写个性化设置字段

请求路径:/{backendPath}/edit_custom_settings

请求方式: POST

传入参数 (JSON):

  • token string: 加密后的token
  • custom_settings JSON{
    • global_settings map[string]string: 更新的全局设置
    • custom_script string: 更新后的自定义脚本
    • custom_settings string: 更新后的自定义设置
      }

传出 :

  • header: 200|400|403|405|500
  • body: OK

其他

login

  • 此接口用于登录并获取临时token

请求路径: /{backendPath}/login

请求方式: POST

传入参数(JSON):

  • access_token string: 加密后的token

传出:

  • header: 200|400|403|405|500
  • body JSON: 返回的登录信息,例:
{
    "token": "123456",
    "timeout": 1754671631
}

tips

  1. 如果遇到明明access path和access key没问题但是依然无法访问api的情况,请校准服务器和本地时间戳,确保时间窗口<=20s
Powered by  LiteBlog
|