Compare commits

..

No commits in common. "0165c7e6f7124d6bc98171eb0f9b4bb32ccdd0d1" and "73e9cf108e202e4c29ff0f3cddc7c7d0a0b3dac2" have entirely different histories.

78 changed files with 46 additions and 788 deletions

View file

@ -4,13 +4,10 @@ Spitfire Browser is a fast, secure, and elegant web browser built on Firefox. Th
## TO-DO: ## TO-DO:
- [ ] Add screenshots [ ] Add screenshots
[ ] Add search-engine test
- [ ] Add search-engine test [ ] Add working downloads
[ ] Add blog/updates
- [ ] Add working downloads
- [ ] Add blog/updates
### Based on: ### Based on:

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

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

@ -1,34 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
const discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL" // Replace with your Discord webhook URL
type DiscordWebhookPayload struct {
Content string `json:"content"`
}
func sendDiscordNotification(content string) {
payload := DiscordWebhookPayload{Content: content}
payloadBytes, err := json.Marshal(payload)
if err != nil {
log.Printf("Error marshalling Discord webhook payload: %v", err)
return
}
resp, err := http.Post(discordWebhookURL, "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
log.Printf("Error sending Discord webhook: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
log.Printf("Unexpected status code from Discord webhook: %d", resp.StatusCode)
}
}

View file

@ -100,19 +100,19 @@ sudo apk add --allow-untrusted spitfire-browser.apk</code></pre>
<ul class="icons"> <ul class="icons">
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/regular/heart.svg" alt="LibrePay"> <img src="images/icons/regular/heart.svg" alt="LibrePay">
<span class="label">LibrePay</span> <span class="label">LibrePay</span>
</a> </a>
</li> </li>
<li> <li>
<a href="https://weforgecode.xyz/Spitfire/" class="icon alt"> <a href="https://weforgecode.xyz/Spitfire/" class="icon alt">
<img src="static/images/icons/brands/git-alt.svg" alt="Forgejo"> <img src="images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span> <span class="label">Forgejo</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/brands/youtube.svg" alt="YouTube"> <img src="images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span> <span class="label">YouTube</span>
</a> </a>
</li> </li>

View file

@ -88,23 +88,23 @@
<h2>Stable Releases</h2> <h2>Stable Releases</h2>
<div class="download-buttons"> <div class="download-buttons">
<a href="#" class="download-button button"> <a href="#" class="download-button button">
<img src="static/images/icons/brands/windows.svg" alt="Windows"> <img src="images/icons/brands/windows.svg" alt="Windows">
<span>Windows</span> <span>Windows</span>
</a> </a>
<a href="#" class="download-button button"> <a href="#" class="download-button button">
<img src="static/images/icons/brands/apple.svg" alt="MacOS"> <img src="images/icons/brands/apple.svg" alt="MacOS">
<span>MacOS</span> <span>MacOS</span>
</a> </a>
<a href="download-linux.html" class="download-button button"> <a href="download-linux.html" class="download-button button">
<img src="static/images/icons/brands/linux.svg" alt="Linux"> <img src="images/icons/brands/linux.svg" alt="Linux">
<span>Linux</span> <span>Linux</span>
<!-- </a> <!-- </a>
<a href="#" class="download-button button"> <a href="#" class="download-button button">
<img src="static/images/icons/brands/linux.svg" alt="Linux (Appimage)"> <img src="images/icons/brands/linux.svg" alt="Linux (Appimage)">
<span>Linux (Appimage)</span> <span>Linux (Appimage)</span>
</a> --> </a> -->
<a href="#" class="download-button button"> <a href="#" class="download-button button">
<img src="static/images/icons/brands/chrome.svg" alt="ChromeOS"> <img src="images/icons/brands/chrome.svg" alt="ChromeOS">
<span>ChromeOS</span> <span>ChromeOS</span>
</a> </a>
</div> </div>
@ -115,11 +115,11 @@
<h2>Nightly Releases</h2> <h2>Nightly Releases</h2>
<div class="download-buttons"> <div class="download-buttons">
<a href="#" class="download-button button"> <a href="#" class="download-button button">
<img src="static/images/icons/brands/windows.svg" alt="Windows Nightly"> <img src="images/icons/brands/windows.svg" alt="Windows Nightly">
<span>Windows</span> <span>Windows</span>
</a> </a>
<a href="#" class="download-button button"> <a href="#" class="download-button button">
<img src="static/images/icons/brands/linux.svg" alt="Linux (Flatpak) Nightly"> <img src="images/icons/brands/linux.svg" alt="Linux (Flatpak) Nightly">
<span>Linux (Flatpak)</span> <span>Linux (Flatpak)</span>
</a> </a>
</div> </div>
@ -133,19 +133,19 @@
<ul class="icons"> <ul class="icons">
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/regular/heart.svg" alt="LibrePay"> <img src="images/icons/regular/heart.svg" alt="LibrePay">
<span class="label">LibrePay</span> <span class="label">LibrePay</span>
</a> </a>
</li> </li>
<li> <li>
<a href="https://weforgecode.xyz/Spitfire/" class="icon alt"> <a href="https://weforgecode.xyz/Spitfire/" class="icon alt">
<img src="static/images/icons/brands/git-alt.svg" alt="Forgejo"> <img src="images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span> <span class="label">Forgejo</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/brands/youtube.svg" alt="YouTube"> <img src="images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span> <span class="label">YouTube</span>
</a> </a>
</li> </li>

13
go.mod
View file

@ -1,13 +0,0 @@
module my-web
go 1.18
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
github.com/gorilla/feeds v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
golang.org/x/sys v0.4.0 // indirect
)

