залил
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
var (
|
||||
db *sql.DB
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type MarkedUser struct {
|
||||
Username string
|
||||
Platform string
|
||||
LastMarked string // YYYY-MM-DD
|
||||
}
|
||||
|
||||
func Init(path string) error {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
db, err = sql.Open("sqlite", path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = createTables()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func Close() {
|
||||
if db != nil {
|
||||
_ = db.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func createTables() error {
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS commands (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
trigger TEXT UNIQUE NOT NULL,
|
||||
template TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 1,
|
||||
cooldown_sec INTEGER DEFAULT 0,
|
||||
permission TEXT DEFAULT 'everyone'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
platform TEXT NOT NULL,
|
||||
event_name TEXT NOT NULL,
|
||||
action_chain TEXT NOT NULL,
|
||||
UNIQUE(platform, event_name)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS hotkey_rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
min_donation_amount INTEGER NOT NULL,
|
||||
combination TEXT NOT NULL,
|
||||
platform TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS platform_tokens (
|
||||
platform TEXT PRIMARY KEY,
|
||||
client_id TEXT,
|
||||
client_secret TEXT,
|
||||
user_token TEXT,
|
||||
user_refresh TEXT,
|
||||
bot_token TEXT,
|
||||
bot_refresh TEXT,
|
||||
user_login TEXT,
|
||||
bot_login TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS marked_users (
|
||||
username TEXT NOT NULL,
|
||||
platform TEXT NOT NULL,
|
||||
last_marked TEXT,
|
||||
PRIMARY KEY (username, platform)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS ai_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
provider TEXT NOT NULL,
|
||||
api_key TEXT,
|
||||
endpoint TEXT,
|
||||
model TEXT,
|
||||
system_prompt TEXT,
|
||||
client_id TEXT,
|
||||
client_secret TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS notification_settings (
|
||||
event_name TEXT PRIMARY KEY,
|
||||
sound_file TEXT NOT NULL,
|
||||
volume INTEGER DEFAULT 70,
|
||||
enabled BOOLEAN DEFAULT 1
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS web_services (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
service_type TEXT NOT NULL,
|
||||
port INTEGER NOT NULL,
|
||||
config_json TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 1,
|
||||
running BOOLEAN DEFAULT 0,
|
||||
started_at DATETIME
|
||||
);
|
||||
`
|
||||
_, err := db.Exec(schema)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- Команды ----------
|
||||
type Command struct {
|
||||
ID int
|
||||
Trigger string
|
||||
Template string
|
||||
Enabled bool
|
||||
CooldownSec int
|
||||
Permission string
|
||||
}
|
||||
|
||||
func GetCommands() ([]Command, error) {
|
||||
rows, err := db.Query("SELECT id, trigger, template, enabled, cooldown_sec, permission FROM commands")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
var cmds []Command
|
||||
for rows.Next() {
|
||||
var c Command
|
||||
err := rows.Scan(&c.ID, &c.Trigger, &c.Template, &c.Enabled, &c.CooldownSec, &c.Permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, c)
|
||||
}
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
func AddCommand(trigger, template string, enabled bool, cooldown int, perm string) error {
|
||||
_, err := db.Exec("INSERT INTO commands (trigger, template, enabled, cooldown_sec, permission) VALUES (?, ?, ?, ?, ?)",
|
||||
trigger, template, enabled, cooldown, perm)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateCommand(id int, trigger, template string, enabled bool, cooldown int, perm string) error {
|
||||
_, err := db.Exec("UPDATE commands SET trigger=?, template=?, enabled=?, cooldown_sec=?, permission=? WHERE id=?",
|
||||
trigger, template, enabled, cooldown, perm, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteCommand(id int) error {
|
||||
_, err := db.Exec("DELETE FROM commands WHERE id=?", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- События (расширенные действия) ----------
|
||||
type Action struct {
|
||||
Type string `json:"type"` // send_message, play_sound, press_hotkey, http_request, run_program, send_alert
|
||||
// Общие поля
|
||||
Text string `json:"text,omitempty"` // для send_message и send_alert
|
||||
SoundFile string `json:"sound_file,omitempty"` // для play_sound и send_alert
|
||||
Keys string `json:"keys,omitempty"` // для press_hotkey
|
||||
URL string `json:"url,omitempty"` // для http_request
|
||||
// Для run_program
|
||||
Executable string `json:"executable,omitempty"`
|
||||
Args string `json:"args,omitempty"`
|
||||
// Для send_alert
|
||||
Title string `json:"title,omitempty"`
|
||||
AlertText string `json:"alert_text,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Duration int `json:"duration,omitempty"` // секунды
|
||||
TargetWebServiceID int `json:"target_web_service_id,omitempty"` // 0 = все alert-сервисы
|
||||
}
|
||||
|
||||
// GetEventActions возвращает цепочку действий для события
|
||||
func GetEventActions(platform, eventName string) ([]Action, error) {
|
||||
var chainJSON string
|
||||
err := db.QueryRow("SELECT action_chain FROM events WHERE platform=? AND event_name=?", platform, eventName).Scan(&chainJSON)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var actions []Action
|
||||
if err := json.Unmarshal([]byte(chainJSON), &actions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
// SaveEventActions сохраняет цепочку действий для события (вставка или обновление)
|
||||
func SaveEventActions(platform, eventName string, actions []Action) error {
|
||||
chainJSON, err := json.Marshal(actions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(`
|
||||
INSERT OR REPLACE INTO events (platform, event_name, action_chain)
|
||||
VALUES (?, ?, ?)
|
||||
`, platform, eventName, string(chainJSON))
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- Горячие клавиши по донатам ----------
|
||||
func GetHotkeyRules(platform string) (map[int]string, error) {
|
||||
rows, err := db.Query("SELECT min_donation_amount, combination FROM hotkey_rules WHERE platform=?", platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
rules := make(map[int]string)
|
||||
for rows.Next() {
|
||||
var amount int
|
||||
var comb string
|
||||
if err := rows.Scan(&amount, &comb); err == nil {
|
||||
rules[amount] = comb
|
||||
}
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func AddHotkeyRule(platform string, minAmount int, combination string) error {
|
||||
_, err := db.Exec("INSERT INTO hotkey_rules (platform, min_donation_amount, combination) VALUES (?, ?, ?)",
|
||||
platform, minAmount, combination)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteHotkeyRule(platform string, minAmount int) error {
|
||||
_, err := db.Exec("DELETE FROM hotkey_rules WHERE platform=? AND min_donation_amount=?", platform, minAmount)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- Токены платформ ----------
|
||||
type PlatformTokens struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
UserToken string
|
||||
UserRefresh string
|
||||
BotToken string
|
||||
BotRefresh string
|
||||
UserLogin string
|
||||
BotLogin string
|
||||
}
|
||||
|
||||
func GetPlatformTokens(platform string) (*PlatformTokens, error) {
|
||||
var pt PlatformTokens
|
||||
row := db.QueryRow("SELECT client_id, client_secret, user_token, user_refresh, bot_token, bot_refresh, user_login, bot_login FROM platform_tokens WHERE platform=?", platform)
|
||||
err := row.Scan(&pt.ClientID, &pt.ClientSecret, &pt.UserToken, &pt.UserRefresh, &pt.BotToken, &pt.BotRefresh, &pt.UserLogin, &pt.BotLogin)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pt, nil
|
||||
}
|
||||
|
||||
func SetPlatformTokens(platform string, pt *PlatformTokens) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT OR REPLACE INTO platform_tokens
|
||||
(platform, client_id, client_secret, user_token, user_refresh, bot_token, bot_refresh, user_login, bot_login)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
platform, pt.ClientID, pt.ClientSecret, pt.UserToken, pt.UserRefresh, pt.BotToken, pt.BotRefresh, pt.UserLogin, pt.BotLogin)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- Отметки пользователей ----------
|
||||
func IsUserMarked(username, platform string) (bool, string, error) {
|
||||
var lastMarked string
|
||||
err := db.QueryRow("SELECT last_marked FROM marked_users WHERE username=? AND platform=?", username, platform).Scan(&lastMarked)
|
||||
if err == sql.ErrNoRows {
|
||||
return false, "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
return true, lastMarked, nil
|
||||
}
|
||||
|
||||
func SetUserMarked(username, platform string, marked bool) error {
|
||||
if marked {
|
||||
_, err := db.Exec("INSERT OR REPLACE INTO marked_users (username, platform, last_marked) VALUES (?, ?, '')", username, platform)
|
||||
return err
|
||||
} else {
|
||||
_, err := db.Exec("DELETE FROM marked_users WHERE username=? AND platform=?", username, platform)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateMarkedUserDate(username, platform string, t time.Time) error {
|
||||
date := t.Format("2006-01-02")
|
||||
_, err := db.Exec("UPDATE marked_users SET last_marked=? WHERE username=? AND platform=?", date, username, platform)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- AI конфиг ----------
|
||||
type AIConfig struct {
|
||||
Provider string `json:"provider"`
|
||||
APIKey string `json:"api_key"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Model string `json:"model"`
|
||||
SystemPrompt string `json:"system_prompt"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
}
|
||||
|
||||
func SaveAIConfig(cfg *AIConfig) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT OR REPLACE INTO ai_config (id, provider, api_key, endpoint, model, system_prompt, client_id, client_secret)
|
||||
VALUES (1, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
cfg.Provider, cfg.APIKey, cfg.Endpoint, cfg.Model, cfg.SystemPrompt, cfg.ClientID, cfg.ClientSecret)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetAIConfig() (*AIConfig, error) {
|
||||
var cfg AIConfig
|
||||
row := db.QueryRow(`
|
||||
SELECT provider, api_key, endpoint, model, system_prompt, client_id, client_secret
|
||||
FROM ai_config WHERE id = 1`)
|
||||
err := row.Scan(&cfg.Provider, &cfg.APIKey, &cfg.Endpoint, &cfg.Model, &cfg.SystemPrompt, &cfg.ClientID, &cfg.ClientSecret)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// По умолчанию
|
||||
return &AIConfig{
|
||||
Provider: "ollama",
|
||||
SystemPrompt: "ты в чате твитча, ответь одним предложением.",
|
||||
}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// ---------- Уведомления ----------
|
||||
type NotificationSetting struct {
|
||||
EventName string `json:"event_name"`
|
||||
SoundFile string `json:"sound_file"`
|
||||
Volume int `json:"volume"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func GetAllNotificationSettings() ([]NotificationSetting, error) {
|
||||
rows, err := db.Query("SELECT event_name, sound_file, volume, enabled FROM notification_settings")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
var settings []NotificationSetting
|
||||
for rows.Next() {
|
||||
var ns NotificationSetting
|
||||
if err := rows.Scan(&ns.EventName, &ns.SoundFile, &ns.Volume, &ns.Enabled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings = append(settings, ns)
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func SaveNotificationSetting(ns *NotificationSetting) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT OR REPLACE INTO notification_settings (event_name, sound_file, volume, enabled)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
ns.EventName, ns.SoundFile, ns.Volume, ns.Enabled)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- Веб-сервисы ----------
|
||||
type ChatWebConfig struct {
|
||||
BackgroundColor string `json:"bg_color"`
|
||||
TextColor string `json:"text_color"`
|
||||
FontSize int `json:"font_size"`
|
||||
FontFamily string `json:"font_family"`
|
||||
Opacity int `json:"opacity"`
|
||||
MessageTimeoutSec int `json:"message_timeout_sec"`
|
||||
MaxMessages int `json:"max_messages"`
|
||||
ShowBadges bool `json:"show_badges"`
|
||||
ShowTimestamps bool `json:"show_timestamps"`
|
||||
}
|
||||
|
||||
type AlertEventConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
TitleTemplate string `json:"title_template"`
|
||||
TextTemplate string `json:"text_template"`
|
||||
ImageFile string `json:"image_file"`
|
||||
SoundFile string `json:"sound_file"`
|
||||
DurationSec int `json:"duration_sec"`
|
||||
}
|
||||
|
||||
type AlertWebConfig struct {
|
||||
Events map[string]AlertEventConfig `json:"events"`
|
||||
DefaultDuration int `json:"default_duration"`
|
||||
DefaultImage string `json:"default_image"`
|
||||
DefaultSound string `json:"default_sound"`
|
||||
}
|
||||
|
||||
type WebService struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"service_type"`
|
||||
Port int `json:"port"`
|
||||
ConfigJSON string `json:"config_json"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Running bool `json:"running"`
|
||||
StartedAt *time.Time `json:"started_at"`
|
||||
}
|
||||
|
||||
func (ws *WebService) GetChatConfig() (*ChatWebConfig, error) {
|
||||
if ws.Type != "chat" {
|
||||
return nil, fmt.Errorf("not a chat service")
|
||||
}
|
||||
var cfg ChatWebConfig
|
||||
if err := json.Unmarshal([]byte(ws.ConfigJSON), &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func (ws *WebService) GetAlertConfig() (*AlertWebConfig, error) {
|
||||
if ws.Type != "alert" {
|
||||
return nil, fmt.Errorf("not an alert service")
|
||||
}
|
||||
var cfg AlertWebConfig
|
||||
if err := json.Unmarshal([]byte(ws.ConfigJSON), &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func GetAllWebServices() ([]WebService, error) {
|
||||
rows, err := db.Query(`SELECT id, service_type, port, config_json, enabled, running, started_at FROM web_services`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
var list []WebService
|
||||
for rows.Next() {
|
||||
var ws WebService
|
||||
var startedAt sql.NullTime
|
||||
err := rows.Scan(&ws.ID, &ws.Type, &ws.Port, &ws.ConfigJSON, &ws.Enabled, &ws.Running, &startedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if startedAt.Valid {
|
||||
ws.StartedAt = &startedAt.Time
|
||||
}
|
||||
list = append(list, ws)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func GetWebService(id int) (*WebService, error) {
|
||||
var ws WebService
|
||||
var startedAt sql.NullTime
|
||||
err := db.QueryRow(`SELECT id, service_type, port, config_json, enabled, running, started_at FROM web_services WHERE id = ?`, id).
|
||||
Scan(&ws.ID, &ws.Type, &ws.Port, &ws.ConfigJSON, &ws.Enabled, &ws.Running, &startedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if startedAt.Valid {
|
||||
ws.StartedAt = &startedAt.Time
|
||||
}
|
||||
return &ws, nil
|
||||
}
|
||||
|
||||
func CreateWebService(serviceType string, port int, config interface{}) (int, error) {
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
res, err := db.Exec(`INSERT INTO web_services (service_type, port, config_json, enabled, running) VALUES (?, ?, ?, 1, 0)`,
|
||||
serviceType, port, string(configJSON))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
return int(id), nil
|
||||
}
|
||||
|
||||
func UpdateWebService(id int, port int, config interface{}, enabled bool) error {
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(`UPDATE web_services SET port = ?, config_json = ?, enabled = ? WHERE id = ?`,
|
||||
port, string(configJSON), enabled, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteWebService(id int) error {
|
||||
_, err := db.Exec(`DELETE FROM web_services WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func SetWebServiceRunning(id int, running bool) error {
|
||||
_, err := db.Exec(`UPDATE web_services SET running = ?, started_at = CURRENT_TIMESTAMP WHERE id = ?`, running, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------- Настройки (ключ-значение) ----------
|
||||
// GetSetting возвращает значение настройки (пустая строка, если нет)
|
||||
func GetSetting(key string) (string, error) {
|
||||
var value string
|
||||
err := db.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&value)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SetSetting сохраняет или обновляет настройку
|
||||
func SetSetting(key, value string) error {
|
||||
_, err := db.Exec("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", key, value)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user