updated blog

This commit is contained in:
partisan 2024-08-16 14:46:31 +02:00
parent 980008d8e5
commit b890637222
9 changed files with 229 additions and 171 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
elements.html
elements.html
notified_entries.json
data/

View file

@ -10,7 +10,26 @@ Spitfire Browser is a fast, secure, and elegant web browser built on Firefox. Th
- [ ] Add working downloads
- [ ] Add blog/updates
- [X] Add blog/updates
- [ ] Add config file
### Blog entries should be fromated this way:
```md
[HEADER]
t: TITLE
d: SHORT-DESC
p: 2024-08-16 15:04
a: AUTHOR
[END]
# lorem ipsum
Vestibulum fermentum tortor id mi. Nullam at arcu a est sollicitudin euismod. Nullam faucibus mi quis velit. Mauris dictum facilisis augue. Nullam sapien sem, ornare ac, nonummy non, lobortis a enim. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Etiam commodo dui eget wisi. Mauris dictum facilisis augue. Etiam posuere lacus quis dolor. In sem justo, commodo ut, suscipit at, pharetra vitae, orci.
Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui eget wisi. Donec vitae arcu. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam neque. Suspendisse sagittis ultrices augue. Suspendisse nisl. Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Phasellus rhoncus. Maecenas libero.
```
### Based on:

View file

@ -7,8 +7,6 @@ import (
"net/http"
)
const discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL" // Replace with your Discord webhook URL
type DiscordWebhookPayload struct {
Content string `json:"content"`
}

313
main.go
View file