14
go.sum
View file

@ -1,14 +0,0 @@
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

View file

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 915 B

View file

Before

Width:  |  Height:  |  Size: 1,013 B

After

Width:  |  Height:  |  Size: 1,013 B

View file

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -16,10 +16,10 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="static/css/main.css" /> <link rel="stylesheet" href="assets/css/main.css" />
<link rel="stylesheet" href="static/css/stars.css" /> <link rel="stylesheet" href="assets/css/stars.css" />
<link rel="stylesheet" href="static/css/fancy-gallery.css" /> <link rel="stylesheet" href="assets/css/fancy-gallery.css" />
<noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript> <noscript><link rel="stylesheet" href="assets/css/noscript.css" /></noscript>
<style> <style>
/* Ensure the wrapper content remains above the background */ /* Ensure the wrapper content remains above the background */
#wrapper { #wrapper {
@ -64,7 +64,7 @@
<!-- Header --> <!-- Header -->
<header id="header" class="alt"> <header id="header" class="alt">
<span class="logo"><img src="static/images/logo.svg" alt="Spitfire Logo" /></span> <span class="logo"><img src="images/logo.svg" alt="Spitfire Logo" /></span>
<h1>Spitfire Browser</h1> <h1>Spitfire Browser</h1>
<p>Fast. Secure. Elegant.</p> <p>Fast. Secure. Elegant.</p>
<a href="download.html" class="button">Download Now</a> <a href="download.html" class="button">Download Now</a>
@ -91,8 +91,8 @@
<p>Spitfire Browser is your gateway to a fast, secure, and elegant browsing experience. Built on Firefox, Spitfire includes essential features like ad blocking, enhanced security, and anonymous browsing with Warp search engine.</p> <p>Spitfire Browser is your gateway to a fast, secure, and elegant browsing experience. Built on Firefox, Spitfire includes essential features like ad blocking, enhanced security, and anonymous browsing with Warp search engine.</p>
<div class="box alt"> <div class="box alt">
<div class="fancy-gallery"> <div class="fancy-gallery">
<div class="gallery-item"><img src="static/images/screenshots/1.png" alt="Screenshot 1" /></div> <div class="gallery-item"><img src="images/screenshots/1.png" alt="Screenshot 1" /></div>
<div class="gallery-item"><img src="static/images/screenshots/1.png" alt="Screenshot 2" /></div> <div class="gallery-item"><img src="images/screenshots/1.png" alt="Screenshot 2" /></div>
</div> </div>
</div> </div>
<ul class="actions"> <ul class="actions">
@ -148,31 +148,31 @@
<ul class="statistics"> <ul class="statistics">
<li class="style1"> <li class="style1">
<a class="icon icon-security"> <a class="icon icon-security">
<img src="static/images/icons/solid/lock.svg" alt="Lock"> <img src="images/icons/solid/lock.svg" alt="Lock">
</a> </a>
<strong>0</strong> Telemetry <strong>0</strong> Telemetry
</li> </li>
<li class="style2"> <li class="style2">
<a class="icon icon-security"> <a class="icon icon-security">
<img src="static/images/icons/regular/eye-slash.svg" alt="Eye-slash"> <img src="images/icons/regular/eye-slash.svg" alt="Eye-slash">
</a> </a>
<strong>100%</strong> Privacy <strong>100%</strong> Privacy
</li> </li>
<li class="style3"> <li class="style3">
<a class="icon icon-security"> <a class="icon icon-security">
<img src="static/images/icons/solid/arrows-rotate.svg" alt="arrows-rotate"> <img src="images/icons/solid/arrows-rotate.svg" alt="arrows-rotate">
</a> </a>
<strong>Auto</strong> Updates <strong>Auto</strong> Updates
</li> </li>
<li class="style4"> <li class="style4">
<a class="icon icon-security"> <a class="icon icon-security">
<img src="static/images/icons/brands/firefox-browser.svg" alt="Firefox"> <img src="images/icons/brands/firefox-browser.svg" alt="Firefox">
</a> </a>
<strong>Based</strong>on Firefox <strong>Based</strong>on Firefox
</li> </li>
<li class="style5"> <li class="style5">
<a class="icon icon-security"> <a class="icon icon-security">
<img src="static/images/icons/solid/code.svg" alt="code"> <img src="images/icons/solid/code.svg" alt="code">
</a> </a>
<strong>Open</strong> Source <strong>Open</strong> Source
</li> </li>
@ -207,19 +207,19 @@
<ul class="icons"> <ul class="icons">
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/regular/heart.svg" alt="LibrePay"> <img src="images/icons/regular/heart.svg" alt="LibrePay">
<span class="label">LibrePay</span> <span class="label">LibrePay</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/brands/git-alt.svg" alt="Forgejo"> <img src="images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span> <span class="label">Forgejo</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" class="icon alt"> <a href="#" class="icon alt">
<img src="static/images/icons/brands/youtube.svg" alt="YouTube"> <img src="images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span> <span class="label">YouTube</span>
</a> </a>
</li> </li>
@ -233,13 +233,13 @@
</div> </div>
<!-- Scripts --> <!-- Scripts -->
<script src="static/js/jquery.min.js" defer></script> <script src="assets/js/jquery.min.js" defer></script>
<script src="static/js/jquery.scrollex.min.js" defer></script> <script src="assets/js/jquery.scrollex.min.js" defer></script>
<script src="static/js/jquery.scrolly.min.js" defer></script> <script src="assets/js/jquery.scrolly.min.js" defer></script>
<script src="static/js/browser.min.js" defer></script> <script src="assets/js/browser.min.js" defer></script>
<script src="static/js/breakpoints.min.js" defer></script> <script src="assets/js/breakpoints.min.js" defer></script>
<script src="static/js/util.js" defer></script> <script src="assets/js/util.js" defer></script>
<script src="static/js/main.js" defer></script> <script src="assets/js/main.js" defer></script>
</body> </body>
</html> </html>

