338 lines
6.8 KiB
Go
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))
|
|
}
|