Compare commits
No commits in common. "b8906372224e04c204739a0e87be6abd74056466" and "ef91b8a8ad7e475405f248c84d2dbe72b41f94c1" have entirely different histories.
b890637222
...
ef91b8a8ad
13 changed files with 185 additions and 229 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,3 +1 @@
|
||||||
elements.html
|
elements.html
|
||||||
notified_entries.json
|
|
||||||
data/
|
|
21
README.md
21
README.md
|
@ -10,26 +10,7 @@ Spitfire Browser is a fast, secure, and elegant web browser built on Firefox. Th
|
||||||
|
|
||||||
- [ ] Add working downloads
|
- [ ] Add working downloads
|
||||||
|
|
||||||
- [X] Add blog/updates
|
- [ ] 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:
|
### Based on:
|
||||||
|
|
||||||
|
|
5
data/news/1.md
Normal file
5
data/news/1.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# 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.
|
3
data/news/2.md
Normal file
3
data/news/2.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# 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.
|
3
data/news/3.md
Normal file
3
data/news/3.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# 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.
|
3
data/news/4.md
Normal file
3
data/news/4.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# 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.
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL" // Replace with your Discord webhook URL
|
||||||
|
|
||||||
type DiscordWebhookPayload struct {
|
type DiscordWebhookPayload struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
313
main.go
313
main.go
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -22,15 +21,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dataDir = "./data"
|
dataDir = "./data"
|
||||||
templateDir = "./templates"
|
templateDir = "./templates"
|
||||||
staticDir = "./static"
|
staticDir = "./static"
|
||||||
notifiedFilePath = "./notified_entries.json"
|
defaultPort = 8080
|
||||||
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
|
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 {
|
type Blog struct {
|
||||||
|
@ -39,32 +35,26 @@ type Blog struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlogEntry struct {
|
type BlogEntry struct {
|
||||||
Title string
|
Content string
|
||||||
Description string
|
Date time.Time
|
||||||
Author string
|
Number int
|
||||||
Content string
|
|
||||||
Date time.Time
|
|
||||||
Number int
|
|
||||||
Notified bool // To track if the notification was sent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageData struct {
|
type PageData struct {
|
||||||
Title string
|
Title string
|
||||||
Date string
|
Description string
|
||||||
Desc string
|
BlogLinks []string
|
||||||
Author string
|
Content template.HTML
|
||||||
Content template.HTML
|
PrevLink string
|
||||||
PrevLink string
|
NextLink string
|
||||||
NextLink string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
blogs []Blog
|
blogs []Blog
|
||||||
bot *tgbotapi.BotAPI
|
bot *tgbotapi.BotAPI
|
||||||
port int
|
port int
|
||||||
creationTimes = make(map[string]time.Time)
|
creationTimes = make(map[string]time.Time)
|
||||||
creationTimesM sync.Mutex
|
creationTimesM sync.Mutex
|
||||||
notifiedEntries = make(map[int]bool)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -76,9 +66,6 @@ func main() {
|
||||||
// Parse the flags
|
// Parse the flags
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Load the notified entries from the file
|
|
||||||
loadNotifiedEntries()
|
|
||||||
|
|
||||||
// Retrieve the Telegram bot token from the environment variable
|
// Retrieve the Telegram bot token from the environment variable
|
||||||
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
|
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
|
||||||
if botToken == "" {
|
if botToken == "" {
|
||||||
|
@ -113,17 +100,43 @@ func main() {
|
||||||
return
|
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, "/")
|
pathParts := strings.Split(r.URL.Path, "/")
|
||||||
if len(pathParts) < 2 {
|
if len(pathParts) < 2 {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
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 {
|
if len(pathParts) == 3 {
|
||||||
blogName := pathParts[1]
|
blogName := pathParts[1]
|
||||||
entryNumber, err := strconv.Atoi(pathParts[2])
|
entryNumber, err := strconv.Atoi(pathParts[2])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
renderBlogEntry(w, r, blogName, entryNumber)
|
renderBlogEntry(w, blogName, entryNumber)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +178,7 @@ func renderIndex(w http.ResponseWriter) {
|
||||||
renderTemplate(w, "index.html", nil)
|
renderTemplate(w, "index.html", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, entryNumber int) {
|
func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
|
||||||
var blog Blog
|
var blog Blog
|
||||||
for _, b := range blogs {
|
for _, b := range blogs {
|
||||||
if b.Name == blogName {
|
if b.Name == blogName {
|
||||||
|
@ -178,20 +191,11 @@ func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, en
|
||||||
var prevLink, nextLink string
|
var prevLink, nextLink string
|
||||||
for i, e := range blog.Entries {
|
for i, e := range blog.Entries {
|
||||||
if e.Number == entryNumber {
|
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
|
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)
|
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)
|
nextLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i+1].Number)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -199,25 +203,69 @@ func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, en
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlContent := blackfriday.Run([]byte(entry.Content))
|
htmlContent := blackfriday.Run([]byte(entry.Content))
|
||||||
|
content := fmt.Sprintf("<div class=\"blog-entry\"><div>%s</div></div>", htmlContent)
|
||||||
|
|
||||||
pageData := PageData{
|
pageData := PageData{
|
||||||
Title: entry.Title,
|
Title: "",
|
||||||
Date: entry.Date.Format("2006-01-02 15:04"),
|
Description: "",
|
||||||
Desc: entry.Description,
|
BlogLinks: getBlogLinks(),
|
||||||
Author: entry.Author,
|
Content: template.HTML(content),
|
||||||
Content: template.HTML(htmlContent),
|
PrevLink: prevLink,
|
||||||
PrevLink: prevLink,
|
NextLink: nextLink,
|
||||||
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)
|
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) {
|
func getBlogs(dir string) ([]Blog, error) {
|
||||||
|
@ -241,6 +289,14 @@ func getBlogs(dir string) ([]Blog, error) {
|
||||||
return blogs, nil
|
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) {
|
func getBlogEntries(dir string) (Blog, error) {
|
||||||
var entries []BlogEntry
|
var entries []BlogEntry
|
||||||
|
|
||||||
|
@ -251,12 +307,28 @@ func getBlogEntries(dir string) (Blog, error) {
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if filepath.Ext(file.Name()) == ".md" {
|
if filepath.Ext(file.Name()) == ".md" {
|
||||||
entry, err := parseMarkdownFile(filepath.Join(dir, file.Name()))
|
content, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Blog{}, err
|
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)
|
entries = append(entries, entry)
|
||||||
|
sendDiscordNotification(fmt.Sprintf("New blog entry: %d", number))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,74 +344,6 @@ func getBlogEntries(dir string) (Blog, error) {
|
||||||
return blog, nil
|
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) {
|
func watchForChanges(dir string) {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -408,15 +412,27 @@ func handleFileChange(path string, isNew bool) {
|
||||||
func updateBlogEntries(blogName, path string) {
|
func updateBlogEntries(blogName, path string) {
|
||||||
for i, blog := range blogs {
|
for i, blog := range blogs {
|
||||||
if blog.Name == blogName {
|
if blog.Name == blogName {
|
||||||
entry, err := parseMarkdownFile(path)
|
content, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing markdown file: %v", err)
|
log.Printf("Error reading file: %v", err)
|
||||||
return
|
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
|
updated := false
|
||||||
for j, e := range blogs[i].Entries {
|
for j, e := range blogs[i].Entries {
|
||||||
if e.Number == entry.Number {
|
if e.Number == number {
|
||||||
blogs[i].Entries[j] = entry
|
blogs[i].Entries[j] = entry
|
||||||
updated = true
|
updated = true
|
||||||
break
|
break
|
||||||
|
@ -431,25 +447,37 @@ func updateBlogEntries(blogName, path string) {
|
||||||
return blogs[i].Entries[a].Number > blogs[i].Entries[b].Number
|
return blogs[i].Entries[a].Number > blogs[i].Entries[b].Number
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("Updated blog %s with entry %d", blogName, entry.Number)
|
log.Printf("Updated blog %s with entry %d", blogName, number)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If blog not found, create new one
|
// If blog not found, create new one
|
||||||
entry, err := parseMarkdownFile(path)
|
content, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing markdown file: %v", err)
|
log.Printf("Error reading file: %v", err)
|
||||||
return
|
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{
|
newBlog := Blog{
|
||||||
Name: blogName,
|
Name: blogName,
|
||||||
Entries: []BlogEntry{entry},
|
Entries: []BlogEntry{entry},
|
||||||
}
|
}
|
||||||
|
|
||||||
blogs = append(blogs, newBlog)
|
blogs = append(blogs, newBlog)
|
||||||
log.Printf("Created new blog %s with entry %d", blogName, entry.Number)
|
log.Printf("Created new blog %s with entry %d", blogName, number)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileModTime(path string) (time.Time, error) {
|
func getFileModTime(path string) (time.Time, error) {
|
||||||
|
@ -459,16 +487,3 @@ func getFileModTime(path string) (time.Time, error) {
|
||||||
}
|
}
|
||||||
return info.ModTime(), nil
|
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
|
done
|
||||||
|
|
||||||
# Run the Go application with the parsed flags
|
# Run the Go application with the parsed flags
|
||||||
go run discord.go rss.go telegram.go save.go main.go -p=$PORT
|
go run discord.go rss.go telegram.go main.go -p=$PORT
|
||||||
|
|
36
save.go
36
save.go
|
@ -1,36 +0,0 @@
|
||||||
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 {
|
.gallery-item:hover::before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% {
|
0%, 100% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
@ -61,4 +61,4 @@
|
||||||
|
|
||||||
.gallery-item {
|
.gallery-item {
|
||||||
animation: pulse 5s infinite;
|
animation: pulse 5s infinite;
|
||||||
} */
|
}
|
16
telegram.go
16
telegram.go
|
@ -24,19 +24,3 @@ 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,8 +55,6 @@
|
||||||
<div class="center-text">
|
<div class="center-text">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<h2>{{.Title}}</h2>
|
<h2>{{.Title}}</h2>
|
||||||
<p>{{.Author}}</p>
|
|
||||||
<p>{{.Date}}</p>
|
|
||||||
<p>{{.Content}}</p>
|
<p>{{.Content}}</p>
|
||||||
</header>
|
</header>
|
||||||
<footer class="major">
|
<footer class="major">
|
||||||
|
|
Loading…
Reference in a new issue