залил

This commit is contained in:
2026-04-15 08:00:15 +03:00
commit 5549b3545e
51 changed files with 8073 additions and 0 deletions
+531
View File
@@ -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
}