474
main.go
View file

@ -1,479 +1,19 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"html/template"
"io/ioutil"
"log"
"net/http" "net/http"
"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
)
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")
}
func main() { func main() {
flag.Parse() // Define the directory where your HTML and CSS files are located
http.Handle("/", http.FileServer(http.Dir(".")))
botToken := os.Getenv("TELEGRAM_BOT_TOKEN") // Start the web server on specfied port
if botToken == "" { port := 10369
botToken = botTokenEnv fmt.Printf("Server is running on http://localhost:%d\n", port)
} err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
var err error
bot, err = tgbotapi.NewBotAPI(botToken)
if err != nil { if err != nil {
log.Printf("Warning: Error creating Telegram bot: %v", err) fmt.Println("Error:", err)
} else {
go startTelegramBot()
}
blogs, err = getBlogs(dataDir)
if err != nil {
log.Fatalf("Error getting blogs: %v", err)
}
go watchForChanges(dataDir)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
renderIndex(w)
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)
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)
})
serverURL := fmt.Sprintf("http://localhost:%d", port)
log.Printf("Starting server on %s", serverURL)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
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
}
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 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,
}
renderTemplate(w, pageData)
}
func getBlogLinks() []string {
var links []string
for _, blog := range blogs {
links = append(links, blog.Name)
}
return links
}
func renderTemplate(w http.ResponseWriter, pageData PageData) {
tmpl, err := template.ParseFiles(filepath.Join(templateDir, "news.html"))
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Error parsing template: %v", err)
return
}
err = tmpl.Execute(w, pageData)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Error executing template: %v", err)
}
}
func renderIndex(w http.ResponseWriter) {
tmpl, err := template.ParseFiles(filepath.Join(templateDir, "index.html"))
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Error parsing template: %v", err)
return
}
err = tmpl.Execute(w, nil)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Error executing template: %v", err)
}
}
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)
}
renderTemplate(w, PageData{
Title: "All Blogs",
Description: "Combined blog entries from all blogs.",
BlogLinks: getBlogLinks(),
Content: template.HTML(content.String()),
PrevLink: prevLink,
NextLink: nextLink,
})
}
func getFileModTime(path string) (time.Time, error) {
info, err := os.Stat(path)
if err != nil {
return time.Time{}, err
}
return info.ModTime(), nil
}

67
rss.go
View file

