updated blog
This commit is contained in:
parent
980008d8e5
commit
b890637222
9 changed files with 229 additions and 171 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
elements.html
|
||||
notified_entries.json
|
||||
data/
|
||||
|
|
21
README.md
21
README.md
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
275
main.go
|
@ -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
2
run.sh
|
@ -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
36
save.go
Normal 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, ¬ifiedEntries)
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
} */
|
16
telegram.go
16
telegram.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in a new issue