├── .github ├── FUNDING.yml └── workflows │ ├── go.yml │ └── lsif.yml ├── .gitignore ├── README.md ├── Taskfile.yml ├── assets ├── embed.go └── index.tmpl ├── go.mod ├── go.sum ├── internal └── process │ └── process.go └── siesta.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: wuhan005 2 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | push: 4 | branches: [ master ] 5 | paths: 6 | - '**.go' 7 | - 'go.mod' 8 | - '.github/workflows/go.yml' 9 | pull_request: 10 | paths: 11 | - '**.go' 12 | - 'go.mod' 13 | - '.github/workflows/go.yml' 14 | env: 15 | GOPROXY: "https://proxy.golang.org" 16 | 17 | jobs: 18 | lint: 19 | name: Lint 20 | runs-on: windows-latest 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v2 24 | - name: Run golangci-lint 25 | uses: golangci/golangci-lint-action@v2.4.0 26 | with: 27 | version: v1.37.0 28 | args: --timeout=30m 29 | -------------------------------------------------------------------------------- /.github/workflows/lsif.yml: -------------------------------------------------------------------------------- 1 | name: LSIF 2 | on: 3 | push: 4 | paths: 5 | - '**.go' 6 | - '.github/workflows/lsif.yml' 7 | jobs: 8 | lsif-go: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Generate LSIF data 13 | uses: sourcegraph/lsif-go-action@master 14 | - name: Upload LSIF data to sourcegraph.com 15 | continue-on-error: true 16 | uses: docker://sourcegraph/src-cli:latest 17 | with: 18 | args: lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea/ 18 | .bin/ 19 | .task 20 | .envrc 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 😴 siesta ![Go](https://github.com/wuhan005/siesta/workflows/Go/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/wuhan005/siesta)](https://goreportcard.com/report/github.com/wuhan005/siesta) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?logo=sourcegraph)](https://sourcegraph.com/github.com/wuhan005/siesta) 2 | 3 | ## 为什么会有这个项目? 4 | 5 | 最近国家出台了关于限制未成年人游戏时间的相关政策。但很可惜,我正在读小学二年级的弟弟只玩单机的 Minecraft,因此我爸在想能否写个简单的程序来约束他的游戏时间。我便糊了这么一个小玩意出来,原理很简单——超过时间了就杀死包含指定关键词的进程,提醒我弟去休息。 6 | 7 | 我知道这东西有千千万万种方式可以绕过,但是对于一个小学二年级的学生来说已经足够了。(如果我弟能为了 bypass 这东西开始钻研计算机,那就太好了!) 8 | 9 | 项目的名字取自英文 午睡 (siesta),寓意要时刻注意休息,不能沉迷游戏。取这个名字才不是因为最近看了什么番。 10 | 11 | ## 开始使用 12 | 13 | ```bash 14 | # otp 为基于时间的验证密钥 15 | # process 为指定进程的关键词 16 | ./siesta.exe --otp --process "minecraft,java" 17 | ``` 18 | 19 | 你可以通过访问 `http://localhost:2830/` 来延长允许游玩的时间。 20 | 21 | ## License 22 | 23 | MIT 24 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | vars: 4 | BUILD_TIME: 5 | sh: date -u '+%Y-%m-%d %I:%M:%S %Z' 6 | BUILD_COMMIT: 7 | sh: git rev-parse HEAD 8 | 9 | tasks: 10 | run: 11 | desc: Build binary & Run 12 | deps: [ build ] 13 | cmds: 14 | - ./.bin/siesta 15 | sources: 16 | - ./**/*.go 17 | 18 | build: 19 | desc: Build binary 20 | cmds: 21 | - go build -v 22 | -trimpath 23 | -o ./.bin/siesta 24 | -------------------------------------------------------------------------------- /assets/embed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 E99p1ant. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package assets 6 | 7 | import ( 8 | "embed" 9 | ) 10 | 11 | //go:embed *.tmpl 12 | var FS embed.FS 13 | -------------------------------------------------------------------------------- /assets/index.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

{{.EndTime}}

