Koishi从0入门 03 编写签到插件

本文最后更新于 2025年6月14日 凌晨

前言

在上一篇教程中,我们学习了如何调用api发送消息,以及发送不同的消息类型,那我们今天来实战写一个签到插件

签到插件的原理就是写入今天的时间,如果判断上一次签到时间和今天是一致的,那就签到失败,否则就是签到成功

指令匹配

Koishi这个框架中,我们除了可以用ctx.on去判断消息外,我们可以直接使用最直接的ctx.command去判断消息

示例代码如下

1
2
3
4
ctx.command('签到')
.action(async({session}) => {
await session.send("1")
})


数据库基础使用

这里是点我具体的文档,那么我们来写一个基础的,首先是声明数据类型

1
2
3
4
export interface sign{
time : string // 这里是存储时间
user_id : string // 这里是用户id
}

第二步声明使用了数据库插件,否则会出现黄色警告

1
export const inject = ["database"]

Koishi里面声明数据库

1
2
3
4
5
declare module 'koishi' {
interface Tables {
sign : sign
}
}

最后是创建数据库

1
2
3
4
ctx.model.extend('sign',{
time: 'string',
user_id: 'string'
})

数据库创建代码为

1
await ctx.database.create(table_name, {.....})

数据库更新代码为

1
await ctx.database.upsert(table_name, [{....}])

数据库搜索代码为

1
await ctx.database.get(table_name, {....})

获取年月日

这里我们可以使用js的库获取

1
2
3
4
5
6
7
function getCurrentDate(): string {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}

开始编写

在我们前面了解完这些知识后,我们先创建一个存放签到数据的数据库

根据上面的代码讲解,我们可以这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { Context, Schema, h } from 'koishi'

export const name = 'test' // 插件名字

export interface Config {} // 插件的配置项

declare module 'koishi' {
interface Tables {
sign_today : sign_today
}
}

export const inject = ["database"]

export const Config: Schema<Config> = Schema.object({})

export interface sign_today {
id?: number // 添加 id 字段(可选)
now_time: string
user_id: string
}

export function apply(ctx: Context) {
ctx.model.extend("sign_today", {
id: 'unsigned',
now_time: 'string',
user_id: 'string'
}, {
primary: 'id',
autoInc: true
})

ctx.command('签到')
.action(async({session}) => {
await session.send("1")
})
}

我们运行一下代码,如果日志出现了

1
2
2025-06-14 00:57:40 [I] hmr reload plugin at external\test\src\index.ts
2025-06-14 00:57:40 [I] sqlite auto creating table sign_today

说明你的数据库已经初始化了,现在我们缺少读写数据库


读写内容

对数据库进行搜索,然后数据库会返回数组

1
2
3
4
5
6
7
8
9
ctx.command('签到')
.action(async({session}) => {
var get_user_sign_time = await ctx.database.get("sign_today", {user_id: session.userId})
if(get_user_sign_time.length == 0){

}else{

}
})

当返回长度为0的数组,说明这个人不存在,我们要创建数据,然后返回签到成功

1
2
3
4
5
6
7
8
9
10
11
12
13
ctx.command('签到')
.action(async({session}) => {
var get_user_sign_time = await ctx.database.get("sign_today", {user_id: session.userId})
if(get_user_sign_time.length == 0){
await ctx.database.create("sign_today", {
"now_time": getCurrentDate(),
"user_id": session.userId
})
await session.send("签到成功")
}else{

}
})

那么我们就要讨论另外一种情况了,如果这个人在数据库有记录的话,我们需要对比时间,如果时间恰好等于今天的时间,说明他已经签到了,否则就是失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ctx.command('签到')
.action(async({session}) => {
var get_user_sign_time = await ctx.database.get("sign_today", {user_id: session.userId})
if(get_user_sign_time.length == 0){
await ctx.database.create("sign_today", {
"now_time": getCurrentDate(),
"user_id": session.userId
})
await session.send("签到成功")
}else{
if(get_user_sign_time[0]['now_time'] == getCurrentDate()){
await session.send("签到失败,你今天已经签到了")
}else{
await ctx.database.upsert("sign_today", [{
"now_time" : getCurrentDate(),
"user_id" : session.userId,
}])
await session.send("签到成功")
}
}
})

好了,这样就大功告成了

效果如上图


代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { Context, Schema, h } from 'koishi'

export const name = 'test' // 插件名字

export interface Config {} // 插件的配置项

declare module 'koishi' {
interface Tables {
sign_today : sign_today
}
}

export const inject = ["database"]

export const Config: Schema<Config> = Schema.object({})

export interface sign_today {
id?: number // 添加 id 字段(可选)
now_time: string
user_id: string
}

export function apply(ctx: Context) {
ctx.model.extend("sign_today", {
id: 'unsigned', // 添加 id 字段定义
now_time: 'string',
user_id: 'string' // 改为 string 类型以匹配接口定义
}, {
primary: 'id', // 明确指定主键
autoInc: true // 自增主键
})

ctx.command('签到')
.action(async({session}) => {
var get_user_sign_time = await ctx.database.get("sign_today", {user_id: session.userId})
if(get_user_sign_time.length == 0){
await ctx.database.create("sign_today", {
"now_time": getCurrentDate(),
"user_id": session.userId
})
await session.send("签到成功")
}else{
if(get_user_sign_time[0]['now_time'] == getCurrentDate()){
await session.send("签到失败,你今天已经签到了")
}else{
await ctx.database.upsert("sign_today", [{
"now_time" : getCurrentDate(),
"user_id" : session.userId,
}])
await session.send("签到成功")
}
}
})
}

function getCurrentDate(): string {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}

后记

学习了如何通过数据库读写实现签到功能后,下期我们来写一个定时任务计划


Koishi从0入门 03 编写签到插件
http://blog.bingyue.top/2025/06/14/koishi_teach_03/
作者
bingyue
发布于
2025年6月14日
许可协议