Skip to content

Commit 4bfbf21

Browse files
committed
init: 🎉 first commit
1 parent 45fa5b0 commit 4bfbf21

File tree

11 files changed

+277
-2
lines changed

11 files changed

+277
-2
lines changed

.github/workflows/send_task.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Distribute tasks on a CRON Schedule
2+
3+
on:
4+
schedule:
5+
- cron: '50 * * * *'
6+
7+
env:
8+
GO_VERSION: 1.19
9+
10+
jobs:
11+
distribute:
12+
name: Trigger task distributed
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Setup Go Environment
16+
uses: actions/setup-go@v3
17+
with:
18+
go-version: ${{ env.GO_VERSION }}
19+
id: go
20+
- name: Checkout Source Code
21+
uses: actions/checkout@v3
22+
- name: Go Get dependencies
23+
run: go get -v -t -d ./...
24+
- name: Distribute task
25+
run: go run cmd/main.go --config daily.toml --server ${{ secrets.EMAIL_SERVER }} --user ${{ secrets.EMAIL_USER }} --pass ${{ secrets.EMAIL_PASS }} --from ${{ secrets.EMAIL_FROM }} --to ${{ secrets.EMAIL_TO }}

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
# daily_bot
2-
A robot that sents you a daily task and motivates you to cultivate a daily habit.
1+
# task_bot
2+
A robot that sends you a daily task and motivates you to cultivate a daily habit, power by `GitHub Actions`.
3+
4+

cmd/main.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
6+
"code-in-gym/task_bot/config"
7+
"code-in-gym/task_bot/notify/email"
8+
"code-in-gym/task_bot/task"
9+
)
10+
11+
func main() {
12+
var (
13+
configPath string
14+
server string
15+
user string
16+
pass string
17+
to string
18+
from string
19+
)
20+
flag.StringVar(&configPath, "config", "daily.toml", "daily config file path")
21+
flag.StringVar(&server, "server", "", "email server")
22+
flag.StringVar(&user, "user", "", "email user")
23+
flag.StringVar(&pass, "pass", "", "email password")
24+
flag.StringVar(&to, "to", "", "email to")
25+
flag.StringVar(&from, "from", "", "email from")
26+
flag.Parse()
27+
28+
// randomly choose some tasks
29+
config := config.NewConfig[any]()
30+
config.Load(configPath)
31+
32+
// notify
33+
// Only support email now
34+
emailConfig := email.Config{}
35+
emailConfig.NewConfig(
36+
server, user, pass, to, from,
37+
)
38+
config.Notify.Email = append(config.Notify.Email, emailConfig)
39+
for _, e := range config.Notify.Email {
40+
for k, v := range config.Task {
41+
t := task.NewDailyTask(k, v)
42+
index := t.Random()
43+
e.NotifyFunc(k, t.Detail(index))
44+
}
45+
}
46+
}

config/config.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
8+
"code-in-gym/task_bot/task"
9+
"code-in-gym/task_bot/notify"
10+
11+
"github.com/BurntSushi/toml"
12+
)
13+
14+
type Config[T any] struct {
15+
Task map[string]task.Task[T]
16+
Notify notify.Config
17+
}
18+
19+
func NewConfig[T any]() *Config[T] {
20+
return &Config[T]{}
21+
}
22+
23+
// Load load config from file,
24+
// DO NOT put any sensitive key or secret in it,
25+
// use GitHub Actions secrets.
26+
func (c *Config[T]) Load(file string) {
27+
if _, err := os.Stat(file); err != nil {
28+
log.Fatalf("Config file `%s` DOES NOT exist.", file)
29+
}
30+
31+
_, err := toml.DecodeFile(file, &c)
32+
if err != nil {
33+
fmt.Fprintln(os.Stderr, err)
34+
os.Exit(1)
35+
}
36+
}

