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