diff --git a/.github/workflows/build-go-binary.yml b/.github/workflows/build-go-binary.yml
index e522452..2aaae32 100644
--- a/.github/workflows/build-go-binary.yml
+++ b/.github/workflows/build-go-binary.yml
@@ -23,4 +23,4 @@ jobs:
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
binary_name: "moon-counter"
- extra_files: LICENSE readme.md config.yaml
+ extra_files: LICENSE config.yaml
diff --git a/.gitignore b/.gitignore
index adb36c8..afe0410 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-*.exe
\ No newline at end of file
+*.exe
+*.db
\ No newline at end of file
diff --git a/common/logger.go b/common/logger.go
index a3cbae9..0d75197 100644
--- a/common/logger.go
+++ b/common/logger.go
@@ -4,6 +4,14 @@ import (
"log"
)
+type Logger struct {
+ path string
+}
+
+func (l Logger) InitLogger(path string) {
+
+}
+
func SilentError(v ...any) {
log.Println(v...)
}
diff --git a/common/models.go b/common/models.go
index 40ded53..c4bd262 100644
--- a/common/models.go
+++ b/common/models.go
@@ -2,10 +2,11 @@ package common
type Config struct {
Host string `yaml:"host"`
- Port int `yaml:"port"`
- ImgTheme string `yaml:"imgTheme"`
+ Port int `yaml:"listen"`
+ ImgTheme string `yaml:"img_yheme"`
Cors bool `yaml:"cors"`
Hostnames []string `yaml:"hostnames"`
+ ErrorLog string `yaml:"error_log"`
DBCfg DBConfig `yaml:"db"`
}
diff --git a/config.yaml b/config.yaml
index 54d6216..3799825 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,12 +1,16 @@
# Moon-Counter Config
-# See config docs:
+# See config docs: https://mini.moonlab.top/post/20231224-14/
host: localhost:3800
-port: 3800
+listen: 3800
cors: true
-imgTheme: rule34
+img_theme: rule34
hostnames:
- moonlab.top
+ # Remove localhost in production
- localhost
+
+error_log: error.log
+
db:
type: sqlite
dbname: moon.db
diff --git a/main.go b/main.go
index 5d5526b..b6f540e 100644
--- a/main.go
+++ b/main.go
@@ -33,7 +33,7 @@ func main() {
db.InitDB(config.DBCfg.Dbname)
defer db.CloseDB()
- server.LoadAssets("rule34")
+ server.LoadAssets(config.ImgTheme)
s := server.NewInstance(&config, db)
s.Start()
diff --git a/moon.db b/moon.db
index ed083b9..36470ba 100644
Binary files a/moon.db and b/moon.db differ
diff --git a/moon.js b/moon.js
new file mode 100644
index 0000000..9a0592d
--- /dev/null
+++ b/moon.js
@@ -0,0 +1,23 @@
+// Text
+fetch('//%s/counter/text')
+ .then(r => {
+ return r.text();
+ })
+ .then(d => {
+ document.getElementById("moon-counter").innerText = d;
+ })
+ .catch(e => {
+ console.error(e);
+ });
+
+// Img
+fetch('//%s/counter/img')
+ .then(r => {
+ return r.text();
+ })
+ .then(d => {
+ document.getElementById("moon-counter-img").src='data:image/svg+xml,' + d;
+ })
+ .catch(e => {
+ console.error(e);
+ });
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 03ef745..57114ca 100644
--- a/readme.md
+++ b/readme.md
@@ -1,34 +1,46 @@
# Moon-Counter
+English | [中文](readme_cn.md)
+
A fast, simple & easy-to-use webpage visitor counter, but not only limited to websites.
With a visual admin panel, put Moon-Counter at every corner
-🚀 Fast and Simple
+##### 🚀 Fast and Simple
-🎉 Self-Host & Easy-To-Use
-Deploy with only one file, zero dependency. No annoying complex installation
+##### 🎉 Easy Deployment
-🔒 Secure
-Support [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), make it hard for strangers to use your self-host service without permisson
+Run counter server with only one binary file, zero dependency. No annoying complex installation
-🌟 SQLite Database.
-Reeeallly easy to control and move
+##### 🔒 Secure [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Support
+
+ Make it hard for strangers to use your counter service without permisson to tally for them
-Two modes to choose, text & image
+##### 🌟 SQLite Database.
+
+Reeeallly easy to control and move
## Image Counter
+#### Common Method
+Make sure id argument is unique for every webpage
+
```
-# Markdown style
-# Make sure id arg is unique for each webpage
# You can use this in Github Profile
-![]()[https://yoursite.com/counter/img?id=uniqueID]
+![](https://yoursite.com/counter/img?id=uniqueID)
-# HTML style
-# Unique id arg is automatically handled
-# If cors is on, you should only use image counter in this way
-
+
+```
+
+#### Secure CORS
+
+Unique id arg is automatically handled
+
+If cors is on in the config file, server will check whether the request origin is vaild, and return cors resources.
+In this case, you should only use image counter in this way
+
+```
+
```
@@ -37,7 +49,7 @@ Two modes to choose, text & image
Add the following code to where you wanna place a text counter.
```
-
+
```
@@ -63,12 +75,12 @@ nano config.yaml
$ moon-counter
```
-For more details and configuration help, Please visit [my blog](https://mini.moonlab.top/)
+For more details and configuration help, Please visit [my blog](https://mini.moonlab.top/post/20231224-14/)
# Credits
-# Lisence
+# License
MIT
diff --git a/readme_cn.md b/readme_cn.md
new file mode 100644
index 0000000..e051918
--- /dev/null
+++ b/readme_cn.md
@@ -0,0 +1,56 @@
+# Moon-Counter
+
+[English]((readme.md)) | 中文
+
+快速,简单 & 易于使用的网页浏览量计数,但并不只局限于网站。
+
+##### 🚀 Fast and Simple
+
+##### 🎉 部署简单
+
+只需一个二进制文件即可启动计数服务器,零依赖。没有繁琐的安装过程。
+
+##### 🔒 安全的 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 支持
+
+让陌生人难以私自使用你的计数服务器,来为他们计数
+
+##### 🌟 SQLite Database.
+
+易于控制和搬迁
+
+## 图片计数器
+
+#### 普通方法
+
+请确保每个网页的 id 参数是独一无二的
+
+```
+# You can use this in Github Profile
+![](https://yoursite.com/counter/img?id=uniqueID)
+
+
+```
+
+#### 安全 CORS
+
+Unique id 参数会自动被处理
+
+如果在配置文件中启用了 CORS,服务器将检查请求的来源是否合法,并返回 CORS 资源。在这种情况下,你应该仅以这种方式使用图片计数器。
+
+```
+
+
+```
+
+## 文字计数器
+
+将以下 html 代码放在你想要计数的地方
+
+```
+
+
+```
+
+# 许可协议
+
+MIT
diff --git a/server/factory.go b/server/factory.go
index eab8280..37d5d21 100644
--- a/server/factory.go
+++ b/server/factory.go
@@ -70,12 +70,14 @@ func LoadAssets(theme string) {
}
func BuildCounterImg(c string) string {
- log.Println("c:", c)
+ iTimes := 6 - len(c)
+ for i := 0; i < iTimes; i++ {
+ c = "0" + c
+ }
var numberTempletes string
// Todo: Handle a situation if each image's dimentions are different
for i, sDigit := range c {
digit := strings.Index("0123456789", string(sDigit))
- log.Println("d:", digit)
img := images[digit]
// Todo: Add more image type support, exclude .gif
numberTempletes += fmt.Sprintf(NUMBER_TEMPLATE, i*img.width, img.width, img.height, "gif", *img.data)
diff --git a/server/helper.go b/server/helper.go
index ac5d57a..643ecdd 100644
--- a/server/helper.go
+++ b/server/helper.go
@@ -7,47 +7,19 @@ import (
"github.com/HelloLingC/moon-counter/common"
)
-const JS = `
-fetch('//%s/counter/text')
- .then(r => {
- return r.text();
- })
- .then(d => {
- document.getElementById("moon-counter").innerText = d;
- })
- .catch(e => {
- console.error(e);
- });
-`
+const JS = `fetch("//%s/counter/text").then(e=>e.text()).then(e=>{document.getElementById("moon-counter").innerText=e}).catch(e=>{console.error(e)});`
-const JS_IMG = `
-fetch('//%s/counter/img')
- .then(r => {
- return r.text();
- })
- .then(d => {
- document.getElementById("moon-counter").innerText = d;
- })
- .catch(e => {
- console.error(e);
- });
-`
+const JS_IMG = `fetch("//%s/counter/img").then(e=>e.text()).then(e=>{document.getElementById("moon-counter-img").src="data:image/svg+xml,"+e}).catch(e=>{console.error(e)});`
-func checkOrigin(w http.ResponseWriter, origin string, hostnames []string) string {
+func checkOrigin(w http.ResponseWriter, origin string, hostnames []string) bool {
parsed, err := url.Parse(origin)
if err != nil {
http.Error(w, "Invaild orgin: not a url", http.StatusBadRequest)
- return ""
+ return false
}
isAllowed := common.StrIsInSlice(parsed.Hostname(), hostnames)
if !isAllowed {
- http.Error(w, "Forbidden", http.StatusForbidden)
- return ""
+ http.Error(w, "Invaild orgin: Forbidden", http.StatusForbidden)
}
- rmOrigin, err := common.StrRemoveProtocol(origin)
- if err != nil {
- http.Error(w, "Invaild origin: missing protocal", http.StatusBadRequest)
- return ""
- }
- return rmOrigin
+ return isAllowed
}
diff --git a/server/server.go b/server/server.go
index f9124f0..465abc1 100644
--- a/server/server.go
+++ b/server/server.go
@@ -23,13 +23,8 @@ func NewInstance(config *common.Config, db database.IDatabase) *Server {
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
- // Handle preflight requests
- if r.Method == http.MethodOptions {
- w.WriteHeader(http.StatusNoContent)
- return
- }
next.ServeHTTP(w, r)
})
}
@@ -45,28 +40,44 @@ func (s Server) jsImgHndl(w http.ResponseWriter, r *http.Request) {
}
func (s Server) imgHndl(w http.ResponseWriter, r *http.Request) {
- identifier := r.URL.Query().Get("id")
- if identifier == "" {
- http.Error(w, "missing identifier", http.StatusBadRequest)
+ origin := r.Header.Get("Origin")
+ if s.Config.Cors && !checkOrigin(w, origin, s.Config.Hostnames) {
return
}
+ var identifier string
+ if origin != "" {
+ // Client is using JS to request here
+ identifier = origin
+ } else {
+ identifier = r.URL.Query().Get("id")
+ if identifier == "" {
+ http.Error(w, "missing identifier", http.StatusBadRequest)
+ return
+ }
+ }
count, err := s.DB.AddCounter(identifier)
if err != nil {
common.SilentError("SQL err when adding:", err)
http.Error(w, "DB Error", http.StatusServiceUnavailable)
return
}
+ // Todo: digits customization
svg := BuildCounterImg(fmt.Sprintf("%d", count))
+ w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Content-Type", "image/svg+xml")
fmt.Fprint(w, svg)
}
func (s Server) textHndl(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
- rmOrigin := checkOrigin(w, origin, s.Config.Hostnames)
- if rmOrigin == "" {
+ if s.Config.Cors && !checkOrigin(w, origin, s.Config.Hostnames) {
// Didn't pass the origin check
- http.Error(w, "access blocked", http.StatusForbidden)
+ return
+ }
+ // Todo; text counter support id argument
+ rmOrigin, err := common.StrRemoveProtocol(origin)
+ if err != nil {
+ http.Error(w, "Invaild origin: missing protocal", http.StatusBadRequest)
return
}
count, err := s.DB.AddCounter(rmOrigin)
@@ -75,7 +86,7 @@ func (s Server) textHndl(w http.ResponseWriter, r *http.Request) {
http.Error(w, "DB error", http.StatusServiceUnavailable)
return
}
- // [S] Do NOT send all the allowed origins to the client
+ // Do NOT send all the allowed origins to the client
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "%d", count)
@@ -85,10 +96,12 @@ func (s Server) Start() {
log.Printf("Moon Counter starts running at localhost:%d", s.Config.Port)
tHndl := http.HandlerFunc(s.textHndl)
+ iHndl := http.HandlerFunc(s.imgHndl)
http.HandleFunc("/moon-counter/js", s.jsTextHndl)
+ http.HandleFunc("/moon-counter/js/img", s.jsImgHndl)
http.Handle("/counter/text", corsMiddleware(tHndl))
- http.HandleFunc("/counter/img", s.imgHndl)
+ http.Handle("/counter/img", corsMiddleware(iHndl))
err := http.ListenAndServe(fmt.Sprintf(":%d", s.Config.Port), nil)
if err != nil {
diff --git a/server/server_admin.go b/server/server_admin.go
new file mode 100644
index 0000000..abb4e43
--- /dev/null
+++ b/server/server_admin.go
@@ -0,0 +1 @@
+package server