14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wuhan005/siesta 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/StackExchange/wmi v1.2.1 // indirect 7 | github.com/flamego/flamego v0.0.0-20210831044743-573bf1411fd9 8 | github.com/flamego/template v0.0.0-20210515150544-049eb6703243 9 | github.com/pkg/errors v0.9.1 10 | github.com/shirou/gopsutil v3.21.8+incompatible 11 | github.com/tklauser/go-sysconf v0.3.9 // indirect 12 | github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 13 | unknwon.dev/clog/v2 v2.2.0 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= 2 | github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= 3 | github.com/alecthomas/participle/v2 v2.0.0-alpha5/go.mod h1:Z1zPLDbcGsVsBYsThKXY00i84575bN/nMczzIrU4rWU= 4 | github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= 5 | github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= 6 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E= 7 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 12 | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= 13 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 14 | github.com/flamego/flamego v0.0.0-20210515130931-267d0ace579b/go.mod h1:DkwGPPSOX6S/4th2btOuP3HQX8zL59GZbxxOt6aFF0A= 15 | github.com/flamego/flamego v0.0.0-20210831044743-573bf1411fd9 h1:USv46KEornQvE4ppsmtL0qMkFeJq1XOFoojVW7yXCXY= 16 | github.com/flamego/flamego v0.0.0-20210831044743-573bf1411fd9/go.mod h1:apiAxIxeHujHFX4Yr0BHmQFJuZUM07XztdQSChbeuPw= 17 | github.com/flamego/template v0.0.0-20210515150544-049eb6703243 h1:LASand8mYCo8iWAsRzz2WmpXIo1CUXAn6iye2yzx5Oc= 18 | github.com/flamego/template v0.0.0-20210515150544-049eb6703243/go.mod h1:9bdmujHm26rSXXYmDQ3pfzUoKuV0R3BahgmSsSbvqUU= 19 | github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= 20 | github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 21 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 22 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 23 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 24 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 25 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 26 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 27 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 28 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 29 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/shirou/gopsutil v3.21.8+incompatible h1:sh0foI8tMRlCidUJR+KzqWYWxrkuuPIGiO6Vp+KXdCU= 33 | github.com/shirou/gopsutil v3.21.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 34 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 35 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 36 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 37 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 38 | github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= 39 | github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= 40 | github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= 41 | github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= 42 | github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4= 43 | github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8= 44 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 45 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 h1:ikCpsnYR+Ew0vu99XlDp55lGgDJdIMx3f4a18jfse/s= 50 | golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 54 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 55 | unknwon.dev/clog/v2 v2.2.0 h1:jkPdsxux0MC04BT/9NHbT75z4prK92SH10VBNmIpVCc= 56 | unknwon.dev/clog/v2 v2.2.0/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc= 57 | -------------------------------------------------------------------------------- /internal/process/process.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 E99p1ant. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build windows 6 | 7 | package process 8 | 9 | import ( 10 | "context" 11 | "strings" 12 | 13 | "github.com/pkg/errors" 14 | "github.com/shirou/gopsutil/process" 15 | log "unknwon.dev/clog/v2" 16 | ) 17 | 18 | // List returns all the windows processes. 19 | func List(ctx context.Context) ([]*process.Process, error) { 20 | return process.ProcessesWithContext(ctx) 21 | } 22 | 23 | // KillByKeyword kills the windows processes whose name contains the given keyword. 24 | func KillByKeyword(ctx context.Context, keyword string) error { 25 | processes, err := List(ctx) 26 | if err != nil { 27 | return errors.Wrap(err, "list process") 28 | } 29 | 30 | for _, p := range processes { 31 | name, err := p.Name() 32 | if err != nil { 33 | continue 34 | } 35 | if strings.Contains(name, keyword) { 36 | if err := p.KillWithContext(ctx); err != nil { 37 | return errors.Wrap(err, "kill") 38 | } 39 | log.Trace("Kill process: %v", name) 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /siesta.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 E99p1ant. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "flag" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/xlzd/gotp" 16 | log "unknwon.dev/clog/v2" 17 | 18 | "github.com/flamego/flamego" 19 | "github.com/flamego/template" 20 | 21 | "github.com/wuhan005/siesta/assets" 22 | "github.com/wuhan005/siesta/internal/process" 23 | ) 24 | 25 | func main() { 26 | defer log.Stop() 27 | err := log.NewConsole() 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | otpSecret := flag.String("otp", "", "") 33 | restrictedKeyword := flag.String("process", "minecraft", "") 34 | flag.Parse() 35 | 36 | // endTime is the time which allows to run the restricted processes. 37 | endTime := time.Now() 38 | totp := gotp.NewDefaultTOTP(*otpSecret) 39 | 40 | go func() { 41 | for { 42 | if time.Now().Before(endTime) { 43 | continue 44 | } 45 | ctx := context.Background() 46 | for _, kw := range strings.Split(*restrictedKeyword, ",") { 47 | if err := process.KillByKeyword(ctx, kw); err != nil { 48 | log.Error("Failed to kill by keyword: %v", err) 49 | } 50 | } 51 | time.Sleep(5 * time.Second) 52 | } 53 | }() 54 | 55 | // Web panel 56 | f := flamego.Classic() 57 | 58 | embedFS, err := template.EmbedFS(assets.FS, ".", []string{".tmpl"}) 59 | if err != nil { 60 | log.Fatal("Failed to embed file system: %v", err) 61 | } 62 | f.Use(template.Templater(template.Options{FileSystem: embedFS})) 63 | 64 | f.Get("/", func(t template.Template, data template.Data) { 65 | data["EndTime"] = endTime.Format("2006-01-02 15:04:05") 66 | t.HTML(http.StatusOK, "index") 67 | }) 68 | 69 | f.Post("/updateEndTime", func(ctx flamego.Context) { 70 | if err := ctx.Request().ParseForm(); err != nil { 71 | ctx.ResponseWriter().WriteHeader(http.StatusBadRequest) 72 | return 73 | } 74 | 75 | code := ctx.Request().Form.Get("code") 76 | if totp.Now() != code { 77 | ctx.ResponseWriter().WriteHeader(http.StatusForbidden) 78 | return 79 | } 80 | minute, err := strconv.Atoi(ctx.Request().Form.Get("minute")) 81 | if err != nil { 82 | ctx.ResponseWriter().WriteHeader(http.StatusBadRequest) 83 | return 84 | } 85 | endTime = endTime.Add(time.Duration(minute) * time.Minute) 86 | ctx.Redirect("/") 87 | }) 88 | f.Run() 89 | } 90 | --------------------------------------------------------------------------------