Website/main.go

490 lines
11 KiB
Go
Raw Normal View History

2024-01-09 14:08:56 +00:00
package main
import (
2024-08-14 10:43:07 +00:00
"flag"
2024-01-09 14:08:56 +00:00
"fmt"
2024-08-14 10:43:07 +00:00
"html/template"
"io/ioutil"
"log"
2024-01-09 14:08:56 +00:00
"net/http"
2024-08-14 10:43:07 +00:00
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/russross/blackfriday/v2"
)
const (
dataDir = "./data"
templateDir = "./templates"
staticDir = "./static"
defaultPort = 8080
botTokenEnv = "YOUR_TELEGRAM_BOT_TOKEN" // Replace with your bot's token or set via environment variable
pageSize = 5 // Number of blog entries per page
)
type Blog struct {
Name string
Entries []BlogEntry
}
type BlogEntry struct {
Content string
Date time.Time
Number int
}
type PageData struct {
Title string
Description string
BlogLinks []string
Content template.HTML
PrevLink string
NextLink string
}
var (
blogs []Blog
bot *tgbotapi.BotAPI
port int
creationTimes = make(map[string]time.Time)
creationTimesM sync.Mutex
2024-01-09 14:08:56 +00:00
)
2024-08-14 10:43:07 +00:00
func init() {
flag.IntVar(&port, "p", defaultPort, "Specify the port to run the server on")
flag.IntVar(&port, "port", defaultPort, "Specify the port to run the server on")
}
2024-01-09 14:08:56 +00:00
func main() {
2024-08-14 12:18:58 +00:00
// Parse the flags
2024-08-14 10:43:07 +00:00
flag.Parse()
2024-08-14 12:18:58 +00:00
// Retrieve the Telegram bot token from the environment variable
2024-08-14 10:43:07 +00:00
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
if botToken == "" {
botToken = botTokenEnv
}
2024-08-14 12:18:58 +00:00
// Initialize the Telegram bot
2024-08-14 10:43:07 +00:00
var err error
bot, err = tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Printf("Warning: Error creating Telegram bot: %v", err)
} else {
go startTelegramBot()
}
2024-08-14 12:18:58 +00:00
// Retrieve blog entries from the data directory
2024-08-14 10:43:07 +00:00
blogs, err = getBlogs(dataDir)
if err != nil {
log.Fatalf("Error getting blogs: %v", err)
}
2024-08-14 12:18:58 +00:00
// Start watching for changes in the data directory
2024-08-14 10:43:07 +00:00
go watchForChanges(dataDir)
2024-08-14 12:18:58 +00:00
// Serve static files (CSS, JS, etc.) from the /static directory
2024-08-14 10:43:07 +00:00
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
2024-08-14 12:18:58 +00:00
// Define route handlers
2024-08-14 10:43:07 +00:00
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
2024-08-14 10:55:34 +00:00
renderIndex(w)
2024-08-14 10:43:07 +00:00
return
}
2024-08-14 11:55:29 +00:00
if r.URL.Path == "/download" {
renderTemplate(w, "download.html", nil)
return
}
if r.URL.Path == "/download-linux" {
renderTemplate(w, "download-linux.html", nil)
return
}
2024-08-14 10:43:07 +00:00
pathParts := strings.Split(r.URL.Path, "/")
if len(pathParts) < 2 {
http.NotFound(w, r)
return
}
siteURL := fmt.Sprintf("http://localhost:%d", port) // Define siteURL here
if pathParts[1] == "rss" {
generateRSSFeed(w, blogs, siteURL)
return
}
if pathParts[1] == "all" {
page, err := strconv.Atoi(r.URL.Query().Get("page"))
if err != nil || page < 1 {
page = 1
}
renderAllBlogs(w, r, page)
return
}
if len(pathParts) == 3 {
blogName := pathParts[1]
entryNumber, err := strconv.Atoi(pathParts[2])
if err == nil {
renderBlogEntry(w, blogName, entryNumber)
return
}
}
blogName := pathParts[1]
for _, blog := range blogs {
if blog.Name == blogName {
http.Redirect(w, r, fmt.Sprintf("/%s/%d", blogName, blog.Entries[0].Number), http.StatusFound)
return
}
}
http.NotFound(w, r)
})
2024-08-14 12:18:58 +00:00
// Start the HTTP server on the specified port
2024-08-14 10:43:07 +00:00
serverURL := fmt.Sprintf("http://localhost:%d", port)
log.Printf("Starting server on %s", serverURL)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
2024-08-14 11:55:29 +00:00
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
tmplPath := filepath.Join(templateDir, tmpl)
t, err := template.ParseFiles(tmplPath)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Error parsing template %s: %v", tmpl, err)
return
}
err = t.Execute(w, data)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Error executing template %s: %v", tmpl, err)
}
}
func renderIndex(w http.ResponseWriter) {
renderTemplate(w, "index.html", nil)
}
func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
var blog Blog
for _, b := range blogs {
if b.Name == blogName {
blog = b
break
}
}
var entry BlogEntry
var prevLink, nextLink string
for i, e := range blog.Entries {
if e.Number == entryNumber {
entry = e
if i > 0 {
prevLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i-1].Number)
}
if i < len(blog.Entries)-1 {
nextLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i+1].Number)
}
break
}
}
htmlContent := blackfriday.Run([]byte(entry.Content))
content := fmt.Sprintf("<div class=\"blog-entry\"><div>%s</div></div>", htmlContent)
pageData := PageData{
Title: "",
Description: "",
BlogLinks: getBlogLinks(),
Content: template.HTML(content),
PrevLink: prevLink,
NextLink: nextLink,
}
// Use the existing renderTemplate to render with a specific PageData type.
renderTemplate(w, "news.html", pageData)
}
func renderAllBlogs(w http.ResponseWriter, r *http.Request, page int) {
var allEntries []BlogEntry
for _, blog := range blogs {
allEntries = append(allEntries, blog.Entries...)
}
sort.Slice(allEntries, func(i, j int) bool {
return allEntries[i].Date.After(allEntries[j].Date)
})
totalEntries := len(allEntries)
startIndex := (page - 1) * pageSize
endIndex := startIndex + pageSize
if startIndex >= totalEntries {
http.NotFound(w, r)
return
}
if endIndex > totalEntries {
endIndex = totalEntries
}
pagedEntries := allEntries[startIndex:endIndex]
var content strings.Builder
for _, entry := range pagedEntries {
htmlContent := blackfriday.Run([]byte(entry.Content))
content.WriteString(fmt.Sprintf("<div class=\"blog-entry\"><div>%s</div></div>", htmlContent))
}
var prevLink, nextLink string
if startIndex > 0 {
prevLink = fmt.Sprintf("/all?page=%d", page-1)
}
if endIndex < totalEntries {
nextLink = fmt.Sprintf("/all?page=%d", page+1)
}
pageData := PageData{
Title: "All Blogs",
Description: "Combined blog entries from all blogs.",
BlogLinks: getBlogLinks(),
Content: template.HTML(content.String()),
PrevLink: prevLink,
NextLink: nextLink,
}
renderTemplate(w, "news.html", pageData)
}
2024-08-14 10:43:07 +00:00
func getBlogs(dir string) ([]Blog, error) {
var blogs []Blog
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
for _, file := range files {
if file.IsDir() {
blog, err := getBlogEntries(filepath.Join(dir, file.Name()))
if err != nil {
return nil, err
}
blogs = append(blogs, blog)
}
}
return blogs, nil
}
2024-08-14 11:55:29 +00:00
func getBlogLinks() []string {
var links []string
for _, blog := range blogs {
links = append(links, blog.Name)
}
return links
}
2024-08-14 10:43:07 +00:00
func getBlogEntries(dir string) (Blog, error) {
var entries []BlogEntry
files, err := ioutil.ReadDir(dir)
if err != nil {
return Blog{}, err
}
for _, file := range files {
if filepath.Ext(file.Name()) == ".md" {
content, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
if err != nil {
return Blog{}, err
}
date, err := getFileModTime(filepath.Join(dir, file.Name()))
if err != nil {
return Blog{}, err
}
number, err := strconv.Atoi(strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())))
if err != nil {
return Blog{}, err
}
entry := BlogEntry{
Content: string(content),
Date: date,
Number: number,
}
entries = append(entries, entry)
sendDiscordNotification(fmt.Sprintf("New blog entry: %d", number))
}
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Number > entries[j].Number
})
blog := Blog{
Name: filepath.Base(dir),
Entries: entries,
}
return blog, nil
}
func watchForChanges(dir string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Error creating file watcher: %v", err)
}
defer watcher.Close()
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Create == fsnotify.Create {
handleFileChange(event.Name, true)
} else if event.Op&fsnotify.Write == fsnotify.Write {
handleFileChange(event.Name, false)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Printf("File watcher error: %v", err)
}
}
}()
err = watcher.Add(dir)
if err != nil {
log.Fatalf("Error adding directory to watcher: %v", err)
}
// Add subdirectories
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() && path != dir {
return watcher.Add(path)
}
return nil
})
if err != nil {
log.Fatalf("Error adding subdirectories to watcher: %v", err)
}
// Block forever
select {}
}
func handleFileChange(path string, isNew bool) {
if filepath.Ext(path) == ".md" {
creationTimesM.Lock()
defer creationTimesM.Unlock()
if isNew {
creationTimes[path] = time.Now()
}
dir := filepath.Dir(path)
blogName := filepath.Base(dir)
updateBlogEntries(blogName, path)
}
}
func updateBlogEntries(blogName, path string) {
for i, blog := range blogs {
if blog.Name == blogName {
content, err := ioutil.ReadFile(path)
if err != nil {
log.Printf("Error reading file: %v", err)
return
}
number, err := strconv.Atoi(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
if err != nil {
log.Printf("Error extracting number from file: %v", err)
return
}
entry := BlogEntry{
Content: string(content),
Date: creationTimes[path],
Number: number,
}
updated := false
for j, e := range blogs[i].Entries {
if e.Number == number {
blogs[i].Entries[j] = entry
updated = true
break
}
}
if !updated {
blogs[i].Entries = append(blogs[i].Entries, entry)
}
sort.Slice(blogs[i].Entries, func(a, b int) bool {
return blogs[i].Entries[a].Number > blogs[i].Entries[b].Number
})
log.Printf("Updated blog %s with entry %d", blogName, number)
return
}
}
// If blog not found, create new one
content, err := ioutil.ReadFile(path)
if err != nil {
log.Printf("Error reading file: %v", err)
return
}
number, err := strconv.Atoi(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
if err != nil {
log.Printf("Error extracting number from file: %v", err)
return
}
entry := BlogEntry{
Content: string(content),
Date: creationTimes[path],
Number: number,
}
newBlog := Blog{
Name: blogName,
Entries: []BlogEntry{entry},
}
blogs = append(blogs, newBlog)
log.Printf("Created new blog %s with entry %d", blogName, number)
}
func getFileModTime(path string) (time.Time, error) {
info, err := os.Stat(path)
2024-01-09 14:08:56 +00:00
if err != nil {
2024-08-14 10:43:07 +00:00
return time.Time{}, err
2024-01-09 14:08:56 +00:00
}
2024-08-14 10:43:07 +00:00
return info.ModTime(), nil
2024-01-09 14:08:56 +00:00
}