remove old files, move todo.md to readme.md
This commit is contained in:
parent
de7e5663e2
commit
c91b76f807
333
old/main.go
333
old/main.go
@ -1,333 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gzipResponseWriter) Write(data []byte) (int, error) {
|
|
||||||
return g.Writer.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GzipMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gz := gzip.NewWriter(w)
|
|
||||||
defer gz.Close()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Encoding", "gzip")
|
|
||||||
next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClientIP(r *http.Request) string {
|
|
||||||
if fwdIP := r.Header.Get("X-Forwarded-For"); fwdIP != "" {
|
|
||||||
return strings.Split(fwdIP, ",")[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
clientIP := r.RemoteAddr
|
|
||||||
if host, _, err := net.SplitHostPort(clientIP); err == nil {
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
return clientIP
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Content string
|
|
||||||
SenderIp string
|
|
||||||
SenderUsername string
|
|
||||||
Timestamp string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMessage(username, ip, content string) *Message {
|
|
||||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
return &Message{
|
|
||||||
Content: content,
|
|
||||||
SenderIp: ip,
|
|
||||||
SenderUsername: username,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Ip string
|
|
||||||
Port int
|
|
||||||
Messages []Message
|
|
||||||
// Add mappings for IPs and usernames
|
|
||||||
Connected map[string]time.Time // Map IP -> Last activity time
|
|
||||||
Usernames map[string]string // Map IP -> Username
|
|
||||||
mu sync.Mutex // For thread safety
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(ip string, port int) *Server {
|
|
||||||
return &Server{
|
|
||||||
Ip: ip,
|
|
||||||
Port: port,
|
|
||||||
Messages: make([]Message, 0),
|
|
||||||
Connected: make(map[string]time.Time),
|
|
||||||
Usernames: make(map[string]string),
|
|
||||||
mu: sync.Mutex{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) AddMessage(username string, userip string, contents string) {
|
|
||||||
message := NewMessage(username, userip, contents)
|
|
||||||
s.Messages = append(s.Messages, *message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) updateActivity(ip string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.Connected[ip] = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) cleanupActivity() {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
for ip, lastActivity := range s.Connected {
|
|
||||||
if time.Since(lastActivity) > 10*time.Second {
|
|
||||||
delete(s.Connected, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handlePing(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
s.updateActivity(clientIP)
|
|
||||||
s.cleanupActivity()
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validUsername(username string) bool {
|
|
||||||
for _, c := range username {
|
|
||||||
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleUsername(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPut {
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
|
|
||||||
var req struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
|
|
||||||
if len(req.Username) > 64 {
|
|
||||||
http.Error(w, fmt.Sprintf(`{"error": "Username too long (must be less than 64 characters)"}`), http.StatusRequestEntityTooLarge)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validUsername(req.Username) {
|
|
||||||
http.Error(w, fmt.Sprintf(`{"error": "Username must only contain alphanumeric characters and/or underscores"}`), http.StatusBadRequest)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, username := range s.Usernames {
|
|
||||||
if username == req.Username {
|
|
||||||
s.mu.Unlock()
|
|
||||||
http.Error(w, fmt.Sprintf(`{"error": "Username already exists"}`), http.StatusConflict)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := s.Usernames[clientIP]; ok {
|
|
||||||
fmt.Println("Name change detected, fixing messages to reflect the change")
|
|
||||||
for i, msg := range s.Messages {
|
|
||||||
if msg.SenderIp == clientIP {
|
|
||||||
s.Messages[i].SenderUsername = req.Username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Usernames[clientIP] = req.Username
|
|
||||||
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "Username registered"})
|
|
||||||
fmt.Println(clientIP, "->", req.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMessageTemplate(file string, body string) string {
|
|
||||||
contents, _ := os.ReadFile(file)
|
|
||||||
return strings.Replace(string(contents), "{{body}}", body, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
|
|
||||||
var body string
|
|
||||||
for _, msg := range s.Messages {
|
|
||||||
body += fmt.Sprintf(`<p><span class="username">%s </span><span class="timestamp">%s</span><br><span class="message">%s</span></p>`,
|
|
||||||
msg.SenderUsername, msg.Timestamp, msg.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(getMessageTemplate("messages.html", body)))
|
|
||||||
|
|
||||||
case http.MethodPut:
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Get client's IP
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
|
|
||||||
// Check if IP is registered
|
|
||||||
s.mu.Lock()
|
|
||||||
username, exists := s.Usernames[clientIP]
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
errorFmt := fmt.Sprintf(`{"error": "IP %s not registered with username"}`, clientIP)
|
|
||||||
http.Error(w, errorFmt, http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
|
|
||||||
http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add message using the stored username and IP
|
|
||||||
s.AddMessage(username, clientIP, msg.Message)
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
|
||||||
"status": "Message received",
|
|
||||||
"from": username,
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleUsernameStatus(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
username, exists := s.Usernames[clientIP]
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"hasUsername": exists,
|
|
||||||
"username": username,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleUsers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
s.updateActivity(clientIP)
|
|
||||||
s.cleanupActivity()
|
|
||||||
s.mu.Lock()
|
|
||||||
var users []string
|
|
||||||
for ip := range s.Connected {
|
|
||||||
users = append(users, s.Usernames[ip])
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"users": users,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != "/" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
w.Write(readFile("root.html"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFile(filepath string) []byte {
|
|
||||||
contents, _ := os.ReadFile(filepath)
|
|
||||||
return contents
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleJs(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = r
|
|
||||||
w.Header().Set("Content-Type", "application/javascript")
|
|
||||||
w.Write(readFile("root.js"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleCss(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = r
|
|
||||||
w.Header().Set("Content-Type", "text/css")
|
|
||||||
w.Write(readFile("root.css"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
|
||||||
handler := http.NewServeMux()
|
|
||||||
handler.HandleFunc("/ping", s.handlePing)
|
|
||||||
handler.HandleFunc("/username", s.handleUsername)
|
|
||||||
handler.HandleFunc("/messages", s.handleMessages)
|
|
||||||
handler.HandleFunc("/username/status", s.handleUsernameStatus)
|
|
||||||
handler.HandleFunc("/users", s.handleUsers)
|
|
||||||
handler.HandleFunc("/", s.handleRoot)
|
|
||||||
handler.HandleFunc("/root.js", s.handleJs)
|
|
||||||
handler.HandleFunc("/root.css", s.handleCss)
|
|
||||||
fmt.Printf("Server starting on %s:%d\n", s.Ip, s.Port)
|
|
||||||
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.Ip, s.Port), GzipMiddleware(handler)); err != nil {
|
|
||||||
fmt.Printf("Server error: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ip := os.Args[1]
|
|
||||||
port, _ := strconv.Atoi(os.Args[2])
|
|
||||||
// ip := "localhost"
|
|
||||||
// port := 8080
|
|
||||||
server := NewServer(ip, port)
|
|
||||||
server.Run()
|
|
||||||
}
|
|
366
old/main.go.bak
366
old/main.go.bak
@ -1,366 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *gzipResponseWriter) Write(data []byte) (int, error) {
|
|
||||||
return g.Writer.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GzipMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gz := gzip.NewWriter(w)
|
|
||||||
defer gz.Close()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Encoding", "gzip")
|
|
||||||
next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClientIP(r *http.Request) string {
|
|
||||||
if fwdIP := r.Header.Get("X-Forwarded-For"); fwdIP != "" {
|
|
||||||
return strings.Split(fwdIP, ",")[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
clientIP := r.RemoteAddr
|
|
||||||
if host, _, err := net.SplitHostPort(clientIP); err == nil {
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
return clientIP
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Content string
|
|
||||||
SenderIp string
|
|
||||||
SenderUsername string
|
|
||||||
Timestamp string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMessage(username, ip, content string) *Message {
|
|
||||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
return &Message{
|
|
||||||
Content: content,
|
|
||||||
SenderIp: ip,
|
|
||||||
SenderUsername: username,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use it
|
|
||||||
func writeMessageToFile(msg Message) {
|
|
||||||
contents := fmt.Sprintf("%s,%s,%s,%s\n", msg.SenderUsername, msg.SenderIp, msg.Timestamp, msg.Content)
|
|
||||||
os.WriteFile("messages.txt", []byte(contents), os.ModeAppend)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use it
|
|
||||||
func writeMessagesToFile(messages []Message) {
|
|
||||||
var contents string
|
|
||||||
for _, msg := range messages {
|
|
||||||
contents += fmt.Sprintf("%s,%s,%s,%s\n", msg.SenderUsername, msg.SenderIp, msg.Timestamp, msg.Content)
|
|
||||||
}
|
|
||||||
os.WriteFile("messages.txt", []byte(contents), os.ModeAppend)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use it
|
|
||||||
func readMessagesFromFile(filepath string) []Message {
|
|
||||||
contents, _ := os.ReadFile(filepath)
|
|
||||||
lines := strings.Split(string(contents), "\n")
|
|
||||||
var messages []Message
|
|
||||||
for _, line := range lines {
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(line, ",")
|
|
||||||
username := parts[0]
|
|
||||||
timestamp := parts[1]
|
|
||||||
content := strings.Join(parts[2:], " ")
|
|
||||||
messages = append(messages, Message{username, "", content, timestamp})
|
|
||||||
}
|
|
||||||
return messages
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Ip string
|
|
||||||
Port int
|
|
||||||
Messages []Message
|
|
||||||
// Add mappings for IPs and usernames
|
|
||||||
Connected map[string]time.Time // Map IP -> Last activity time
|
|
||||||
Usernames map[string]string // Map IP -> Username
|
|
||||||
mu sync.Mutex // For thread safety
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(ip string, port int) *Server {
|
|
||||||
return &Server{
|
|
||||||
Ip: ip,
|
|
||||||
Port: port,
|
|
||||||
Messages: make([]Message, 0),
|
|
||||||
Connected: make(map[string]time.Time),
|
|
||||||
Usernames: make(map[string]string),
|
|
||||||
mu: sync.Mutex{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) AddMessage(username string, userip string, contents string) {
|
|
||||||
message := NewMessage(username, userip, contents)
|
|
||||||
s.Messages = append(s.Messages, *message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) updateActivity(ip string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.Connected[ip] = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) cleanupActivity() {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
for ip, lastActivity := range s.Connected {
|
|
||||||
if time.Since(lastActivity) > 10*time.Second {
|
|
||||||
delete(s.Connected, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handlePing(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
s.updateActivity(clientIP)
|
|
||||||
s.cleanupActivity()
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validUsername(username string) bool {
|
|
||||||
for _, c := range username {
|
|
||||||
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleUsername(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPut {
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
|
|
||||||
var req struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
|
|
||||||
if len(req.Username) > 64 {
|
|
||||||
http.Error(w, fmt.Sprintf(`{"error": "Username too long (must be less than 64 characters)"}`), http.StatusRequestEntityTooLarge)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validUsername(req.Username) {
|
|
||||||
http.Error(w, fmt.Sprintf(`{"error": "Username must only contain alphanumeric characters and/or underscores"}`), http.StatusBadRequest)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, username := range s.Usernames {
|
|
||||||
if username == req.Username {
|
|
||||||
s.mu.Unlock()
|
|
||||||
http.Error(w, fmt.Sprintf(`{"error": "Username already exists"}`), http.StatusConflict)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := s.Usernames[clientIP]; ok {
|
|
||||||
fmt.Println("Name change detected, fixing messages to reflect the change")
|
|
||||||
for i, msg := range s.Messages {
|
|
||||||
if msg.SenderIp == clientIP {
|
|
||||||
s.Messages[i].SenderUsername = req.Username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Usernames[clientIP] = req.Username
|
|
||||||
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "Username registered"})
|
|
||||||
fmt.Println(clientIP, "->", req.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMessageTemplate(file string, body string) string {
|
|
||||||
contents, _ := os.ReadFile(file)
|
|
||||||
return strings.Replace(string(contents), "{{body}}", body, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
|
|
||||||
var body string
|
|
||||||
for _, msg := range s.Messages {
|
|
||||||
body += fmt.Sprintf(`<p><span class="username">%s </span><span class="timestamp">%s</span><br><span class="message">%s</span></p>`,
|
|
||||||
msg.SenderUsername, msg.Timestamp, msg.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(getMessageTemplate("messages.html", body)))
|
|
||||||
|
|
||||||
case http.MethodPut:
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Get client's IP
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
|
|
||||||
// Check if IP is registered
|
|
||||||
s.mu.Lock()
|
|
||||||
username, exists := s.Usernames[clientIP]
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
errorFmt := fmt.Sprintf(`{"error": "IP %s not registered with username"}`, clientIP)
|
|
||||||
http.Error(w, errorFmt, http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
|
|
||||||
http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add message using the stored username and IP
|
|
||||||
s.AddMessage(username, clientIP, msg.Message)
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{
|
|
||||||
"status": "Message received",
|
|
||||||
"from": username,
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleUsernameStatus(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
username, exists := s.Usernames[clientIP]
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"hasUsername": exists,
|
|
||||||
"username": username,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleUsers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
clientIP := getClientIP(r)
|
|
||||||
s.updateActivity(clientIP)
|
|
||||||
s.cleanupActivity()
|
|
||||||
s.mu.Lock()
|
|
||||||
var users []string
|
|
||||||
for ip := range s.Connected {
|
|
||||||
users = append(users, s.Usernames[ip])
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"users": users,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != "/" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
w.Write(readFile("root.html"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFile(filepath string) []byte {
|
|
||||||
contents, _ := os.ReadFile(filepath)
|
|
||||||
return contents
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleJs(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = r
|
|
||||||
w.Header().Set("Content-Type", "application/javascript")
|
|
||||||
w.Write(readFile("root.js"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) handleCss(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = r
|
|
||||||
w.Header().Set("Content-Type", "text/css")
|
|
||||||
w.Write(readFile("root.css"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
|
||||||
handler := http.NewServeMux()
|
|
||||||
handler.HandleFunc("/ping", s.handlePing)
|
|
||||||
handler.HandleFunc("/username", s.handleUsername)
|
|
||||||
handler.HandleFunc("/messages", s.handleMessages)
|
|
||||||
handler.HandleFunc("/username/status", s.handleUsernameStatus)
|
|
||||||
handler.HandleFunc("/users", s.handleUsers)
|
|
||||||
handler.HandleFunc("/", s.handleRoot)
|
|
||||||
handler.HandleFunc("/root.js", s.handleJs)
|
|
||||||
handler.HandleFunc("/root.css", s.handleCss)
|
|
||||||
fmt.Printf("Server starting on %s:%d\n", s.Ip, s.Port)
|
|
||||||
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.Ip, s.Port), GzipMiddleware(handler)); err != nil {
|
|
||||||
fmt.Printf("Server error: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ip := os.Args[1]
|
|
||||||
port, _ := strconv.Atoi(os.Args[2])
|
|
||||||
// ip := "localhost"
|
|
||||||
// port := 8080
|
|
||||||
server := NewServer(ip, port)
|
|
||||||
server.Run()
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
{{body}}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
247
old/root.css
247
old/root.css
@ -1,247 +0,0 @@
|
|||||||
:root {
|
|
||||||
--radchat-color: #40a02b;
|
|
||||||
--main-bg-color: #11111b;
|
|
||||||
--pum-button-inactive-fg: #cdd6f4;
|
|
||||||
--pum-button-inactive-bg: #11111b;
|
|
||||||
--pum-button-active-fg: #89b4fa;
|
|
||||||
--pum-button-active-bg: #1e1e2e;
|
|
||||||
--pum-title-color: #cdd6f4;
|
|
||||||
--pum-bg-color: #1e1e2e;
|
|
||||||
--user-color: #89b4fa;
|
|
||||||
--timestamp-color: #313244;
|
|
||||||
--separator-color: #181825;
|
|
||||||
--message-color: #cdd6f4;
|
|
||||||
--message-bg-color: #1e1e2e;
|
|
||||||
--input-bg-color: #181825;
|
|
||||||
--input-text-color: #cdd6f4;
|
|
||||||
--input-button-inactive-bg: #b4befe;
|
|
||||||
--input-button-inactive-fg: #11111b;
|
|
||||||
--input-button-active-bg: #89b4fa;
|
|
||||||
--input-button-active-fg: #11111b;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0; /* Remove padding from body */
|
|
||||||
background-color: var(--main-bg-color);
|
|
||||||
color: var(--pum-title-color);
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden; /* Prevent body scroll */
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 20px; /* Move padding here */
|
|
||||||
padding-bottom: 80px; /* Space for message input */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-controls {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
display: flex;
|
|
||||||
gap: 20px; /* Space between buttons */
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.header-controls {
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-button, .users-button {
|
|
||||||
top: 20px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: var(--pum-button-inactive-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-button {
|
|
||||||
right: calc(48%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.users-button {
|
|
||||||
right: calc(52%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-icon, .users-icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
fill: var(--pum-button-inactive-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-button:hover, .users-button:hover {
|
|
||||||
background-color: var(--pum-button-active-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-button:hover .settings-icon,
|
|
||||||
.users-button:hover .users-icon {
|
|
||||||
fill: var(--pum-button-active-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#users-list {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-user {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 28px;
|
|
||||||
color: var(--user-color);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.username-section, .users-section {
|
|
||||||
position: fixed;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
top: 70px;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: var(--pum-bg-color);
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
||||||
z-index: 100;
|
|
||||||
display: none;
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.username-section {
|
|
||||||
right: 50%;
|
|
||||||
transform: translateX(10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.users-section {
|
|
||||||
right: 50%;
|
|
||||||
transform: translateX(-10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.users-section h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-item {
|
|
||||||
padding: 8px 0;
|
|
||||||
color: var(--user-color);
|
|
||||||
border-bottom: 1px solid var(--separator-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.messages-section {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin-top: 60px; /* Space for gear icon */
|
|
||||||
margin-bottom: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 0; /* Allow flex-grow to work */
|
|
||||||
}
|
|
||||||
.message {
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
.message .username {
|
|
||||||
color: var(--user-color);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.message .timestamp {
|
|
||||||
color: var(--timestamp-color);
|
|
||||||
font-weight: thin;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message .content {
|
|
||||||
margin-top: 5px;
|
|
||||||
color: var(--message-color);
|
|
||||||
word-wrap: break-word; /* Handle long words */
|
|
||||||
word-break: break-word; /* Break words at arbitrary points if needed */
|
|
||||||
white-space: pre-wrap; /* Preserve whitespace and wraps */
|
|
||||||
max-width: 100%; /* Ensure it doesn't exceed container width */
|
|
||||||
}
|
|
||||||
.message-section {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--message-bg-color);
|
|
||||||
z-index: 100; /* Ensure it stays on top */
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--input-bg-color);
|
|
||||||
color: var(--input-text-color);
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--input-button-inactive-bg);
|
|
||||||
color: var(--input-button-inactive-fg);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: var(--input-button-active-bg);
|
|
||||||
color: var(--input-button-active-fg)
|
|
||||||
}
|
|
||||||
|
|
||||||
button.scroll {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
background-color: var(--input-button-inactive-bg);
|
|
||||||
padding: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.scroll:hover {
|
|
||||||
background-color: var(--input-button-active-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-icon {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
fill: var(--var-input-button-inactive-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-icon:hover {
|
|
||||||
fill: var(--var-input-button-active-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radchat {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--radchat-color);
|
|
||||||
}
|
|
||||||
#status, #username-status {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<meta http-equiv="Cache-Control" content="max-age=86400, must-revalidate">
|
|
||||||
<meta http-equiv="Pragma" content="cache">
|
|
||||||
<meta http-equiv="Expires" content="86400">
|
|
||||||
<title>RadChat</title>
|
|
||||||
<script src="root.js"></script>
|
|
||||||
<link rel="stylesheet" href="root.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="radchat">
|
|
||||||
RadChat
|
|
||||||
</div>
|
|
||||||
<div class="header-controls">
|
|
||||||
<button class="users-button" onclick="toggleUsers()">
|
|
||||||
<svg class="users-icon" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="settings-button" onclick="toggleSettings()">
|
|
||||||
<svg class="settings-icon" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.65.07-.97 0-.32-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="current-user" class="current-user"></div>
|
|
||||||
|
|
||||||
<div class="users-section" id="users-panel">
|
|
||||||
<h3>Online Users</h3>
|
|
||||||
<div id="users-list">
|
|
||||||
<!-- Users will be loaded here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="username-section" id="settings-panel">
|
|
||||||
<h3>Username</h3>
|
|
||||||
<div style="display: flex; gap: 10px;">
|
|
||||||
<input type="text" id="username" placeholder="Enter username">
|
|
||||||
<button onclick="setUsername()">Set</button>
|
|
||||||
</div>
|
|
||||||
<div id="username-status"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="messages-section" id="messages">
|
|
||||||
<!-- Messages will be loaded here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="message-section">
|
|
||||||
<div class="message-container">
|
|
||||||
<input type="text" id="message" placeholder="Type a message">
|
|
||||||
<button onclick="sendMessage()">Send</button>
|
|
||||||
<button id="scroll" class="scroll"><svg class="scroll-icon" id="scroll-icon" viewBox="0 0 24 24">
|
|
||||||
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
|
|
||||||
</svg></button>
|
|
||||||
</div>
|
|
||||||
<div id="status"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
244
old/root.js
244
old/root.js
@ -1,244 +0,0 @@
|
|||||||
function toggleSettings() {
|
|
||||||
const panel = document.getElementById('settings-panel');
|
|
||||||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
|
||||||
if (panel.style.display === 'block') {
|
|
||||||
const username = document.getElementById('username');
|
|
||||||
username.focus();
|
|
||||||
username.selectionStart = username.selectionEnd = username.value.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleUsers() {
|
|
||||||
const panel = document.getElementById('users-panel');
|
|
||||||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', function(event) {
|
|
||||||
const settingsPanel = document.getElementById('settings-panel');
|
|
||||||
const settingsButton = document.querySelector('.settings-button');
|
|
||||||
const usersPanel = document.getElementById('users-panel');
|
|
||||||
const usersButton = document.querySelector('.users-button');
|
|
||||||
|
|
||||||
if (!settingsPanel.contains(event.target) && !settingsButton.contains(event.target)) {
|
|
||||||
settingsPanel.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!usersPanel.contains(event.target) && !usersButton.contains(event.target)) {
|
|
||||||
usersPanel.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('keypress', function(event) {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
const settingsPanel = document.getElementById('settings-panel');
|
|
||||||
const inputPanel = document.getElementById('message');
|
|
||||||
if (settingsPanel.contains(event.target)) {
|
|
||||||
setUsername();
|
|
||||||
}
|
|
||||||
if (inputPanel.contains(event.target)) {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadUsers() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/users');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
const usersList = document.getElementById('users-list');
|
|
||||||
usersList.innerHTML = '';
|
|
||||||
|
|
||||||
data.users.sort().forEach(user => {
|
|
||||||
const userDiv = document.createElement('div');
|
|
||||||
userDiv.className = 'user-item';
|
|
||||||
userDiv.textContent = user;
|
|
||||||
usersList.appendChild(userDiv);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading users:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let lastMessageCount = 0;
|
|
||||||
async function loadMessages() {
|
|
||||||
try {
|
|
||||||
const messagesDiv = document.getElementById('messages');
|
|
||||||
const response = await fetch('/messages');
|
|
||||||
const text = await response.text();
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.innerHTML = text;
|
|
||||||
|
|
||||||
if (messagesDiv.scrollTop != bottom) {
|
|
||||||
// show a button to scroll to the bottom
|
|
||||||
const scrollToBottomButton = document.getElementById('scroll');
|
|
||||||
scrollToBottomButton.style.display = 'block';
|
|
||||||
scrollToBottomButton.onclick = scrollToBottom;
|
|
||||||
} else {
|
|
||||||
// hide the button
|
|
||||||
const scrollToBottomButton = document.getElementById('scroll');
|
|
||||||
scrollToBottomButton.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = tempDiv.getElementsByTagName('p');
|
|
||||||
|
|
||||||
const newMessageCount = messages.length;
|
|
||||||
|
|
||||||
if (newMessageCount > lastMessageCount || lastMessageCount === 0) {
|
|
||||||
messagesDiv.innerHTML = '';
|
|
||||||
Array.from(messages).forEach(msg => {
|
|
||||||
const messageDiv = document.createElement('div');
|
|
||||||
messageDiv.className = 'message';
|
|
||||||
const [username, content] = msg.innerHTML.split('<br>');
|
|
||||||
messageDiv.innerHTML = '<div class="username">' + username + '</div>' +
|
|
||||||
'<div class="content">' + content + '</div>';
|
|
||||||
messagesDiv.appendChild(messageDiv);
|
|
||||||
});
|
|
||||||
scrollToBottom();
|
|
||||||
lastMessageCount = newMessageCount;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading messages:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bottom = 0;
|
|
||||||
|
|
||||||
function scrollToBottom() {
|
|
||||||
const messagesDiv = document.getElementById('messages');
|
|
||||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
||||||
bottom = messagesDiv.scrollTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkUsername() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/username/status');
|
|
||||||
const data = await response.json();
|
|
||||||
if (!data.hasUsername) {
|
|
||||||
document.getElementById('settings-panel').style.display = 'block'
|
|
||||||
const username = document.getElementById('username');
|
|
||||||
username.focus();
|
|
||||||
username.selectionStart = username.selectionEnd = username.value.length
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking username status:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateCurrentUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/username/status');
|
|
||||||
const data = await response.json();
|
|
||||||
const userDiv = document.getElementById('current-user');
|
|
||||||
if (data.hasUsername) {
|
|
||||||
userDiv.textContent = data.username;
|
|
||||||
} else {
|
|
||||||
userDiv.textContent = '';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting username:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setUsername() {
|
|
||||||
const username = document.getElementById('username').value;
|
|
||||||
if (!username) {
|
|
||||||
showUsernameStatus('Please enter a username', 'red');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/username', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ username: username })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
showUsernameStatus('Username set successfully!', 'green');
|
|
||||||
updateCurrentUser();
|
|
||||||
setTimeout(() => {
|
|
||||||
document.getElementById('settings-panel').style.display = 'none';
|
|
||||||
}, 500);
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
showUsernameStatus(data.error || 'Failed to set username', 'red');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showUsernameStatus('Error connecting to server', 'red');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendMessage() {
|
|
||||||
const message = document.getElementById('message').value;
|
|
||||||
if (!message) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/messages', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ message: message })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
document.getElementById('message').value = '';
|
|
||||||
// showStatus('Message sent!', '#64b5f6');
|
|
||||||
loadMessages();
|
|
||||||
} else {
|
|
||||||
showStatus(data.error || 'Failed to send message', 'red');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showStatus('Error connecting to server', 'red');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showStatus(message, color) {
|
|
||||||
const status = document.getElementById('status');
|
|
||||||
status.textContent = message;
|
|
||||||
status.style.color = color;
|
|
||||||
setTimeout(() => {
|
|
||||||
status.textContent = '';
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUsernameStatus(message, color) {
|
|
||||||
const status = document.getElementById('username-status');
|
|
||||||
status.textContent = message;
|
|
||||||
status.style.color = color;
|
|
||||||
setTimeout(() => {
|
|
||||||
status.textContent = '';
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pingCheck() {
|
|
||||||
try {
|
|
||||||
await fetch('/ping', {method: 'POST'});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ping failed:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initialize() {
|
|
||||||
await loadMessages();
|
|
||||||
document.getElementById('users-panel').style.display = 'none';
|
|
||||||
document.getElementById('settings-panel').style.display = 'none';
|
|
||||||
checkUsername();
|
|
||||||
updateCurrentUser();
|
|
||||||
setInterval(loadMessages, 1000);
|
|
||||||
setInterval(loadUsers, 1000);
|
|
||||||
setInterval(pingCheck, 3000);
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize();
|
|
Loading…
x
Reference in New Issue
Block a user