Skip to content

Commit

Permalink
release v0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
HelloLingC committed Dec 24, 2023
1 parent 06eb3a7 commit 1788c70
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-go-binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.exe
*.exe
*.db
8 changes: 8 additions & 0 deletions common/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import (
"log"
)

type Logger struct {
path string
}

func (l Logger) InitLogger(path string) {

}

func SilentError(v ...any) {
log.Println(v...)
}
5 changes: 3 additions & 2 deletions common/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down
10 changes: 7 additions & 3 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Binary file modified moon.db
Binary file not shown.
23 changes: 23 additions & 0 deletions moon.js
Original file line number Diff line number Diff line change
@@ -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);
});
48 changes: 30 additions & 18 deletions readme.md
Original file line number Diff line number Diff line change
@@ -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
<script src="https://yoursite.com/moon-counter/js/img"></script>
<img src="//yoursite.com/counter/img?id=uniqueID"></img>
```

#### 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

```
<script src="//yoursite.com/moon-counter/js/img"></script>
<img id="moon-counter-img"></img>
```

Expand All @@ -37,7 +49,7 @@ Two modes to choose, text & image
Add the following code to where you wanna place a text counter.

```
<script src="https://yoursite.com/moon-counter/js"></script>
<script src="//yoursite.com/moon-counter/js"></script>
<span id="moon-counter"></span>
```

Expand All @@ -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

Expand Down
56 changes: 56 additions & 0 deletions readme_cn.md
Original file line number Diff line number Diff line change
@@ -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)
<img src="//yoursite.com/counter/img?id=uniqueID"></img>
```

#### 安全 CORS

Unique id 参数会自动被处理

如果在配置文件中启用了 CORS,服务器将检查请求的来源是否合法,并返回 CORS 资源。在这种情况下,你应该仅以这种方式使用图片计数器。

```
<script src="//yoursite.com/moon-counter/js/img"></script>
<img id="moon-counter-img"></img>
```

## 文字计数器

将以下 html 代码放在你想要计数的地方

```
<script src="//yoursite.com/moon-counter/js"></script>
<span id="moon-counter"></span>
```

# 许可协议

MIT
6 changes: 4 additions & 2 deletions server/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 6 additions & 34 deletions server/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
41 changes: 27 additions & 14 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions server/server_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package server

0 comments on commit 1788c70

Please sign in to comment.