mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-19 22:00:11 +08:00
448 lines
10 KiB
Go
448 lines
10 KiB
Go
// Package timer 群管定时器
|
||
package timer
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
"unicode"
|
||
|
||
"github.com/sirupsen/logrus"
|
||
zero "github.com/wdvxdr1123/ZeroBot"
|
||
"github.com/wdvxdr1123/ZeroBot/message"
|
||
|
||
"github.com/FloatTech/ZeroBot-Plugin/utils/file"
|
||
)
|
||
|
||
type Clock struct {
|
||
// 记录每个定时器以便取消
|
||
timersmap TimersMap
|
||
// 定时器map
|
||
timers *(map[string]*Timer)
|
||
// 读写锁
|
||
timersmu sync.RWMutex
|
||
// 定时器存储位置
|
||
pbfile *string
|
||
}
|
||
|
||
var (
|
||
// @全体成员
|
||
atall = message.MessageSegment{
|
||
Type: "at",
|
||
Data: map[string]string{
|
||
"qq": "all",
|
||
},
|
||
}
|
||
)
|
||
|
||
func NewClock(pbfile string) (c Clock) {
|
||
c.loadTimers(pbfile)
|
||
c.timers = &c.timersmap.Timers
|
||
c.pbfile = &pbfile
|
||
return
|
||
}
|
||
|
||
func firstWeek(date *time.Time, week time.Weekday) (d time.Time) {
|
||
d = date.AddDate(0, 0, 1-date.Day())
|
||
for d.Weekday() != week {
|
||
d = d.AddDate(0, 0, 1)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (ts *Timer) nextWakeTime() (date time.Time) {
|
||
date = time.Now()
|
||
m := ts.Month
|
||
d := ts.Day
|
||
h := ts.Hour
|
||
mn := ts.Minute
|
||
w := ts.Week
|
||
unit := time.Duration(int(ts.Minute)-date.Minute()) * time.Minute
|
||
logrus.Debugln("[timer] unit init:", unit)
|
||
if mn >= 0 {
|
||
switch {
|
||
case h < 0:
|
||
if unit <= time.Second {
|
||
unit += time.Hour
|
||
}
|
||
case d < 0 || w < 0:
|
||
if unit <= time.Second {
|
||
unit += time.Hour * 24
|
||
}
|
||
case d == 0 && w >= 0:
|
||
delta := time.Hour * 24 * time.Duration(int(w)-int(date.Weekday()))
|
||
if delta < 0 {
|
||
delta += time.Hour * 24 * 7
|
||
}
|
||
unit += delta
|
||
case m < 0:
|
||
unit = -1
|
||
}
|
||
} else {
|
||
unit = time.Minute
|
||
}
|
||
logrus.Debugln("[timer] unit:", unit)
|
||
stable := 0
|
||
if mn < 0 {
|
||
mn = int32(date.Minute())
|
||
}
|
||
if h < 0 {
|
||
h = int32(date.Hour())
|
||
} else {
|
||
stable |= 0x8
|
||
}
|
||
if d < 0 {
|
||
d = int32(date.Day())
|
||
} else if d > 0 {
|
||
stable |= 0x4
|
||
} else {
|
||
d = int32(date.Day())
|
||
if w >= 0 {
|
||
stable |= 0x2
|
||
}
|
||
}
|
||
if m < 0 {
|
||
m = int32(date.Month())
|
||
} else {
|
||
stable |= 0x1
|
||
}
|
||
switch stable {
|
||
case 0b0101:
|
||
if ts.Day != int32(time.Now().Day()) || ts.Month != int32(time.Now().Month()) {
|
||
h = 0
|
||
}
|
||
case 0b1001:
|
||
if ts.Month != int32(time.Now().Month()) {
|
||
d = 0
|
||
}
|
||
case 0b0001:
|
||
if ts.Month != int32(time.Now().Month()) {
|
||
d = 0
|
||
h = 0
|
||
}
|
||
}
|
||
logrus.Debugln("[timer] stable:", stable)
|
||
logrus.Debugln("[timer] m:", m, "d:", d, "h:", h, "mn:", mn, "w:", w)
|
||
date = time.Date(date.Year(), time.Month(m), int(d), int(h), int(mn), date.Second(), date.Nanosecond(), date.Location())
|
||
logrus.Debugln("[timer] date original:", date)
|
||
if unit > 0 {
|
||
date = date.Add(unit)
|
||
}
|
||
logrus.Debugln("[timer] date after add:", date)
|
||
if time.Until(date) <= 0 {
|
||
if ts.Month < 0 {
|
||
if ts.Day > 0 || (ts.Day == 0 && ts.Week >= 0) {
|
||
date = date.AddDate(0, 1, 0)
|
||
} else if ts.Day < 0 || ts.Week < 0 {
|
||
if ts.Hour > 0 {
|
||
date = date.AddDate(0, 0, 1)
|
||
} else if ts.Minute > 0 {
|
||
date = date.Add(time.Hour)
|
||
}
|
||
}
|
||
} else {
|
||
date = date.AddDate(1, 0, 0)
|
||
}
|
||
}
|
||
logrus.Debugln("[timer] date after fix:", date)
|
||
if stable&0x8 != 0 && date.Hour() != int(h) {
|
||
switch {
|
||
case stable&0x4 == 0:
|
||
date = date.AddDate(0, 0, 1).Add(-time.Hour)
|
||
case stable&0x2 == 0:
|
||
date = date.AddDate(0, 0, 7).Add(-time.Hour)
|
||
case stable*0x1 == 0:
|
||
date = date.AddDate(0, 1, 0).Add(-time.Hour)
|
||
default:
|
||
date = date.AddDate(1, 0, 0).Add(-time.Hour)
|
||
}
|
||
}
|
||
logrus.Debugln("[timer] date after s8:", date)
|
||
if stable&0x4 != 0 && date.Day() != int(d) {
|
||
switch {
|
||
case stable*0x1 == 0:
|
||
date = date.AddDate(0, 1, -1)
|
||
default:
|
||
date = date.AddDate(1, 0, -1)
|
||
}
|
||
}
|
||
logrus.Debugln("[timer] date after s4:", date)
|
||
if stable&0x2 != 0 && int32(date.Weekday()) != w {
|
||
switch {
|
||
case stable*0x1 == 0:
|
||
date = date.AddDate(0, 1, 0)
|
||
default:
|
||
date = date.AddDate(1, 0, 0)
|
||
}
|
||
date = firstWeek(&date, time.Weekday(w))
|
||
}
|
||
logrus.Debugln("[timer] date after s2:", date)
|
||
if time.Until(date) <= 0 {
|
||
date = time.Now().Add(time.Minute)
|
||
}
|
||
return date
|
||
}
|
||
|
||
func (ts *Timer) judgeHM() {
|
||
if ts.Hour < 0 || ts.Hour == int32(time.Now().Hour()) {
|
||
if ts.Minute < 0 || ts.Minute == int32(time.Now().Minute()) {
|
||
//zero.GetBot(zero.BotConfig.SelfID)
|
||
zero.RangeBot(func(id int64, ctx *zero.Ctx) bool {
|
||
ctx.Event = new(zero.Event)
|
||
ctx.Event.GroupID = int64(ts.Grpid)
|
||
if ts.Url == "" {
|
||
ctx.SendChain(atall, message.Text(ts.Alert))
|
||
} else {
|
||
ctx.SendChain(atall, message.Text(ts.Alert), message.Image(ts.Url).Add("cache", "0"))
|
||
}
|
||
return false
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// RegisterTimer 注册计时器
|
||
func (c *Clock) RegisterTimer(ts *Timer, save bool) {
|
||
key := ts.GetTimerInfo()
|
||
if c.timers != nil {
|
||
t, ok := c.GetTimer(key)
|
||
if t != ts && ok { // 避免重复注册定时器
|
||
t.Enable = false
|
||
}
|
||
c.timersmu.Lock()
|
||
(*c.timers)[key] = ts
|
||
c.timersmu.Unlock()
|
||
if save {
|
||
c.SaveTimers()
|
||
}
|
||
}
|
||
logrus.Printf("[群管]注册计时器[%t]%s", ts.Enable, key)
|
||
for ts.Enable {
|
||
nextdate := ts.nextWakeTime()
|
||
sleepsec := time.Until(nextdate)
|
||
logrus.Printf("[群管]计时器%s将睡眠%ds", key, sleepsec/time.Second)
|
||
time.Sleep(sleepsec)
|
||
if ts.Enable {
|
||
if ts.Month < 0 || ts.Month == int32(time.Now().Month()) {
|
||
if ts.Day < 0 || ts.Day == int32(time.Now().Day()) {
|
||
ts.judgeHM()
|
||
} else if ts.Day == 0 {
|
||
if ts.Week < 0 || ts.Week == int32(time.Now().Weekday()) {
|
||
ts.judgeHM()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// CancelTimer 取消计时器
|
||
func (c *Clock) CancelTimer(key string) bool {
|
||
t, ok := (*c.timers)[key]
|
||
if ok {
|
||
t.Enable = false
|
||
c.timersmu.Lock()
|
||
delete(*c.timers, key) // 避免重复取消
|
||
c.timersmu.Unlock()
|
||
_ = c.SaveTimers()
|
||
}
|
||
return ok
|
||
}
|
||
|
||
// SaveTimers 保存当前计时器
|
||
func (c *Clock) SaveTimers() error {
|
||
c.timersmu.RLock()
|
||
data, err := c.timersmap.Marshal()
|
||
c.timersmu.RUnlock()
|
||
if err == nil {
|
||
c.timersmu.Lock()
|
||
defer c.timersmu.Unlock()
|
||
f, err1 := os.OpenFile(*c.pbfile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
||
if err1 != nil {
|
||
return err1
|
||
} else {
|
||
_, err2 := f.Write(data)
|
||
f.Close()
|
||
return err2
|
||
}
|
||
}
|
||
return err
|
||
}
|
||
|
||
// ListTimers 列出本群所有计时器
|
||
func (c *Clock) ListTimers(grpID uint64) []string {
|
||
// 数组默认长度为map长度,后面append时,不需要重新申请内存和拷贝,效率很高
|
||
if c.timers != nil {
|
||
g := strconv.FormatUint(grpID, 10)
|
||
c.timersmu.RLock()
|
||
keys := make([]string, 0, len(*c.timers))
|
||
for k := range *c.timers {
|
||
if strings.Contains(k, g) {
|
||
start := strings.Index(k, "]")
|
||
msg := strings.ReplaceAll(k[start+1:]+"\n", "-1", "每")
|
||
msg = strings.ReplaceAll(msg, "月0日0周", "月周天")
|
||
msg = strings.ReplaceAll(msg, "月0日", "月")
|
||
msg = strings.ReplaceAll(msg, "日0周", "日")
|
||
keys = append(keys, msg)
|
||
}
|
||
}
|
||
c.timersmu.RUnlock()
|
||
return keys
|
||
} else {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func (c *Clock) GetTimer(key string) (t *Timer, ok bool) {
|
||
c.timersmu.RLock()
|
||
t, ok = (*c.timers)[key]
|
||
c.timersmu.RUnlock()
|
||
return
|
||
}
|
||
|
||
func (c *Clock) loadTimers(pbfile string) {
|
||
if file.IsExist(pbfile) {
|
||
f, err := os.Open(pbfile)
|
||
if err == nil {
|
||
data, err1 := io.ReadAll(f)
|
||
if err1 == nil {
|
||
if len(data) > 0 {
|
||
c.timersmap.Unmarshal(data)
|
||
for _, t := range c.timersmap.Timers {
|
||
go c.RegisterTimer(t, false)
|
||
}
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
c.timersmap.Timers = make(map[string]*Timer)
|
||
}
|
||
|
||
// GetTimerInfo 获得标准化定时字符串
|
||
func (ts *Timer) GetTimerInfo() string {
|
||
return fmt.Sprintf("[%d]%d月%d日%d周%d:%d", ts.Grpid, ts.Month, ts.Day, ts.Week, ts.Hour, ts.Minute)
|
||
}
|
||
|
||
// GetFilledTimer 获得填充好的ts
|
||
func GetFilledTimer(dateStrs []string, matchDateOnly bool) *Timer {
|
||
monthStr := []rune(dateStrs[1])
|
||
dayWeekStr := []rune(dateStrs[2])
|
||
hourStr := []rune(dateStrs[3])
|
||
minuteStr := []rune(dateStrs[4])
|
||
|
||
var ts Timer
|
||
ts.Month = chineseNum2Int(monthStr)
|
||
if (ts.Month != -1 && ts.Month <= 0) || ts.Month > 12 { // 月份非法
|
||
logrus.Println("[群管]月份非法!")
|
||
return &ts
|
||
}
|
||
lenOfDW := len(dayWeekStr)
|
||
if lenOfDW == 4 { // 包括末尾的"日"
|
||
dayWeekStr = []rune{dayWeekStr[0], dayWeekStr[2]} // 去除中间的十
|
||
ts.Day = chineseNum2Int(dayWeekStr)
|
||
if (ts.Day != -1 && ts.Day <= 0) || ts.Day > 31 { // 日期非法
|
||
logrus.Println("[群管]日期非法1!")
|
||
return &ts
|
||
}
|
||
} else if dayWeekStr[lenOfDW-1] == rune('日') { // xx日
|
||
dayWeekStr = dayWeekStr[:lenOfDW-1]
|
||
ts.Day = chineseNum2Int(dayWeekStr)
|
||
if (ts.Day != -1 && ts.Day <= 0) || ts.Day > 31 { // 日期非法
|
||
logrus.Println("[群管]日期非法2!")
|
||
return &ts
|
||
}
|
||
} else if dayWeekStr[0] == rune('每') { // 每周
|
||
ts.Week = -1
|
||
} else { // 周x
|
||
ts.Week = chineseNum2Int(dayWeekStr[1:])
|
||
if ts.Week == 7 { // 周天是0
|
||
ts.Week = 0
|
||
}
|
||
if ts.Week < 0 || ts.Week > 6 { // 星期非法
|
||
ts.Week = -11
|
||
logrus.Println("[群管]星期非法!")
|
||
return &ts
|
||
}
|
||
}
|
||
if len(hourStr) == 3 {
|
||
hourStr = []rune{hourStr[0], hourStr[2]} // 去除中间的十
|
||
}
|
||
ts.Hour = chineseNum2Int(hourStr)
|
||
if ts.Hour < -1 || ts.Hour > 23 { // 小时非法
|
||
logrus.Println("[群管]小时非法!")
|
||
return &ts
|
||
}
|
||
if len(minuteStr) == 3 {
|
||
minuteStr = []rune{minuteStr[0], minuteStr[2]} // 去除中间的十
|
||
}
|
||
ts.Minute = chineseNum2Int(minuteStr)
|
||
if ts.Minute < -1 || ts.Minute > 59 { // 分钟非法
|
||
logrus.Println("[群管]分钟非法!")
|
||
return &ts
|
||
}
|
||
if !matchDateOnly {
|
||
urlStr := dateStrs[5]
|
||
if urlStr != "" { // 是图片url
|
||
ts.Url = urlStr[3:] // utf-8下用为3字节
|
||
logrus.Println("[群管]" + ts.Url)
|
||
if !strings.HasPrefix(ts.Url, "http") {
|
||
ts.Url = "illegal"
|
||
logrus.Println("[群管]url非法!")
|
||
return &ts
|
||
}
|
||
}
|
||
ts.Alert = dateStrs[6]
|
||
ts.Enable = true
|
||
}
|
||
return &ts
|
||
}
|
||
|
||
// chineseNum2Int 汉字数字转int,仅支持-10~99,最多两位数,其中"每"解释为-1,"每二"为-2,以此类推
|
||
func chineseNum2Int(rs []rune) int32 {
|
||
r := -1
|
||
l := len(rs)
|
||
mai := rune('每')
|
||
if unicode.IsDigit(rs[0]) { // 默认可能存在的第二位也为int
|
||
r, _ = strconv.Atoi(string(rs))
|
||
} else {
|
||
if rs[0] == mai {
|
||
if l == 2 {
|
||
r = -chineseChar2Int(rs[1])
|
||
}
|
||
} else if l == 1 {
|
||
r = chineseChar2Int(rs[0])
|
||
} else {
|
||
ten := chineseChar2Int(rs[0])
|
||
if ten != 10 {
|
||
ten *= 10
|
||
}
|
||
ge := chineseChar2Int(rs[1])
|
||
if ge == 10 {
|
||
ge = 0
|
||
}
|
||
r = ten + ge
|
||
}
|
||
}
|
||
return int32(r)
|
||
}
|
||
|
||
// chineseChar2Int 处理单个字符的映射0~10
|
||
func chineseChar2Int(c rune) int {
|
||
if c == rune('日') || c == rune('天') { // 周日/周天
|
||
return 7
|
||
} else {
|
||
match := []rune("零一二三四五六七八九十")
|
||
for i, m := range match {
|
||
if c == m {
|
||
return i
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
}
|