@ -1,6 +1,7 @@
package main
import (
"bufio"
"flag"
"fmt"
"html/template"
@ -21,12 +22,15 @@ import (
)
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
dataDir = "./data"
templateDir = "./templates"
staticDir = "./static"
notifiedFilePath = "./notified_entries.json"
defaultPort = 8080
pageSize = 5 // Number of blog entries per page
botTokenEnv = "YOUR_TELEGRAM_BOT_TOKEN" // Replace with your bot's token or set via environment variable
YOUR_TELEGRAM_CHAT_ID = 0
discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL"
)
type Blog struct {
@ -35,26 +39,32 @@ type Blog struct {
}
type BlogEntry struct {
Content string
Date time.Time
Number int
Title string
Description string
Author string
Content string
Date time.Time
Number int
Notified bool // To track if the notification was sent
}
type PageData struct {
Title string
Description string
BlogLinks []string
Content template.HTML
PrevLink string
NextLink string
Title string
Date string
Desc string
Author 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
blogs []Blog
bot *tgbotapi.BotAPI
port int
creationTimes = make(map[string]time.Time)
creationTimesM sync.Mutex
notifiedEntries = make(map[int]bool)
)
func init() {
@ -66,6 +76,9 @@ func main() {
// Parse the flags
flag.Parse()
// Load the notified entries from the file
loadNotifiedEntries()
// Retrieve the Telegram bot token from the environment variable
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
if botToken == "" {
@ -100,43 +113,17 @@ func main() {
return
}
if r.URL.Path == "/download" {
renderTemplate(w, "download.html", nil)
return
}
if r.URL.Path == "/download-linux" {
renderTemplate(w, "download-linux.html", nil)
return
}
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)
renderBlogEntry(w, r, blogName, entryNumber)
return
}
}
@ -178,7 +165,7 @@ func renderIndex(w http.ResponseWriter) {
renderTemplate(w, "index.html", nil)
}
func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, entryNumber int) {
var blog Blog
for _, b := range blogs {
if b.Name == blogName {
@ -191,11 +178,20 @@ func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
var prevLink, nextLink string
for i, e := range blog.Entries {
if e.Number == entryNumber {
// Check if the entry date is in the future
if time.Now().Before(e.Date) {
http.NotFound(w, r) // If the post date is in the future, do not show the entry
return
}
entry = e
if i > 0 {
// Check if the previous entry is visible
if i > 0 && !time.Now().Before(blog.Entries[i-1].Date) {
prevLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i-1].Number)
}
if i < len(blog.Entries)-1 {
// Check if the next entry is visible
if i < len(blog.Entries)-1 && !time.Now().Before(blog.Entries[i+1].Date) {
nextLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i+1].Number)
}
break
@ -203,69 +199,25 @@ func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
}
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,
Title: entry.Title,
Date: entry.Date.Format("2006-01-02 15:04"),
Desc: entry.Description,
Author: entry.Author,
Content: template.HTML(htmlContent),
PrevLink: prevLink,
NextLink: nextLink,
}
renderTemplate(w, "news.html", pageData)
// Handle notifications if they haven't been sent yet
if !entry.Notified {
sendNotifications(entry)
entry.Notified = true
saveNotifiedEntry(entry.Number)
}
}
func getBlogs(dir string) ([]Blog, error) {
@ -289,14 +241,6 @@ func getBlogs(dir string) ([]Blog, error) {
return blogs, nil
}
func getBlogLinks() []string {
var links []string
for _, blog := range blogs {
links = append(links, blog.Name)
}
return links
}
func getBlogEntries(dir string) (Blog, error) {
var entries []BlogEntry
@ -307,28 +251,12 @@ func getBlogEntries(dir string) (Blog, error) {
for _, file := range files {
if filepath.Ext(file.Name()) == ".md" {
content, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
entry, err := parseMarkdownFile(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))
}
}
@ -344,6 +272,74 @@ func getBlogEntries(dir string) (Blog, error) {
return blog, nil
}
func parseMarkdownFile(path string) (BlogEntry, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return BlogEntry{}, err
}
scanner := bufio.NewScanner(strings.NewReader(string(content)))
var title, description, postTime, author string
var articleContent strings.Builder
var insideHeader bool
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "[HEADER]" {
insideHeader = true
continue
} else if line == "[END]" {
insideHeader = false
continue
}
if insideHeader {
if strings.HasPrefix(line, "t:") {
title = strings.TrimSpace(line[2:])
} else if strings.HasPrefix(line, "d:") {
description = strings.TrimSpace(line[2:])
} else if strings.HasPrefix(line, "p:") {
postTime = strings.TrimSpace(line[2:])
} else if strings.HasPrefix(line, "a:") {
author = strings.TrimSpace(line[2:])
}
} else {
articleContent.WriteString(line + "\n")
}
}
date := time.Now()
if postTime != "" {
date, err = time.Parse("2006-01-02 15:04", postTime)
if err != nil {
log.Printf("Error parsing date in file %s: %v", path, err)
}
}
number, err := strconv.Atoi(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
if err != nil {
log.Printf("Error extracting number from file %s: %v", path, err)
return BlogEntry{}, err
}
// Check if the entry has already been notified
notified := notifiedEntries[number]
entry := BlogEntry{
Title: title,
Description: description,
Author: author,
Content: articleContent.String(),
Date: date,
Number: number,
Notified: notified,
}
return entry, nil
}
func watchForChanges(dir string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
@ -412,27 +408,15 @@ func handleFileChange(path string, isNew bool) {
func updateBlogEntries(blogName, path string) {
for i, blog := range blogs {
if blog.Name == blogName {
content, err := ioutil.ReadFile(path)
entry, err := parseMarkdownFile(path)
if err != nil {
log.Printf("Error reading file: %v", err)
log.Printf("Error parsing markdown 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 {
if e.Number == entry.Number {
blogs[i].Entries[j] = entry
updated = true
break
@ -447,37 +431,25 @@ func updateBlogEntries(blogName, path string) {
return blogs[i].Entries[a].Number > blogs[i].Entries[b].Number
})
log.Printf("Updated blog %s with entry %d", blogName, number)
log.Printf("Updated blog %s with entry %d", blogName, entry.Number)
return
}
}
// If blog not found, create new one
content, err := ioutil.ReadFile(path)
entry, err := parseMarkdownFile(path)
if err != nil {
log.Printf("Error reading file: %v", err)
log.Printf("Error parsing markdown 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)
log.Printf("Created new blog %s with entry %d", blogName, entry.Number)
}
func getFileModTime(path string) (time.Time, error) {
@ -487,3 +459,16 @@ func getFileModTime(path string) (time.Time, error) {
}
return info.ModTime(), nil
}
func sendNotifications(entry BlogEntry) {
message := fmt.Sprintf("New blog post published!\nTitle: %s\nDescription: %s\nAuthor: %s\nDate: %s",
entry.Title, entry.Description, entry.Author, entry.Date.Format("2006-01-02 15:04"))
// Send notification to Telegram
sendTelegramNotification(message)
// Send notification to Discord
sendDiscordNotification(message)
log.Printf("Sent notifications for entry %d: %s", entry.Number, entry.Title)
}

2
run.sh
View file

@ -13,4 +13,4 @@ while [[ "$#" -gt 0 ]]; do
done
# Run the Go application with the parsed flags
go run discord.go rss.go telegram.go main.go -p=$PORT
go run discord.go rss.go telegram.go save.go main.go -p=$PORT

36
save.go Normal file
View file

@ -0,0 +1,36 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
)
func loadNotifiedEntries() {
data, err := ioutil.ReadFile(notifiedFilePath)
if err != nil {
if os.IsNotExist(err) {
return // No file, no entries notified yet
}
log.Fatalf("Error reading notified entries file: %v", err)
}
err = json.Unmarshal(data, &notifiedEntries)
if err != nil {
log.Fatalf("Error parsing notified entries file: %v", err)
}
}
func saveNotifiedEntry(entryNumber int) {
notifiedEntries[entryNumber] = true
data, err := json.Marshal(notifiedEntries)
if err != nil {
log.Fatalf("Error serializing notified entries: %v", err)
}
err = ioutil.WriteFile(notifiedFilePath, data, 0644)
if err != nil {
log.Fatalf("Error writing notified entries file: %v", err)
}
}

View file

@ -47,7 +47,7 @@
.gallery-item:hover::before {
opacity: 1;
}
/*
@keyframes pulse {
0%, 100% {
transform: scale(1);
@ -61,4 +61,4 @@
.gallery-item {
animation: pulse 5s infinite;
}
} */

View file

@ -24,3 +24,19 @@ func startTelegramBot() {
}
}
}
func sendTelegramNotification(message string) {
if bot == nil {
log.Println("Telegram bot is not initialized")
return
}
// Replace with the actual chat ID you want to send messages to
chatID := int64(YOUR_TELEGRAM_CHAT_ID)
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
log.Printf("Error sending Telegram notification: %v", err)
}
}

View file

@ -55,6 +55,8 @@
<div class="center-text">
<header class="major">
<h2>{{.Title}}</h2>
<p>{{.Author}}</p>
<p>{{.Date}}</p>
<p>{{.Content}}</p>
</header>
<footer class="major">