daily.toml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# daily task
2+
[task.leetcode]
3+
describe = "今天的 LeetCode 题目是 #%v, 试着在20分钟内解决它吧! You can do it!"
4+
tasks = [
5+
704, 27, 977, 209, 59, # 数组
6+
203, 707, 206, 24, 19, 142, # 链表
7+
242, 1002, 349, 202, 1, 454, 383, 15, 18, # 哈希表
8+
344, 541, 151, 459, # 字符串
9+
27, 344, 151, 206, 19, 142, 15, 18, # 双指针法
10+
232, 225, 20, 1047, 150, 239, 347, # 栈与队列
11+
226, 101, 104, 111, 222, 110, 257, 404, 513, 112, 106, 654, 617, 700, 98, 530, 501, 236, 235, 701, 450, 669, 108, 538,# 二叉树
12+
77, 216, 17, 39, 40, 131, 93, 78, 90, 491, 46, 47, 332, 51, 37,# 回溯
13+
455, 376, 53, 122, 55, 45, 1005, 134, 135, 860, 406, 406, 452, 435, 763, 56, 738, 968,# 贪心算法
14+
509, 70, 746, 62, 63, 343, 96, 416, 1049, 494, 474, 518, 377, 70, 322, 279, 139, 198, 213, 337, 121, 122, 123, 188, 309, 714, 300, 674, 718, 1143, 1035, 53, 392, 115, 583, 72, 647, 516, # 动态规划
15+
739, 496, 503, 42, 84, # 单调栈
16+
]
17+
cycle = true
18+
19+
[task.reading]
20+
describe = "今天的回顾旧书是 %v, 享受阅读的乐趣吧!"
21+
tasks = [
22+
"《这些人那些事》",
23+
"《The Minimalist Entrepreneur》",
24+
"《侠隐》",
25+
]
26+
cycle = false

go.mod

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module code-in-gym/task_bot
2+
3+
go 1.19
4+
5+
require (
6+
github.com/BurntSushi/toml v1.3.0
7+
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
8+
)
9+
10+
require gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA=
2+
github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
3+
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
4+
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
5+
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
6+
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=

notify/base/base.go

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package base
2+
3+
type DefaultNotify struct {
4+
Kind string
5+
NotifyFunc func(string, string) error
6+
}

notify/email/email.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package email
2+
3+
import (
4+
"code-in-gym/task_bot/notify/base"
5+
"net"
6+
"strconv"
7+
"strings"
8+
9+
"gopkg.in/gomail.v2"
10+
)
11+
12+
type Config struct {
13+
base.DefaultNotify
14+
Server string
15+
User string
16+
Pass string
17+
To string
18+
From string
19+
}
20+
21+
func (c *Config) NewConfig(server, user, pass, to, from string) error {
22+
c.NotifyFunc = c.SendNotify
23+
c.Kind = "email"
24+
c.Server = server
25+
c.Pass = pass
26+
c.User = user
27+
c.To = to
28+
c.From = from
29+
return nil
30+
}
31+
32+
func (c *Config) SendNotify(subject, content string) error {
33+
host, p, err := net.SplitHostPort(c.Server)
34+
if err != nil {
35+
return err
36+
}
37+
38+
port, err := strconv.Atoi(p)
39+
if err != nil {
40+
return err
41+
}
42+
43+
email := "Task" + "[" + c.User + "]"
44+
if c.From != "" {
45+
email = c.From
46+
}
47+
48+
split := func(r rune) bool {
49+
return r == ';' || r == ','
50+
}
51+
52+
recipients := strings.FieldsFunc(c.To, split)
53+
54+
m := gomail.NewMessage()
55+
m.SetHeader("From", email)
56+
m.SetHeader("To", recipients...)
57+
m.SetHeader("Subject", "[TaskBot] " + subject)
58+
m.SetBody("text/html; charset=UTF-8", content)
59+
60+
d := gomail.NewDialer(host, port, c.User, c.Pass)
61+
err = d.DialAndSend(m)
62+
63+
return err
64+
}

notify/notify.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package notify
2+
3+
import "code-in-gym/task_bot/notify/email"
4+
5+
type Notify interface {
6+
SendNotify()
7+
}
8+
9+
type Config struct {
10+
Email []email.Config `toml:"email"`
11+
}

task/task.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package task
2+
3+
import (
4+
"fmt"
5+
"math/rand"
6+
"time"
7+
)
8+
9+
type TaskHandle[T any] interface {
10+
Random() int
11+
Detail() string
12+
Finished(int) // remove the finished task, then rewrite the task config file
13+
}
14+
15+
type Task[T any] struct {
16+
Describe string
17+
Tasks []T
18+
Cycle bool
19+
}
20+
21+
type DailyTask[T any] struct {
22+
Task[T]
23+
TaskType string
24+
}
25+
26+
func NewDailyTask[T any](taskType string, tasks Task[T]) *DailyTask[T] {
27+
// TODO: remove finished task
28+
// if cycle is true and all tasks were finished,
29+
// restart it from the beginning.
30+
return &DailyTask[T]{
31+
Task: tasks,
32+
TaskType: taskType,
33+
}
34+
}
35+
36+
func (d *DailyTask[T]) Detail(index int) string {
37+
return fmt.Sprintf(d.Describe, d.Tasks[index])
38+
}
39+
40+
func (d *DailyTask[T]) Random() int {
41+
rand.Seed(time.Now().Unix())
42+
return rand.Intn(len(d.Tasks))
43+
}

0 commit comments

Comments
 (0)