Compare commits
10 Commits
16a65df664
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5470ba1298 | ||
|
|
13fae1c23f | ||
|
|
21719a6cf7 | ||
| addaade269 | |||
|
|
6ee6c9c8d9 | ||
|
|
a0133e0981 | ||
| 70c668bdf1 | |||
|
|
68c89de1a4 | ||
|
|
0b7de76874 | ||
|
|
797e115191 |
@@ -13,7 +13,7 @@ go install gitea.elara.ws/Hazel/transfem-startpage
|
||||
Then you can run the program `transfem-startpage`
|
||||
|
||||
```sh
|
||||
transfem-startpage
|
||||
transfem-startpage help
|
||||
```
|
||||
|
||||
To configure this new tab page as website, you can install the firefox extension [New Tab Override](https://addons.mozilla.org/en-US/firefox/addon/new-tab-override/). Then just configure the url as `http://127.0.0.1:{port}/`. The default port should be `5500` but it will also print it out when starting the server. Make sure to check the box `Set focus to the web page instead of the address bar` in the extension settings, because the new tab page auto focuses the search bar.
|
||||
@@ -55,10 +55,9 @@ air dev
|
||||
|
||||
## TODO
|
||||
|
||||
- implementing proper command line args
|
||||
- clear cache
|
||||
- implement fetching in intervals
|
||||
- host this website on a demo page
|
||||
- implement templating for every one of the frontend files
|
||||
- implement functionality to clear and clean cache
|
||||
- host this website on a demo page
|
||||
- implement ctl
|
||||
- implement autocomplete with a nice go backend and fast communication. Since it all runs locally nobody should have privacy concerns NEEDS TO BE ABLE TO TOGGLED OFF FOR DEMO PAGE
|
||||
|
||||
|
||||
17
demo.toml
Normal file
17
demo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[Server]
|
||||
Port = 1312
|
||||
|
||||
[DiyHrt]
|
||||
FetchIntervals = 60
|
||||
|
||||
[Template]
|
||||
ActiveCard = "listings"
|
||||
PageTitle = "TransfemStartpage demo"
|
||||
HeaderPhrases = [
|
||||
"GirlJuice.Inject();",
|
||||
"You.Cute = true;",
|
||||
"You.Gay = true;",
|
||||
"Nazi.Punch();",
|
||||
"Dolls.GiveGuns();",
|
||||
"Firefox > Chrome"
|
||||
]
|
||||
@@ -49,16 +49,6 @@ body {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (max-height: 300px) {
|
||||
.search-grid {
|
||||
grid-template-rows: 4em;
|
||||
}
|
||||
|
||||
.search-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cards {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@@ -70,6 +60,20 @@ body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (max-height: 500px) {
|
||||
.search-grid {
|
||||
grid-template-rows: 4em;
|
||||
}
|
||||
|
||||
.search-logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
width: 10em;
|
||||
@@ -97,7 +101,6 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.card-image {
|
||||
height: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
|
||||
@@ -4,90 +4,94 @@ const form = document.getElementById("search-form");
|
||||
const input = document.getElementById("search-input");
|
||||
|
||||
// https://stackoverflow.com/a/3809435/16804841
|
||||
const expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
|
||||
const expression =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
|
||||
const urlRegex = new RegExp(expression);
|
||||
|
||||
const searchEngines = {
|
||||
"g": {
|
||||
action: "https://www.google.com/search",
|
||||
name: "q",
|
||||
},
|
||||
"d": {
|
||||
action: "https://duckduckgo.com/",
|
||||
name: "q",
|
||||
},
|
||||
"y": {
|
||||
action: "https://yandex.com/search/",
|
||||
name: "text",
|
||||
},
|
||||
"lure": {
|
||||
action: "https://lure.sh/pkgs",
|
||||
name: "q",
|
||||
},
|
||||
g: {
|
||||
action: "https://www.google.com/search",
|
||||
name: "q",
|
||||
},
|
||||
d: {
|
||||
action: "https://duckduckgo.com/",
|
||||
name: "q",
|
||||
},
|
||||
y: {
|
||||
action: "https://www.youtube.com/results",
|
||||
name: "search_query",
|
||||
},
|
||||
ya: {
|
||||
action: "https://yandex.com/search/",
|
||||
name: "text",
|
||||
},
|
||||
lure: {
|
||||
action: "https://lure.sh/pkgs",
|
||||
name: "q",
|
||||
},
|
||||
};
|
||||
|
||||
const translationPrefixes = [
|
||||
"t",
|
||||
"translation",
|
||||
]
|
||||
|
||||
const translationPrefixes = ["t", "translation"];
|
||||
|
||||
function getDeepLUrl(s) {
|
||||
const parts = s.split("-")
|
||||
if (parts.length != 3) {
|
||||
return undefined
|
||||
}
|
||||
const parts = s.split("-");
|
||||
if (parts.length != 3) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `https://www.deepl.com/en/translator?/#${encodeURIComponent(parts[0].trim())}/${encodeURIComponent(parts[1].trim())}/${encodeURIComponent(parts[2].trim())}`;
|
||||
return `https://www.deepl.com/en/translator?/#${encodeURIComponent(
|
||||
parts[0].trim()
|
||||
)}/${encodeURIComponent(parts[1].trim())}/${encodeURIComponent(
|
||||
parts[2].trim()
|
||||
)}`;
|
||||
}
|
||||
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
form.addEventListener("submit", event => {
|
||||
event.preventDefault();
|
||||
s = input.value;
|
||||
|
||||
s = input.value;
|
||||
// check if url
|
||||
if (s.match(urlRegex)) {
|
||||
window.open(s, "_self");
|
||||
return;
|
||||
}
|
||||
|
||||
// check if url
|
||||
if (s.match(urlRegex)) {
|
||||
window.open(s, "_self");
|
||||
return
|
||||
// deepl translations
|
||||
let doTranslation = false;
|
||||
for (const value of translationPrefixes) {
|
||||
const prefix = `!${value} `;
|
||||
if (s.startsWith(prefix)) {
|
||||
doTranslation = true;
|
||||
s = s.slice(prefix.length); // Remove the !{key} prefix
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// deepl translations
|
||||
let doTranslation = false;
|
||||
for (const value of translationPrefixes) {
|
||||
const prefix = `!${value} `;
|
||||
if (s.startsWith(prefix)) {
|
||||
doTranslation = true;
|
||||
s = s.slice(prefix.length); // Remove the !{key} prefix
|
||||
break;
|
||||
}
|
||||
if (doTranslation) {
|
||||
const url = getDeepLUrl(s);
|
||||
if (url) {
|
||||
window.open(url.toString(), "_self");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (doTranslation) {
|
||||
const url = getDeepLUrl(s);
|
||||
if (url) {
|
||||
window.open(url.toString(), "_self");
|
||||
return;
|
||||
}
|
||||
// Check if the string starts with ! followed by a key from searchEngines
|
||||
let selectedEngine = {
|
||||
action: form.getAttribute("action"),
|
||||
name: input.getAttribute("name"),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(searchEngines)) {
|
||||
const prefix = `!${key} `;
|
||||
if (s.startsWith(prefix)) {
|
||||
selectedEngine = value;
|
||||
s = s.slice(prefix.length); // Remove the !{key} prefix
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the string starts with ! followed by a key from searchEngines
|
||||
let selectedEngine = {
|
||||
action: form.getAttribute("action"),
|
||||
name: input.getAttribute("name"),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(searchEngines)) {
|
||||
const prefix = `!${key} `;
|
||||
if (s.startsWith(prefix)) {
|
||||
selectedEngine = value;
|
||||
s = s.slice(prefix.length); // Remove the !{key} prefix
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const url = new URL(selectedEngine.action);
|
||||
url.searchParams.set(selectedEngine.name, s.trim());
|
||||
window.open(url.toString(), "_self");
|
||||
const url = new URL(selectedEngine.action);
|
||||
url.searchParams.set(selectedEngine.name, s.trim());
|
||||
window.open(url.toString(), "_self");
|
||||
});
|
||||
|
||||
98
internal/cache/cache.go
vendored
Normal file
98
internal/cache/cache.go
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/utils"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
CacheDir string
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
func getCacheDir() (string, error) {
|
||||
baseDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
baseDir = "/tmp"
|
||||
}
|
||||
cacheDir := filepath.Join(baseDir, utils.Name)
|
||||
err = os.MkdirAll(cacheDir, 0o755)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cacheDir, nil
|
||||
}
|
||||
|
||||
func getProfileCacheDir(profile string) (string, error) {
|
||||
var profileCacheDir string
|
||||
|
||||
cacheDir, err := getCacheDir()
|
||||
if err != nil {
|
||||
return profileCacheDir, err
|
||||
}
|
||||
|
||||
profileCacheDir = filepath.Join(cacheDir, profile)
|
||||
err = os.MkdirAll(cacheDir, 0o755)
|
||||
return profileCacheDir, err
|
||||
}
|
||||
|
||||
func NewCache(profile string) Cache {
|
||||
cacheDir, err := getProfileCacheDir(profile)
|
||||
|
||||
return Cache{
|
||||
CacheDir: cacheDir,
|
||||
Disabled: err != nil,
|
||||
}
|
||||
}
|
||||
|
||||
const baseCacheUrl = "cache"
|
||||
|
||||
func (c Cache) StartStaticServer(e *echo.Echo) error {
|
||||
e.Static("/"+baseCacheUrl, c.CacheDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashUrl(url string) string {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, url)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func (c Cache) CacheUrl(urlString string) (string, error) {
|
||||
filename := hashUrl(urlString) + filepath.Ext(urlString)
|
||||
targetPath := filepath.Join(c.CacheDir, filename)
|
||||
|
||||
// if the file was already downloaded it doesn't need to be downloaded again
|
||||
if _, err := os.Stat(targetPath); errors.Is(err, os.ErrNotExist) {
|
||||
resp, err := http.Get(urlString)
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return urlString, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
file, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
return urlString, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return urlString, err
|
||||
}
|
||||
} else {
|
||||
return url.JoinPath(baseCacheUrl, filename)
|
||||
}
|
||||
|
||||
return url.JoinPath(baseCacheUrl, filename)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/utils"
|
||||
"github.com/TwiN/go-color"
|
||||
)
|
||||
|
||||
@@ -22,8 +23,8 @@ type Argument struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
var HelpHeader = `This is the help page of transfem-startpage.
|
||||
` + color.Purple + `transfem-startpage {program} {...args}` + color.Reset + `
|
||||
var HelpHeader = `This is the help page of ` + utils.Name + `.
|
||||
` + color.Purple + utils.BinaryName + ` {program} {...args}` + color.Reset + `
|
||||
The following Programs are available:`
|
||||
var Programs = []Program{
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/utils"
|
||||
"github.com/TwiN/go-color"
|
||||
)
|
||||
|
||||
@@ -55,7 +56,7 @@ func specificHelp(programName string) error {
|
||||
fmt.Println(color.Bold + "MAN PAGE FOR " + strings.ToUpper(programName) + color.Reset)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(color.Purple + "transfem-startpage " + programName + color.Reset + getArgumentString(program.Arguments))
|
||||
fmt.Println(color.Purple + utils.BinaryName + " " + programName + color.Reset + getArgumentString(program.Arguments))
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(color.Bold + "arguments" + color.Reset)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package diyhrt
|
||||
|
||||
type DiyHrtConfig struct {
|
||||
ApiKey string
|
||||
ApiKey string
|
||||
FetchIntervals int
|
||||
|
||||
StoreFilter StoreFilter
|
||||
ListingFilter ListingFilter
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const endpoint = "https://diyhrt.market/api/listings"
|
||||
|
||||
func GetListings(apiKey string) ([]Listing, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("API_KEY key not set. Set it as env or in DiyHrt.ApiKey")
|
||||
return nil, errors.New("diyhrt API_KEY key not set. Set it as env or in DiyHrt.ApiKey")
|
||||
}
|
||||
|
||||
// Create HTTP client
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/diyhrt"
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/utils"
|
||||
"github.com/pelletier/go-toml"
|
||||
)
|
||||
|
||||
@@ -62,7 +63,8 @@ func NewConfig() Config {
|
||||
Port: 5500,
|
||||
},
|
||||
DiyHrt: diyhrt.DiyHrtConfig{
|
||||
ApiKey: os.Getenv("API_KEY"),
|
||||
ApiKey: os.Getenv("API_KEY"),
|
||||
FetchIntervals: 60, // fetch every hour
|
||||
StoreFilter: diyhrt.StoreFilter{
|
||||
Limit: 0,
|
||||
IncludeIds: []int{7},
|
||||
@@ -105,7 +107,7 @@ func (rc *Config) ScanForConfigFile(profile string) error {
|
||||
|
||||
baseDir, cacheDirErr := os.UserConfigDir()
|
||||
if cacheDirErr == nil {
|
||||
configFile := filepath.Join(baseDir, "startpage", profileFile)
|
||||
configFile := filepath.Join(baseDir, utils.Name, profileFile)
|
||||
|
||||
if err := rc.LoadConfigFile(configFile); !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
@@ -138,12 +140,3 @@ func (rc *Config) LoadConfigFile(file string) error {
|
||||
|
||||
return toml.Unmarshal(content, rc)
|
||||
}
|
||||
|
||||
func (c *Config) Init() error {
|
||||
log.Println("downloading website icons...")
|
||||
for i := range c.Template.Websites {
|
||||
c.Template.Websites[i].Cache()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,23 +4,34 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/cache"
|
||||
"gitea.elara.ws/Hazel/transfem-startpage/internal/rendering"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
var Config = rendering.NewConfig()
|
||||
|
||||
func StartFetching() {
|
||||
for {
|
||||
log.Println("Fetch DiyHrt data...")
|
||||
Config.FetchDiyHrt()
|
||||
time.Sleep(time.Duration(Config.DiyHrt.FetchIntervals) * time.Second)
|
||||
|
||||
if Config.DiyHrt.FetchIntervals == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Start(profile string) error {
|
||||
err := Config.ScanForConfigFile(profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = Config.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go StartFetching()
|
||||
|
||||
err = Config.FetchDiyHrt()
|
||||
if err != nil {
|
||||
@@ -30,11 +41,19 @@ func Start(profile string) error {
|
||||
e := echo.New()
|
||||
|
||||
// statically serve the file
|
||||
cacheDir, err := rendering.GetCacheDir()
|
||||
if err == nil {
|
||||
e.Static("/cache", cacheDir)
|
||||
} else {
|
||||
log.Println("didn't enable cache dir", err)
|
||||
cache := cache.NewCache(profile)
|
||||
if !cache.Disabled {
|
||||
cache.StartStaticServer(e)
|
||||
|
||||
log.Println("downloading website icons...")
|
||||
for i, w := range Config.Template.Websites {
|
||||
u, err := cache.CacheUrl(w.ImageUrl)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
Config.Template.Websites[i].ImageUrl = u
|
||||
Config.Template.Websites[i].IsFetched = true
|
||||
}
|
||||
}
|
||||
|
||||
// https://echo.labstack.com/docs/cookbook/embed-resources
|
||||
|
||||
6
internal/utils/meta.go
Normal file
6
internal/utils/meta.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
var Name = "transfem-startpage"
|
||||
var BinaryName = os.Args[0]
|
||||
@@ -1 +1 @@
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
Reference in New Issue
Block a user