@ -1,67 +0,0 @@
package main
import (
"fmt"
"net/http"
"time"
"github.com/gorilla/feeds"
)
func generateRSSFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
feed := &feeds.Feed{
Title: "My Blog",
Link: &feeds.Link{Href: siteURL},
Description: "A blog about various topics.",
Author: &feeds.Author{Name: "Your Name", Email: "your-email@example.com"},
Created: time.Now(),
}
for _, blog := range blogs {
for _, entry := range blog.Entries {
feed.Items = append(feed.Items, &feeds.Item{
Title: fmt.Sprintf("Entry %d", entry.Number), // Use entry number as title
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)},
Description: entry.Content,
Created: entry.Date,
})
}
}
rss, err := feed.ToRss()
if err != nil {
http.Error(w, "Error generating RSS feed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/rss+xml")
w.Write([]byte(rss))
}
func generateBlogRSSFeed(w http.ResponseWriter, blog Blog, siteURL string) {
feed := &feeds.Feed{
Title: blog.Name,
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", siteURL, blog.Name)},
Description: blog.Name,
Author: &feeds.Author{Name: "Your Name", Email: "your-email@example.com"},
Created: time.Now(),
}
for _, entry := range blog.Entries {
feed.Items = append(feed.Items, &feeds.Item{
Title: fmt.Sprintf("Entry %d", entry.Number), // Use entry number as title
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)},
Description: entry.Content,
Created: entry.Date,
})
}
rss, err := feed.ToRss()
if err != nil {
http.Error(w, "Error generating RSS feed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/rss+xml")
w.Write([]byte(rss))
}

3
run.sh
View file

@ -1,3 +0,0 @@
go run discord.go rss.go telegram.go main.go

View file

@ -1,26 +0,0 @@
package main
import (
"log"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func startTelegramBot() {
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message == nil {
continue
}
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
_, err := bot.Send(msg)
if err != nil {
log.Printf("Error sending message: %v", err)
}
}
}

View file

@ -1,108 +0,0 @@
<!DOCTYPE HTML>
<!--
Stellar by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
<head>
<link rel="icon" type="image/png" href="favicon.png">
<title>{{.Title}}</title>
<meta content="🌐 {{.Title}}" property="og:title" />
<meta content="Privacy respecting user friendly web browser." property="og:description" />
<meta content="https://spitfirebrowser.com/" property="og:url" />
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="../assets/css/main.css" />
<link rel="stylesheet" href="../assets/css/stars.css" />
<noscript><link rel="stylesheet" href="../assets/css/noscript.css" /></noscript>
<style>
/* Ensure the wrapper content remains above the background */
#wrapper {
position: relative;
z-index: 1;
}
</style>
</head>
<body class="is-preload">
<!-- Star Background Divs -->
<div id="star-background">
<div id="stars"></div>
<div id="stars2"></div>
<div id="stars3"></div>
</div>
<!-- Wrapper -->
<div id="wrapper">
<!-- Header -->
<header id="header">
<h1>{{.Title}}</h1>
</header>
<!-- Main -->
<div id="main">
<!-- Stable Release Section -->
<section class="download-section">
<div class="center-text">
<div class="blog-entries">
{{.Content}}
</div>
<div class="pagination">
{{if .PrevLink}}
<a href="{{.PrevLink}}" class="prev">Previous</a>
{{end}}
{{if .NextLink}}
<a href="{{.NextLink}}" class="next">Next</a>
{{end}}
</div>
</div>
</section>
</div>
<!-- Footer -->
<footer id="footer">
<section>
<h2>Support me</h2>
<ul class="icons">
<li>
<a href="#" class="icon alt">
<img src="static/images/icons/regular/heart.svg" alt="LibrePay">
<span class="label">LibrePay</span>
</a>
</li>
<li>
<a href="https://weforgecode.xyz/Spitfire/" class="icon alt">
<img src="static/images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span>
</a>
</li>
<li>
<a href="#" class="icon alt">
<img src="static/images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span>
</a>
</li>
</ul>
</section>
<section>
<p class="copyright">&copy; Spitfire Browser. Design based on <a href="https://html5up.net">HTML5 UP</a>.</p>
</section>
</footer>
</div>
<!-- Scripts -->
<script src="assets/js/jquery.min.js" defer></script>
<script src="assets/js/jquery.scrollex.min.js" defer></script>
<script src="assets/js/jquery.scrolly.min.js" defer></script>
<script src="assets/js/browser.min.js" defer></script>
<script src="assets/js/breakpoints.min.js" defer></script>
<script src="assets/js/util.js" defer></script>
<script src="assets/js/main.js" defer></script>
</body>
</html>