Compare commits

..

2 commits

Author SHA1 Message Date
partisan
b890637222 updated blog 2024-08-16 14:46:31 +02:00
partisan
980008d8e5 Adde blog update 2024-08-16 14:46:02 +02:00
13 changed files with 229 additions and 185 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
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

@ -1,5 +0,0 @@
# 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.

View file

@ -1,3 +0,0 @@
# lorem ipsum
Fusce nibh. Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. Aliquam in lorem sit amet leo accumsan lacinia. Fusce wisi. Curabitur sagittis hendrerit ante. Mauris elementum mauris vitae tortor. Nulla accumsan, elit sit amet varius semper, nulla mauris mollis quam, tempor suscipit diam nulla vel leo. Phasellus rhoncus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. Aenean placerat. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Cras elementum. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Maecenas lorem. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.

View file

@ -1,3 +0,0 @@
# lorem ipsum
Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Nulla turpis magna, cursus sit amet, suscipit a, interdum id, felis. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Nullam faucibus mi quis velit. Aliquam erat volutpat. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis risus. In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Nam sed tellus id magna elementum tincidunt. Donec vitae arcu. Etiam bibendum elit eget erat.

View file

@ -1,3 +0,0 @@
# lorem ipsum
Mauris dictum facilisis augue. Nullam at arcu a est sollicitudin euismod. Praesent dapibus. Suspendisse sagittis ultrices augue. Integer imperdiet lectus quis justo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. Pellentesque sapien. Fusce dui leo, imperdiet in, aliquam sit amet, feugiat eu, orci. Maecenas ipsum velit, consectetuer eu lobortis ut, dictum at dui. Etiam dictum tincidunt diam. Vestibulum fermentum tortor id mi. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit. Pellentesque pretium lectus id turpis. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Maecenas aliquet accumsan leo. Morbi leo mi, nonummy eget tristique non, rhoncus non leo. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

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"`
}

275
main.go
View file

@ -1,6 +1,7 @@
package main
import (
"bufio"
"flag"
"fmt"
"html/template"
@ -24,9 +25,12 @@ const (
dataDir = "./data"
templateDir = "./templates"
staticDir = "./static"
notifiedFilePath = "./notified_entries.json"
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
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,15 +39,20 @@ type Blog struct {
}
type BlogEntry struct {
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
Date string
Desc string
Author string
Content template.HTML
PrevLink string
NextLink string
@ -55,6 +64,7 @@ var (
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()),
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">