Telegram Webhookによるボット操作機能を追加(課題の追加・完了・一覧・スヌーズ対応)

This commit is contained in:
2026-06-07 12:04:47 +09:00
parent c7f4c40964
commit bd600c24c9
10 changed files with 812 additions and 20 deletions

View File

@@ -0,0 +1,169 @@
package service
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
var (
reTimeHHMM = regexp.MustCompile(`^(\d{1,2}):(\d{2})$`)
reTimeHHJMM = regexp.MustCompile(`^(\d{1,2})時(\d{1,2})分$`)
reTimeHHJ = regexp.MustCompile(`^(\d{1,2})時$`)
reDateMD = regexp.MustCompile(`^(\d{1,2})/(\d{1,2})$`)
reDateMJDJ = regexp.MustCompile(`^(\d{1,2})月(\d{1,2})日$`)
)
var weekdayNames = map[string]time.Weekday{
"月": time.Monday,
"火": time.Tuesday,
"水": time.Wednesday,
"木": time.Thursday,
"金": time.Friday,
"土": time.Saturday,
"日": time.Sunday,
}
// ParseAddCommand parses the arguments after /add.
// Format: <title> <date> [time] [#subject] [!priority]
// Returns title, dueDate, subject, priority (Go string: "high"/"medium"/"low"), error.
// On format error, err.Error() == "usage" means show usage hint.
func ParseAddCommand(text string) (title string, dueDate time.Time, subject string, priority string, err error) {
priority = "medium"
tokens := strings.Fields(text)
if len(tokens) == 0 {
err = fmt.Errorf("usage")
return
}
// Extract #subject and !priority modifiers
var core []string
for _, tok := range tokens {
switch {
case strings.HasPrefix(tok, "#") && len(tok) > 1:
subject = tok[1:]
case strings.HasPrefix(tok, "!") && len(tok) > 1:
priority = parsePriorityJP(tok[1:])
default:
core = append(core, tok)
}
}
if len(core) < 2 {
err = fmt.Errorf("usage")
return
}
// Extract optional time token from end
hour, minute := 23, 59
if h, m, ok := parseTimeToken(core[len(core)-1]); ok {
hour, minute = h, m
core = core[:len(core)-1]
}
if len(core) < 2 {
err = fmt.Errorf("usage")
return
}
// Extract date token from end
dateTok := core[len(core)-1]
parsedDate, ok := parseDateToken(dateTok, hour, minute)
if !ok {
err = fmt.Errorf("日付が読み取れませんでした: %q\n使える形式: 今日/明日/明後日/月〜日/MM/DD/M月D日", dateTok)
return
}
dueDate = parsedDate
core = core[:len(core)-1]
if len(core) == 0 {
err = fmt.Errorf("usage")
return
}
title = strings.Join(core, " ")
return
}
func parsePriorityJP(s string) string {
switch s {
case "高":
return "high"
case "低":
return "low"
default:
return "medium"
}
}
func parseTimeToken(s string) (hour, minute int, ok bool) {
if m := reTimeHHMM.FindStringSubmatch(s); m != nil {
h, _ := strconv.Atoi(m[1])
min, _ := strconv.Atoi(m[2])
if h <= 23 && min <= 59 {
return h, min, true
}
}
if m := reTimeHHJMM.FindStringSubmatch(s); m != nil {
h, _ := strconv.Atoi(m[1])
min, _ := strconv.Atoi(m[2])
if h <= 23 && min <= 59 {
return h, min, true
}
}
if m := reTimeHHJ.FindStringSubmatch(s); m != nil {
h, _ := strconv.Atoi(m[1])
if h <= 23 {
return h, 0, true
}
}
return 0, 0, false
}
func parseDateToken(s string, hour, minute int) (time.Time, bool) {
now := time.Now()
loc := now.Location()
switch s {
case "今日":
return time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, loc), true
case "明日":
d := now.AddDate(0, 0, 1)
return time.Date(d.Year(), d.Month(), d.Day(), hour, minute, 0, 0, loc), true
case "明後日":
d := now.AddDate(0, 0, 2)
return time.Date(d.Year(), d.Month(), d.Day(), hour, minute, 0, 0, loc), true
}
if wd, ok := weekdayNames[s]; ok {
d := nextWeekdayFrom(now, wd)
return time.Date(d.Year(), d.Month(), d.Day(), hour, minute, 0, 0, loc), true
}
var month, day int
if m := reDateMD.FindStringSubmatch(s); m != nil {
month, _ = strconv.Atoi(m[1])
day, _ = strconv.Atoi(m[2])
} else if m := reDateMJDJ.FindStringSubmatch(s); m != nil {
month, _ = strconv.Atoi(m[1])
day, _ = strconv.Atoi(m[2])
} else {
return time.Time{}, false
}
if month < 1 || month > 12 || day < 1 || day > 31 {
return time.Time{}, false
}
t := time.Date(now.Year(), time.Month(month), day, hour, minute, 0, 0, loc)
if t.Before(now) {
t = t.AddDate(1, 0, 0)
}
return t, true
}
// nextWeekdayFrom returns the date of the next occurrence of wd from 'from'.
func nextWeekdayFrom(from time.Time, wd time.Weekday) time.Time {
days := (int(wd) - int(from.Weekday()) + 7) % 7
return from.AddDate(0, 0, days)
}