This commit is contained in:
meera 2026-07-12 20:50:13 +03:00
parent aa2d04368e
commit 03c3c58811
3 changed files with 193 additions and 7 deletions

4
.gitignore vendored
View File

@ -28,6 +28,10 @@ go.work.sum
# env file # env file
.env .env
#sensitive config file and cred file
config.json
accounts.txt
ircserver
# Editor/IDE # Editor/IDE
# .idea/ # .idea/
# .vscode/ # .vscode/

190
http.go
View File

@ -1,19 +1,201 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
) )
type ModeResponse struct {
Action string `json:"action"`
Mode string `json:"mode"`
Target string `json:"target"`
Channel string `json:"channel"`
Status string `json:"status"`
}
type KickResponse struct {
Action string `json:"action"`
Target string `json:"target"`
Channel string `json:"channel"`
Reason string `json:"reason"`
Status string `json:"status"`
}
func startHTTP(cfg Config) { func startHTTP(cfg Config) {
htport := cfg.Webapiport htport := cfg.Webapiport
http.HandleFunc("/admin/", handleAdmin) http.HandleFunc("/admin/kick", handleKick)
http.HandleFunc("/admin/mode", handleModeAPI)
http.HandleFunc("/admin/list", handleListAPI)
http.HandleFunc("/admin/reloadmotd", handleMOTDreload)
fmt.Printf("[web] starting API on port %d\n", htport) fmt.Printf("[web] starting API on port %d\n", htport)
http.ListenAndServe(fmt.Sprintf(":%d", htport), nil) http.ListenAndServe(fmt.Sprintf(":%d", htport), nil)
} }
func handleAdmin(w http.ResponseWriter, r *http.Request) { func handleKick(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path { user, pass, ok := r.BasicAuth()
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if r.Method != http.MethodPost {
http.Error(w, "POST required", http.StatusMethodNotAllowed)
return
}
channelName := normalizeChannelName(r.FormValue("channel"))
targetNick := r.FormValue("nick")
reason := r.FormValue("reason")
if reason == "" {
reason = "Kicked via API"
}
chMu.Lock()
ch, ok := channels[channelName]
chMu.Unlock()
if !ok {
http.Error(w, "No such channel", http.StatusNotFound)
return
}
ch.mu.Lock()
defer ch.mu.Unlock()
target, ok := ch.members[targetNick]
if !ok {
http.Error(w, "Target not in channel", http.StatusNotFound)
return
}
if globalOps[targetNick] {
http.Error(w, "Cannot kick global operator", http.StatusForbidden)
return
}
for _, member := range ch.members {
member.fwrite(":server KICK %s %s :%s\r\n", channelName, targetNick, reason)
}
// Remove target
delete(ch.members, targetNick)
delete(target.channels, channelName)
resp := KickResponse{
Action: "kick",
Target: targetNick,
Channel: channelName,
Reason: reason,
Status: "ok",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func handleModeAPI(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if r.Method != http.MethodPost {
http.Error(w, "POST required", http.StatusMethodNotAllowed)
return
}
channelName := normalizeChannelName(r.FormValue("channel"))
mode := r.FormValue("mode")
targetNick := r.FormValue("nick")
chMu.Lock()
ch, ok := channels[channelName]
chMu.Unlock()
if !ok {
http.Error(w, "No such channel", http.StatusNotFound)
return
}
ch.mu.Lock()
defer ch.mu.Unlock()
switch mode {
case "+o":
if targetNick != "" {
ch.ops[targetNick] = true
for _, member := range ch.members {
member.fwrite(":api MODE %s +o %s\r\n", channelName, targetNick)
}
}
case "-o":
if targetNick != "" {
if globalOps[targetNick] {
http.Error(w, "Cannot remove global operator", http.StatusForbidden)
return
}
delete(ch.ops, targetNick)
for _, member := range ch.members {
member.fwrite(":api MODE %s -o %s\r\n", channelName, targetNick)
}
}
default: default:
http.NotFound(w, r) http.Error(w, "Unknown MODE flag", http.StatusBadRequest)
return
}
resp := ModeResponse{
Action: "mode",
Mode: mode,
Target: targetNick,
Channel: channelName,
Status: "ok",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
type ChannelInfo struct {
Name string `json:"name"`
Description string `json:"description"`
Users int `json:"users"`
}
func handleListAPI(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
chMu.Lock()
defer chMu.Unlock()
var list []ChannelInfo
for _, ch := range channels {
ch.mu.Lock()
count := len(ch.members)
desc := ch.description
name := ch.name
ch.mu.Unlock()
list = append(list, ChannelInfo{
Name: name,
Description: desc,
Users: count,
})
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(list); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func handleMOTDreload(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || !checkCredentials(user, pass) || !globalOps[user] {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
loadMOTD(motdFile)
type htresp struct {
Status string `json:"status"`
}
resp := htresp{
Status: "ok",
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
} }
} }

View File

@ -64,11 +64,11 @@ func loadConfig() (*Config, error) {
var conf Config var conf Config
jsdata, err := os.ReadFile("config.json") jsdata, err := os.ReadFile("config.json")
if err != nil { if err != nil {
return 0, err return nil, err
} }
err = json.Unmarshal(jsdata, &conf) err = json.Unmarshal(jsdata, &conf)
if err != nil { if err != nil {
return 0, err return nil, err
} }
for _, cfg := range conf.Channels { for _, cfg := range conf.Channels {
addChannel(cfg.Channel, cfg.Description) addChannel(cfg.Channel, cfg.Description)
@ -78,7 +78,7 @@ func loadConfig() (*Config, error) {
globalOps[cleanedNick] = true globalOps[cleanedNick] = true
} }
motdFile = conf.Motd motdFile = conf.Motd
return conf.Port, nil return &conf, nil
} }
func loadMOTD(filename string) error { func loadMOTD(filename string) error {
file, err := os.Open(filename) file, err := os.Open(filename)