CENTCOM/centcom.go
2026-01-17 01:24:48 -08:00

338 lines
6.8 KiB
Go

package main
import (
"bytes"
"database/sql"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
_ "github.com/mattn/go-sqlite3"
)
const CONFIGFILE = "./config.json"
type Client struct {
Conn *websocket.Conn
Name string
Mu sync.Mutex
}
type Server struct {
Mu sync.Mutex
Clients []*Client
Db *sql.DB
Cef *CEF
}
type CEF struct {
Conn *websocket.Conn
FromServer chan string
ToServer chan string
Kill chan error
}
type Message struct {
Type string `json:"type"`
User string `json:"user"`
Content string `json:"content"`
}
type Config struct {
Listen string `json:"listen"`
Webhook string `json:"webhook"`
Cef struct {
Channel string `json:"channel"`
Username string `json:"username"`
Password string `json:"password"`
} `json:"cef"`
Users []string `json:"users"`
}
var config Config
var server = Server{
Clients: make([]*Client, 0),
}
func (s *Server) KillClient(client *Client) {
s.Mu.Lock()
defer s.Mu.Unlock()
for i, _ := range s.Clients {
if client == s.Clients[i] {
s.Clients = append(s.Clients[:i], s.Clients[i+1:]...)
return
}
}
}
func (c *Client) Send(struc any) {
c.Mu.Lock()
defer c.Mu.Unlock()
err := c.Conn.WriteJSON(struc)
if err != nil {
server.KillClient(c)
}
}
func Webhook(message Message) {
jsonValue, _ := json.Marshal(struct {
Content string `json:"content"`
Username string `json:"username"`
}{
Content: message.Content,
Username: message.User,
})
_, err := http.Post(config.Webhook, "application/json", bytes.NewBuffer(jsonValue))
if err != nil {
log.Println("webhook", err)
}
}
func (s *Server) BroadcastMessage(from string, content string) {
message := Message{
Type: "message",
User: from,
Content: content,
}
for _, c := range s.Clients {
c.Send(message)
}
tx, err := s.Db.Begin()
stmt, err := tx.Prepare("insert into messages(`type`, `user`, `content`) values(?, ?, ?)")
if err != nil {
log.Fatal(err)
return
}
_, err = stmt.Exec("message", from, content)
if err != nil {
log.Println(stmt)
return
}
tx.Commit()
go Webhook(message)
go s.CEFSend(message)
}
func (s *Server) CEFSend(message Message) {
msg := fmt.Sprintf("NPC %s %s :%s", config.Cef.Channel, message.User, message.Content)
select {
case s.Cef.ToServer <- msg:
default:
}
}
func (s *Server) SendHistory(client *Client) {
// Blast
rows, err := s.Db.Query("SELECT `user`, `content` FROM messages ORDER BY rowid ASC")
if err != nil {
log.Println(err)
return
}
defer rows.Close()
var user, content string
for rows.Next() {
err = rows.Scan(&user, &content)
if err != nil {
log.Panicln(err)
}
client.Send(Message{
Type: "history",
User: user,
Content: content,
})
}
client.Send(Message{
Type: "ready",
User: strconv.Itoa(len(server.Clients)),
Content: "Welcome back, " + client.Name,
})
}
func loadConfig() {
data, err := os.ReadFile(CONFIGFILE)
if err != nil {
log.Panicln("Could not read config, ", err)
}
err = json.Unmarshal(data, &config)
if err != nil {
log.Panicln("Could not load config, ", err)
}
}
func watchConfig() {
initialStat, _ := os.Stat(CONFIGFILE)
for {
stat, _ := os.Stat(CONFIGFILE)
if stat.Size() != initialStat.Size() || stat.ModTime() != initialStat.ModTime() {
loadConfig()
}
time.Sleep(1 * time.Second)
}
}
func (c *CEF) Send(m string) {
log.Println("[CEF-O]", m)
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(m))
if err != nil {
log.Println("[CEF-S]", err)
c.Kill <- err
return
}
}
func (c *CEF) Loop() {
for {
_, message, err := c.Conn.ReadMessage()
log.Println("[CEF]", string(message), err)
if err != nil {
c.Kill <- err
return
}
c.FromServer <- string(message)
}
}
func BasicCef() {
c, _, err := websocket.DefaultDialer.Dial("wss://cef.icu/chat", nil)
defer c.Close()
if err != nil {
return
}
cef := &CEF{
Conn: c,
FromServer: make(chan string),
ToServer: make(chan string),
Kill: make(chan error),
}
defer close(cef.FromServer)
defer close(cef.ToServer)
defer close(cef.Kill)
server.Cef = cef
cef.Send("CAP REQ :account-notify account-tag away-notify batch chghost cef/extended-names draft/chathistory draft/multiline draft/event-playback draft/relaymsg echo-message extended-join invite-notify labeled-response message-tags multi-prefix sasl server-time setname userhost-in-names")
cef.Send("NICK " + config.Cef.Username)
cef.Send("USER " + config.Cef.Username + " . . :cool dude")
cef.Send("AUTHENTICATE PLAIN")
auth := fmt.Sprintf("%s\000%s\000%s", config.Cef.Username, config.Cef.Username, config.Cef.Password)
cef.Send("AUTHENTICATE " + base64.StdEncoding.EncodeToString([]byte(auth)))
cef.Send("CAP END")
// cef.Send("JOIN " + config.Cef.Channel)
go cef.Loop()
for {
select {
case msg := <-cef.FromServer:
split := strings.Split(msg, " ")
if len(split) > 2 {
if split[1] == "PING" {
cef.Send("PONG " + split[2])
}
}
case msg := <-cef.ToServer:
cef.Send(msg)
case killError := <-cef.Kill:
log.Println("[CEF]", killError)
break
}
}
}
var upgrader = websocket.Upgrader{} // use default options
func comm(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
var client = &Client{
Conn: c,
Name: "",
}
setup := false
var inbound struct {
Msg string `json:"msg"`
}
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
err = json.Unmarshal(message, &inbound)
if err != nil {
log.Println("unmarshal", err)
return
}
log.Printf("recv: %s", message)
if !setup {
if slices.Contains(config.Users, inbound.Msg) {
server.Mu.Lock()
server.Clients = append(server.Clients, client)
server.Mu.Unlock()
defer server.KillClient(client)
setup = true
client.Name = inbound.Msg
server.SendHistory(client)
} else {
err := c.WriteJSON(Message{
Type: "error",
User: "",
Content: "LANCER NOT FOUND",
})
if err != nil {
log.Println("writejson", err)
return
}
}
} else {
server.BroadcastMessage(client.Name, inbound.Msg)
}
}
}
func CefDaemon() {
for {
BasicCef()
time.Sleep(60)
}
}
func main() {
loadConfig()
go watchConfig()
var addr = flag.String("addr", config.Listen, "http service address")
db, err := sql.Open("sqlite3", "./messages.db")
if err != nil {
log.Fatal(err)
}
server.Db = db
db.Exec("CREATE TABLE messages(`type` text, `user` text, `content` text)")
defer db.Close()
go CefDaemon()
flag.Parse()
log.SetFlags(0)
fs := http.FileServer(http.Dir("./assets"))
http.Handle("/", fs)
http.HandleFunc("/comm", comm)
log.Fatal(http.ListenAndServe(*addr, nil))
}