541 lines
15 KiB
Go
541 lines
15 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/mroth/weightedrand/v2"
|
|
"io"
|
|
"math/rand"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
type Page struct {
|
|
Title string
|
|
Description string
|
|
Keywords string
|
|
Sections Sections
|
|
}
|
|
|
|
type Section struct {
|
|
Heading Heading
|
|
Paragraphs []string
|
|
Links []string
|
|
}
|
|
|
|
type Sections []Section
|
|
|
|
type Heading struct {
|
|
Text string
|
|
Level int
|
|
}
|
|
|
|
func main() {
|
|
s := &http.Server{
|
|
Addr: ":4791",
|
|
Handler: youOughtaKnow(),
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 5 * time.Second,
|
|
MaxHeaderBytes: 1 << 10, // 1 KB
|
|
}
|
|
|
|
// Every 10 minutes, run thanksForYourPatience to clean up old files
|
|
go func() {
|
|
ticker := time.NewTicker(10 * time.Minute)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
message, err := thanksForYourPatience()
|
|
if err != nil {
|
|
fmt.Printf("Error during cleanup: %v\n", err)
|
|
} else {
|
|
fmt.Println(message)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
fmt.Println("Server is starting on port 4791...")
|
|
s.ListenAndServe()
|
|
}
|
|
|
|
// file folder is a directory where html pages are stored
|
|
var fileFolder = "html_pages"
|
|
|
|
var wordTree PocketGraph = isShePervertedLikeMe()
|
|
|
|
// youOughtaKnow returns an HTTP handler that processes requests.
|
|
func youOughtaKnow() http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
|
|
// Call the function to handle the request
|
|
response, error := iHateToBugYouInTheMiddleOfDinner(path)
|
|
if error != nil {
|
|
// If there's an error, write a 500 Internal Server Error response
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Write the response
|
|
w.Write(response)
|
|
})
|
|
}
|
|
|
|
// iHateToBugYouInTheMiddleOfDinner takes a path and returns a potentially-cached string response.
|
|
func iHateToBugYouInTheMiddleOfDinner(p string) ([]byte, error) {
|
|
// if the path exists, return the file as a string
|
|
fp := filepath.Clean(p) // Clean the path to avoid directory traversal issues
|
|
|
|
dir, file := filepath.Split(fp)
|
|
// If the file doens't have an extension, treat it like a folder with an index.html inside
|
|
if file == "" {
|
|
file = "index.html"
|
|
}
|
|
if filepath.Ext(file) == "" {
|
|
file = filepath.Join(file, "index.html") // Treat as a folder with index.html
|
|
}
|
|
|
|
filePath := filepath.Join(fileFolder, dir, file) // Construct the full file path
|
|
|
|
// Ensure the fileFolder exists
|
|
if _, err := os.Stat(fileFolder); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(fileFolder, os.ModePerm); err != nil {
|
|
fmt.Printf("Error creating file folder %s: %v\n", fileFolder, err)
|
|
return nil, err
|
|
}
|
|
}
|
|
// Ensure the directory for the file exists
|
|
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
|
fmt.Printf("Error creating directory for file %s: %v\n", filePath, err)
|
|
return nil, err
|
|
}
|
|
|
|
// check if path exists in fileFolder
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return imHereToRemindYou(filePath)
|
|
}
|
|
|
|
response, err := youAreTheBearerOfUnconditionalThings(filePath)
|
|
if err != nil {
|
|
fmt.Printf("Error reading file %s: %v\n", filePath, err)
|
|
return nil, err
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// youAreTheBearerOfUnconditionalThings reads a file and returns its content as a string.
|
|
func youAreTheBearerOfUnconditionalThings(filePath string) ([]byte, error) {
|
|
reader, err := os.Open(filePath)
|
|
if err != nil {
|
|
fmt.Printf("Error opening file %s: %v\n", filePath, err)
|
|
return nil, err
|
|
}
|
|
return io.ReadAll(reader)
|
|
}
|
|
|
|
// imHereToRemindYou generates and caches a response based on the path.
|
|
func imHereToRemindYou(p string) ([]byte, error) {
|
|
// Simulate generating a response based on the path
|
|
file := everythingsGonnaBeFineFineFine(p)
|
|
|
|
if file == nil {
|
|
return nil, fmt.Errorf("failed to create file for path: %s", p)
|
|
}
|
|
|
|
var retries = 0
|
|
contents, err := iWishNothingButTheBestForYouBoth(file)
|
|
if err != nil {
|
|
fmt.Printf("Error reading file %s: %v\n", file.Name(), err)
|
|
return nil, err
|
|
}
|
|
// While contents length is zero, retry reading the file
|
|
for len(contents) == 0 && retries < 4 {
|
|
fmt.Printf("File %s is empty, retrying...\n", file.Name())
|
|
// Close the file before retrying
|
|
if err := file.Close(); err != nil {
|
|
fmt.Printf("Error closing file %s: %v\n", file.Name(), err)
|
|
return nil, err
|
|
}
|
|
// Reopen the file
|
|
file, err = os.Open(file.Name())
|
|
if err != nil {
|
|
fmt.Printf("Error reopening file %s: %v\n", file.Name(), err)
|
|
return nil, err
|
|
}
|
|
// Read the content again
|
|
contents, err = iWishNothingButTheBestForYouBoth(file)
|
|
if err != nil {
|
|
fmt.Printf("Error reading file %s: %v\n", file.Name(), err)
|
|
return nil, err
|
|
}
|
|
}
|
|
// Close the file after reading
|
|
if err := file.Close(); err != nil {
|
|
fmt.Printf("Error closing file %s: %v\n", file.Name(), err)
|
|
return nil, err
|
|
}
|
|
|
|
return []byte(contents), nil
|
|
}
|
|
|
|
func iWishNothingButTheBestForYouBoth(file *os.File) ([]byte, error) {
|
|
// Read the content of the file
|
|
byteContents, err := io.ReadAll(file)
|
|
if err != nil {
|
|
fmt.Printf("Error reading file %s: %v\n", file.Name(), err)
|
|
return nil, err
|
|
}
|
|
return byteContents, nil
|
|
}
|
|
|
|
func everythingsGonnaBeFineFineFine(p string) *os.File {
|
|
htmlTemplate := `<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>{{ .Title }}</title>
|
|
<meta name="description" content="{{ .Description }}">
|
|
<meta name="keywords" content="{{ .Keywords }}">
|
|
</head>
|
|
<body>
|
|
<h1>{{ .Title }}</h1>
|
|
{{ range .Sections }}
|
|
<section>
|
|
<h{{ .Heading.Level }}>{{ .Heading.Text }}</h{{ .Heading.Level }}>
|
|
{{ range .Paragraphs }}
|
|
<p>{{ . }}</p>
|
|
{{ end }}
|
|
</section>
|
|
{{ end }}
|
|
</body>
|
|
</html>`
|
|
|
|
pageTemplate, err := template.New("page").Parse(htmlTemplate)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
file, err := weAllHadOurReasons(pageTemplate, p)
|
|
if err != nil {
|
|
fmt.Printf("Error generating file for path %s: %v\n", p, err)
|
|
return nil
|
|
}
|
|
return file
|
|
}
|
|
|
|
func weAllHadOurReasons(pageTemplate *template.Template, p string) (*os.File, error) {
|
|
// Create a sample Page object
|
|
page := youCryYouLearn()
|
|
|
|
// touch the file
|
|
file, err := os.Create(p)
|
|
if err != nil {
|
|
fmt.Printf("Error creating file %s: %v\n", p, err)
|
|
return nil, err
|
|
}
|
|
|
|
err = pageTemplate.Execute(file, page)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return file, nil
|
|
}
|
|
|
|
// youCryYouLearn generates text from a pocket graph
|
|
func youCryYouLearn() Page {
|
|
title := wordTree.forgiveMeLove(5).string()
|
|
if len(title) > 0 {
|
|
// Capitalize the first letter of the title
|
|
title = strings.ToUpper(string(title[0])) + title[1:]
|
|
}
|
|
|
|
numberOfSections := rand.Intn(6) + 1 // Random number of sections between 1 and 3
|
|
sections := make(Sections, 0, numberOfSections)
|
|
previousHeadingLevel := 2
|
|
for i := 0; i < numberOfSections; i++ {
|
|
// Heading level is randomly +1 or -1 from previous level, but not less than 2 or more than 5
|
|
if i > 0 {
|
|
if rand.Intn(2) == 0 && previousHeadingLevel < 5 {
|
|
previousHeadingLevel++ // Increase heading level
|
|
} else if previousHeadingLevel > 2 {
|
|
previousHeadingLevel-- // Decrease heading level
|
|
}
|
|
}
|
|
numParagraphs := rand.Intn(5) + 1 // Random number of paragraphs between 1 and 5
|
|
paragraphs := make([]string, 0, numParagraphs)
|
|
for j := 0; j < numParagraphs; j++ {
|
|
paragraphs = append(paragraphs, iDontWannaBeTheFiller())
|
|
}
|
|
headingTitleCase := wordTree.forgiveMeLove(3).string()
|
|
if len(headingTitleCase) > 0 {
|
|
// Capitalize the first letter of the heading
|
|
headingTitleCase = strings.ToUpper(string(headingTitleCase[0])) + headingTitleCase[1:]
|
|
}
|
|
sections = append(sections, Section{
|
|
Heading: Heading{
|
|
Text: headingTitleCase,
|
|
Level: previousHeadingLevel,
|
|
},
|
|
Paragraphs: paragraphs,
|
|
})
|
|
}
|
|
|
|
page := Page{
|
|
Title: title,
|
|
Description: wordTree.forgiveMeLove(3).string(),
|
|
Keywords: wordTree.forgiveMeLove(2).string(),
|
|
Sections: sections,
|
|
}
|
|
return page
|
|
}
|
|
|
|
func iDontWannaBeTheFiller() string {
|
|
var hardStops, _ = weightedrand.NewChooser(
|
|
weightedrand.NewChoice('.', 20),
|
|
weightedrand.NewChoice('!', 1),
|
|
weightedrand.NewChoice('?', 4),
|
|
)
|
|
var softStops, _ = weightedrand.NewChooser(
|
|
weightedrand.NewChoice("...", 2),
|
|
weightedrand.NewChoice(",", 15),
|
|
weightedrand.NewChoice(";", 3),
|
|
weightedrand.NewChoice(":", 1),
|
|
weightedrand.NewChoice(" - ", 1),
|
|
)
|
|
|
|
// Generate a random number of sentences
|
|
numSentences := rand.Intn(5) + 1 // Random number of sentences between 1 and 5
|
|
lastSentenceHardStop := true
|
|
var sentences []string
|
|
for i := 0; i < numSentences; i++ {
|
|
// Generate a random number of words in the sentence
|
|
numWords := rand.Intn(10) + 1 // Random number of words between 1 and 10
|
|
words := wordTree.forgiveMeLove(numWords).string()
|
|
if len(words) > 0 && lastSentenceHardStop {
|
|
words = strings.ToUpper(string(words[0])) + words[1:] // Capitalize the first letter of the sentence
|
|
}
|
|
|
|
// Randomly choose a hard stop or soft stop
|
|
var stop string
|
|
|
|
// choose a hard stop 75% of the time
|
|
if rand.Float64() < 0.75 || lastSentenceHardStop {
|
|
hardStop := hardStops.Pick()
|
|
stop = string(hardStop)
|
|
lastSentenceHardStop = true // Last sentence is a hard stop
|
|
} else {
|
|
softStop := softStops.Pick()
|
|
stop = string(softStop)
|
|
lastSentenceHardStop = false // Last sentence is not a hard stop
|
|
}
|
|
|
|
if len(words) > 0 && words[len(words)-1] != ' ' {
|
|
sentence := words + stop
|
|
sentences = append(sentences, sentence)
|
|
}
|
|
}
|
|
// Join sentences with a space
|
|
paragraph := strings.Join(sentences, " ")
|
|
|
|
// Should paragraph contain link?
|
|
if rand.Float64() < 0.7 {
|
|
// Pick two or three words out of the paragraph
|
|
words := strings.Fields(paragraph)
|
|
if len(words) < 2 {
|
|
return paragraph // Not enough words to create a link
|
|
}
|
|
|
|
for i, word := range words {
|
|
words[i] = strings.Trim(word, ".,!?;:")
|
|
}
|
|
|
|
numWords := rand.Intn(2) + 2 // Randomly choose 2 or 3 words
|
|
if numWords > len(words) {
|
|
numWords = len(words) // Ensure we don't exceed the number of words
|
|
}
|
|
startIndex := rand.Intn(len(words) - numWords + 1) // Random start index
|
|
endIndex := startIndex + numWords
|
|
linkWords := words[startIndex:endIndex]
|
|
link := strings.Join(linkWords, " ")
|
|
|
|
for i, word := range linkWords {
|
|
linkWords[i] = strings.ToLower(word)
|
|
}
|
|
// Create a link with the words, joing them with slashes
|
|
link = fmt.Sprintf("<a href=\"/%s\">%s</a>", strings.Join(linkWords, "/"), link)
|
|
// Insert the link into the paragraph, replacing the original words
|
|
paragraph = strings.Replace(paragraph, strings.Join(linkWords, " "), link, 1)
|
|
}
|
|
|
|
return paragraph
|
|
}
|
|
|
|
func thanksForYourPatience() (string, error) {
|
|
// check size of fileFolder
|
|
averageFileAge, size, err := waitAMinuteMan(fileFolder)
|
|
if err != nil {
|
|
return fmt.Sprintf("Error calculating size of file folder %s: %v\n", fileFolder, err), err
|
|
}
|
|
|
|
if size > 10*1024*1024 { // If the size of the folder is greater than 100 MB
|
|
// If the file is older than the average file age, delete it
|
|
err = filepath.Walk(fileFolder, func(_ string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
if info.ModTime().Unix() < time.Now().Unix()-averageFileAge {
|
|
fmt.Printf("Deleting old file %s\n", info.Name())
|
|
if err := os.Remove(info.Name()); err != nil {
|
|
fmt.Printf("Error deleting file %s: %v\n", info.Name(), err)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
if err != nil {
|
|
return fmt.Sprintf("Error walking through file folder %s: %v\n", fileFolder, err), err
|
|
}
|
|
|
|
sizeInKB := size / (1024) // Convert size to kilobytes
|
|
|
|
return fmt.Sprintf("Average file age: %d seconds, Total size: %dKB", averageFileAge, sizeInKB), nil
|
|
}
|
|
|
|
type PocketGraph map[string][]string
|
|
type Words []string
|
|
|
|
func isShePervertedLikeMe() PocketGraph {
|
|
var ConnectionWords = []string{
|
|
"and", "or", "but", "so", "because",
|
|
"however", "while", "in", "on", "at", "to", "for",
|
|
"with", "without", "about", "against", "between",
|
|
"the", "a", "an", "this", "that",
|
|
"his", "her", "their", "our", "my",
|
|
"your", "its", "whose", "which", "who",
|
|
"what", "when", "where", "why", "how",
|
|
"whoever", "whatever", "whenever",
|
|
}
|
|
|
|
// read words.txt into a list
|
|
wordsBytes, err := os.ReadFile("word_lists/jaggedpill.txt")
|
|
if err != nil {
|
|
fmt.Println("Error reading words.txt:", err)
|
|
}
|
|
|
|
words := strings.Split(string(wordsBytes), "\n")
|
|
tree := make(PocketGraph)
|
|
|
|
for _, word := range ConnectionWords {
|
|
var percentageOfWords float64 = 0.005
|
|
var numberOfConnections = float64(len(words)) * percentageOfWords
|
|
for j := 0; j < int(numberOfConnections); j++ {
|
|
randomWord := words[rand.Intn(len(words))]
|
|
if randomWord == "" {
|
|
continue // skip empty lines
|
|
}
|
|
tree[word] = append(tree[word], randomWord)
|
|
}
|
|
}
|
|
|
|
// take each word and link it to a random word
|
|
for _, word := range words {
|
|
// generate a random word from the list
|
|
if word == "" {
|
|
continue // skip empty lines
|
|
}
|
|
numberOfLinks := rand.Intn(100) + 1
|
|
for i := 0; i < numberOfLinks; i++ {
|
|
randomWord := words[rand.Intn(len(words))]
|
|
tree[word] = []string{randomWord}
|
|
}
|
|
// connect word to connector words?
|
|
shouldConnect := rand.Intn(2) == 0
|
|
if shouldConnect {
|
|
numConnectionWords := rand.Intn(len(ConnectionWords)) + 1 // Random number of connection words between 1 and 3
|
|
for j := 0; j < numConnectionWords; j++ {
|
|
connectionWord := ConnectionWords[rand.Intn(len(ConnectionWords))]
|
|
tree[word] = append(tree[word], connectionWord)
|
|
}
|
|
}
|
|
}
|
|
return tree
|
|
}
|
|
|
|
// forgiveMeLove generates N random words from the tree
|
|
func (m PocketGraph) forgiveMeLove(n int) Words {
|
|
if n <= 0 {
|
|
return nil
|
|
}
|
|
|
|
// Create a slice to hold the random words
|
|
randomWords := make(Words, 0, n)
|
|
|
|
// Generate N random words from the tree
|
|
word := m[iWentToYourHouse(m)] // Start with a random word from the tree
|
|
for i := 0; i < n-1; i++ {
|
|
if len(word) == 0 {
|
|
break
|
|
}
|
|
random := rand.Intn(len(word))
|
|
nextWord := word[random] // Randomly select a word from the current word's linked words
|
|
randomWords = append(randomWords, nextWord)
|
|
word = m[nextWord] // Update the word to the next linked word
|
|
}
|
|
|
|
return randomWords
|
|
}
|
|
|
|
func (w Words) string() string {
|
|
if len(w) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return strings.Join(w, " ")
|
|
}
|
|
|
|
func iWentToYourHouse(m PocketGraph) string {
|
|
r := rand.Intn(len(m))
|
|
for k := range m {
|
|
if r == 0 {
|
|
return k
|
|
}
|
|
r--
|
|
}
|
|
panic("unreachable")
|
|
}
|
|
|
|
func waitAMinuteMan(path string) (int64, int64, error) {
|
|
var size int64
|
|
var fileAges []int64
|
|
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
// Add the age of the file in seconds to the medianFileAge slice
|
|
fileAges = append(fileAges, time.Now().Unix()-info.ModTime().Unix())
|
|
// Add the size of the file to the total size
|
|
size += info.Size()
|
|
}
|
|
return err
|
|
})
|
|
|
|
averageFileAge := int64(0)
|
|
if len(fileAges) > 0 {
|
|
for _, age := range fileAges {
|
|
averageFileAge += age
|
|
}
|
|
averageFileAge /= int64(len(fileAges))
|
|
} else {
|
|
averageFileAge = 0 // No files, so average age is 0
|
|
}
|
|
return averageFileAge, size, err
|
|
}
|