├── .env.example ├── .github └── workflows │ ├── ci.yml │ ├── schedule-end.yml │ └── schedule-satrt.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── main.go ├── model ├── dataModel.go └── sentence.json ├── service ├── moguding.go └── serverChan.go ├── test ├── env_test.go ├── ft_test.go ├── md5_test.go ├── time_test.go └── week_test.go └── utils ├── code.go ├── sentence.go ├── sign.go ├── timePicker.go └── weekPicker.go /.env.example: -------------------------------------------------------------------------------- 1 | ACCOUNT=111111 2 | PASSWORD=111111 3 | ADDRESS=北京市 · 北京市 4 | CITY=朝阳区 5 | PROVINCE=北京市 6 | # 经度·纬度 7 | # 参考:https://lbs.amap.com/console/show/picker 8 | LONGITUDE=111.0 9 | LATITUDE=111.0 10 | # 方糖推送服务 11 | KEY=xxxx -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | env: 13 | ACCOUNT: ${{ secrets.ACCOUNT }} 14 | PASSWORD: ${{ secrets.PASSWORD }} 15 | KEY: ${{ secrets.KEY }} 16 | ADDRESS: ${{ secrets.ADDRESS }} 17 | CITY: ${{ secrets.CITY }} 18 | PROVINCE: ${{ secrets.PROVINCE }} 19 | LONGITUDE: ${{ secrets.LONGITUDE }} 20 | LATITUDE: ${{ secrets.LATITUDE }} 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: 1.15 29 | 30 | - name: Test Unit 31 | run: | 32 | go mod download 33 | go run main.go 34 | go test -v -run ^TestRemoteEnv$ towelong/mogu/test 35 | go test -v -run ^TestRemoteTime$ towelong/mogu/test 36 | go test -count=1 -v -run ^TestWeek$ towelong/mogu/test 37 | go test -count=1 -v -run ^TestWeekDiary$ towelong/mogu/test -------------------------------------------------------------------------------- /.github/workflows/schedule-end.yml: -------------------------------------------------------------------------------- 1 | name: schedule-end 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "25 10 * * *" 7 | 8 | jobs: 9 | build: 10 | env: 11 | ACCOUNT: ${{ secrets.ACCOUNT }} 12 | PASSWORD: ${{ secrets.PASSWORD }} 13 | KEY: ${{ secrets.KEY }} 14 | ADDRESS: ${{ secrets.ADDRESS }} 15 | CITY: ${{ secrets.CITY }} 16 | PROVINCE: ${{ secrets.PROVINCE }} 17 | LONGITUDE: ${{ secrets.LONGITUDE }} 18 | LATITUDE: ${{ secrets.LATITUDE }} 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Clone repository 22 | uses: actions/checkout@v2 23 | 24 | - name: Install golang 25 | uses: actions/setup-go@master 26 | with: 27 | go-version: 1.15 28 | id: go 29 | 30 | - name: Run App 31 | run: | 32 | go mod download 33 | go run main.go -------------------------------------------------------------------------------- /.github/workflows/schedule-satrt.yml: -------------------------------------------------------------------------------- 1 | name: schedule-start 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 23 * * *" 7 | 8 | jobs: 9 | build: 10 | env: 11 | ACCOUNT: ${{ secrets.ACCOUNT }} 12 | PASSWORD: ${{ secrets.PASSWORD }} 13 | KEY: ${{ secrets.KEY }} 14 | ADDRESS: ${{ secrets.ADDRESS }} 15 | CITY: ${{ secrets.CITY }} 16 | PROVINCE: ${{ secrets.PROVINCE }} 17 | LONGITUDE: ${{ secrets.LONGITUDE }} 18 | LATITUDE: ${{ secrets.LATITUDE }} 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Clone repository 22 | uses: actions/checkout@v2 23 | 24 | - name: Install golang 25 | uses: actions/setup-go@master 26 | with: 27 | go-version: 1.15 28 | id: go 29 | 30 | - name: Run App 31 | run: | 32 | go mod download 33 | go run main.go -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/go 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=go 4 | 5 | ### vscode ### 6 | .vscode 7 | .vscode/* 8 | !.vscode/settings.json 9 | !.vscode/tasks.json 10 | !.vscode/launch.json 11 | !.vscode/extensions.json 12 | *.code-workspace 13 | 14 | ### Go ### 15 | # Binaries for programs and plugins 16 | *.exe 17 | *.exe~ 18 | *.dll 19 | *.so 20 | *.dylib 21 | 22 | # Test binary, built with `go test -c` 23 | *.test 24 | 25 | # Output of the go coverage tool, specifically when used with LiteIDE 26 | *.out 27 | 28 | # Dependency directories (remove the comment below to include it) 29 | # vendor/ 30 | 31 | ### Go Patch ### 32 | /vendor/ 33 | /Godeps/ 34 | 35 | ## env 36 | .env 37 | 38 | ## idea 39 | .idea 40 | 41 | # End of https://www.toptal.com/developers/gitignore/api/go -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WeLong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 蘑菇钉自动打卡 2 | ![Go](https://github.com/ToWeLong/go-mogu/workflows/Go/badge.svg) 3 | ![schedule-start](https://github.com/ToWeLong/go-mogu/workflows/schedule-start/badge.svg) 4 | ![schedule-end](https://github.com/ToWeLong/go-mogu/workflows/schedule-end/badge.svg) 5 | [![GitHub language](https://img.shields.io/badge/language-golang-orange.svg)](https://golang.org/) 6 | [![GitHub license](https://img.shields.io/github/license/ToWeLong/zhihu-hot-questions)](https://github.com/ToWeLong/go-mogu/blob/main/LICENSE) 7 | > 用法:首先找到`.env.example`文件,打开并修改其中的隐私信息,去掉后缀`.example`,存储为`.env`文件即可。 8 | 9 | > 重要: 由于蘑菇钉最近更新了接口,导致之前的接口都不可用了,然后还新增了一个sign,也就是签名。 10 | > 非常感谢 `@晓宇` 同学提供的sign算法 11 | 12 | 13 | # 完成 14 | - [X] 自动签到 15 | - [X] 周报自动编写 16 | - [X] 日志记录 17 | 18 | 19 | # 测试单个函数(去除cached) 20 | ```bash 21 | go test -count=1 -v -run ^TestWeek$ towelong/mogu/test 22 | ``` 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module towelong/mogu 2 | 3 | go 1.16 4 | 5 | require github.com/joho/godotenv v1.3.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 2 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "towelong/mogu/service" 8 | "towelong/mogu/utils" 9 | 10 | "github.com/joho/godotenv" 11 | ) 12 | 13 | func main() { 14 | // load enviroment secret key 15 | godotenv.Load() 16 | moguding := service.NewMoGuService() 17 | token, userId := moguding.MoGuLogin(os.Getenv("ACCOUNT"), os.Getenv("PASSWORD")) 18 | planID := moguding.GetPlanID(token, userId) 19 | isSuccess, types := moguding.SignIn(token, planID, userId) 20 | title, message := utils.EnumToMsg(types) 21 | if !isSuccess { 22 | service.SendMessage(title, message) 23 | log.Fatal(title) 24 | } 25 | if service.SendMessage(title, message) { 26 | fmt.Println("打卡成功") 27 | } 28 | 29 | // 写周记 30 | isTrue, weekType := moguding.WeeklyDiary(token, planID) 31 | headline, msg := utils.EnumToMsg(weekType) 32 | if !isTrue && weekType == utils.NOWEEK { 33 | fmt.Println(msg) 34 | } else { 35 | if service.SendMessage(headline, msg) { 36 | fmt.Println("打卡成功") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /model/dataModel.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // DataModel is used to reflect data 4 | type DataModel struct { 5 | Code int `json:"code"` 6 | Data data `json:"data"` 7 | } 8 | 9 | // Data is used reflect data to dataDodel 10 | type data struct { 11 | NiKeName string `json:"nikeName"` 12 | UserID string `json:"userId"` 13 | Token string `json:"token"` 14 | } 15 | 16 | // PlanModel return Object 17 | type PlanModel struct { 18 | Code int `json:"code"` 19 | Data []plan `json:"data"` 20 | } 21 | 22 | // Plan Object 23 | type plan struct { 24 | PlanID string `json:"planId"` 25 | PlanName string `json:"planName"` 26 | } 27 | 28 | // SignInModel signIn form data 29 | type SignInModel struct { 30 | // sign device 31 | Device string `json:"device"` 32 | // planId 33 | PlanID string `json:"planId"` 34 | // default: China 35 | Country string `json:"country"` 36 | // default: NORMAL 37 | State string `json:"state"` 38 | // default:"" 39 | Description string `json:"description"` 40 | // address 41 | Address string `json:"address"` 42 | // "go to work":START or "go off work":END 43 | Type string `json:"type"` 44 | // 经度 参考:https://lbs.amap.com/console/show/picker 45 | Longitude string `json:"longitude"` 46 | // 纬度 参考:https://lbs.amap.com/console/show/picker 47 | Latitude string `json:"latitude"` 48 | // 区 49 | City string `json:"city"` 50 | // 市 51 | Province string `json:"province"` 52 | } 53 | 54 | // WeekWriterModel Weekly Diary form 55 | type WeekWriterModel struct { 56 | AttachmentList []string `json:"attachmentList"` 57 | Attachments string `json:"attachments"` 58 | // 周报内容 59 | Content string `json:"content"` 60 | PlanID string `json:"planId"` 61 | // 周报类型 62 | ReportType string `json:"reportType"` 63 | // 周报标题 64 | Title string `json:"title"` 65 | // 第 X 周 66 | Weeks string `json:"weeks"` 67 | // 当前周开始时间 68 | StartTime string `json:"startTime"` 69 | // 当前周结束时间 70 | EndTime string `json:"endTime"` 71 | } 72 | 73 | // SentenceModel init data 74 | type SentenceModel struct { 75 | Code int `json:"code"` 76 | Data []text `json:"data"` 77 | } 78 | 79 | type text struct { 80 | Text string `json:"text"` 81 | } 82 | -------------------------------------------------------------------------------- /model/sentence.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": [ 4 | { 5 | "text": "未来的路太长总有一段路需要自己独行。专升本之路也是如此,通过最近几周的学习逐渐感受到升本之路的艰辛,不过没关系,坚持就是胜利,升本人加油呀! " 6 | }, 7 | { 8 | "text": "最近看的一本书写到:白发无凭吾老矣!青春不再汝知乎?年将弱冠非童子,学不成名岂丈夫? 这更加使我坚定了专升本的决心和毅力,新的一周又开始了 大家加油! " 9 | }, 10 | { 11 | "text": "一转眼又到了写周记的时候了,还是送上一句话:青春啊,永远是美好的,可是真正的青春,只属于这些力争上游的人,永远忘我劳动的人,永远谦虚的人。保证自己永远的谦逊,然后多学习,在升本途中,能够锻炼自己自学的能力,也能够提升自己的学历,这是双赢,未来希望能成为更好的自己。 " 12 | }, 13 | { 14 | "text": "这一周的学习任务其实并没有之前那么的难了,到目前为止现在已经是第三轮复习了,这句话送给大家:任何新生事物在开始时都不过是一株幼苗,一切新生事物之可贵,就因为在这新生的幼苗中,有无限的活力在成长,成长为巨人成长为力量。 " 15 | }, 16 | { 17 | "text": "吾人在世,不可厌“今”而徒回思“过去”,梦想“将来”以耗误“现在”的努力。又不可以“今”境自足,毫不拿出“现在”的努力,谋“将来”的发展。宜善用“今”,以努力为“将来”之创造。 " 18 | }, 19 | { 20 | "text": "共勉:愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者流的话。能做事的做事,能发声的发声。有一分热,发一分光。就令萤火一般,也可以在黑暗里发一点光,不必等候炬火。 " 21 | }, 22 | { 23 | "text": "年轻要有朝气,青年的朝气倘若消失,前进的好奇心衰退,人生就没有意义。坚持就是胜利~ 一起加油吧 " 24 | }, 25 | { 26 | "text": "青春并不是生命中一段时光,它是心灵上的一种状况。它跟丰润的面颊,殷红的嘴唇,柔滑的膝盖无关。它是一种沉静的意志,想象的能力,感情的活力,它更是生命之泉的新血液。 " 27 | }, 28 | { 29 | "text": "真正希望过“很宽阔、很美好的生活”,就创造它吧,和那些正在英勇地建立空前未有的、宏伟的事业的人手携手地去工作吧。在生活中,堆积了许多美好的、实际的工作,这些工作会使我们的土地富饶,会把人从偏颇、成见和迷信的可耻的俘虏中解放出来。 " 30 | }, 31 | { 32 | "text": "没有狂风和暴雨的吹打,哪来果实的的成熟;没有刺骨的寒风,哪来坚韧的松柏;没有冰天雪地;哪来傲骨的梅花。彼得逊说过:人生中,经常有无数来自外部的打击,但这些打击究竟会对你产生怎样的影响,最终的决定权在你自己手中。 " 33 | }, 34 | { 35 | "text": "有些话,适合藏在心里,有些痛苦,适合无声无息的忘记。当经历过,你成长了,自己知道就好。很多改变,不需要你自己说,别人会看得到。 " 36 | }, 37 | { 38 | "text": "也许你听过一首歌,歌词是“不抛弃,不放弃”;也许你攻克难题时,老师会对你说:“快解出来了,不要放弃”;也许在长跑时,你会对自己说:“坚持一下就到终点了,不能放弃”。但是,我要告诉你,学会放弃不需要的东西。加油啊!!! " 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /service/moguding.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "time" 12 | "towelong/mogu/model" 13 | "towelong/mogu/utils" 14 | ) 15 | 16 | const ( 17 | url = "https://api.moguding.net:9000" 18 | salt = "3478cbbc33f84bd00d75d7dfa69e0daa" 19 | ) 20 | 21 | // MoGuService generate a serious of function's interfaces. 22 | type MoGuService interface { 23 | MoGuLogin(account, password string) (string, string) 24 | GetPlanID(token, userId string) string 25 | SignIn(token, planID, userId string) (bool, string) 26 | WeeklyDiary(token, planID string) (bool, string) 27 | } 28 | 29 | // moGuService is a empty struction, which include a serious of functions. 30 | // mainly, in order to let it face to Object. 31 | type moGuService struct { 32 | } 33 | 34 | // NewMoGuService init struction. 35 | func NewMoGuService() MoGuService { 36 | return &moGuService{} 37 | } 38 | 39 | // MoGuLogin is a login logic of MoGu. 40 | // account: When user register application,it is usually a phone number. 41 | // password: Create by User 42 | func (m moGuService) MoGuLogin(account, password string) (token, userId string) { 43 | body := map[string]string{ 44 | "phone": account, 45 | "password": password, 46 | "loginType": "android", 47 | "uuid": "", 48 | } 49 | client := &http.Client{} 50 | form, _ := json.Marshal(body) 51 | request, err := http.NewRequest( 52 | "POST", 53 | url+"/session/user/v1/login", 54 | bytes.NewReader(form), 55 | ) 56 | if err == nil { 57 | request.Header.Add("accept-language", "zh-CN,zh;q=0.8") 58 | request.Header.Add("user-agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; Android SDK built for x86 Build/KK) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30") 59 | request.Header.Add("content-type", "application/json; charset=UTF-8") 60 | request.Header.Add("cache-control", "no-cache") 61 | resp, err := client.Do(request) 62 | if err == nil { 63 | defer resp.Body.Close() 64 | result, _ := ioutil.ReadAll(resp.Body) 65 | var data model.DataModel 66 | _ = json.Unmarshal(result, &data) 67 | if data.Code == 200 { 68 | return data.Data.Token, data.Data.UserID 69 | } 70 | } 71 | } 72 | return "", "" 73 | } 74 | 75 | // GetPlanID getPlanID get task id 76 | func (m moGuService) GetPlanID(token, userId string) string { 77 | body := map[string]int{ 78 | "state": 1, 79 | } 80 | client := &http.Client{} 81 | form, _ := json.Marshal(body) 82 | request, err := http.NewRequest( 83 | "POST", 84 | url+"/practice/plan/v3/getPlanByStu", 85 | bytes.NewReader(form), 86 | ) 87 | if err == nil { 88 | // 计算获取实习计划的sign 89 | // str = userId + student + salt 90 | sign := utils.CreateSign(userId + "student" + salt) 91 | request.Header.Add("user-agent", "Mozilla/5.0 (Linux; U; Android 9; zh-cn; ONEPLUS A6010 Build/PKQ1.180716.001) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1") 92 | request.Header.Add("content-type", "application/json; charset=UTF-8") 93 | request.Header.Add("authorization", token) 94 | request.Header.Add("rolekey", "student") 95 | request.Header.Add("sign", sign) 96 | resp, err := client.Do(request) 97 | if err == nil { 98 | defer resp.Body.Close() 99 | result, _ := ioutil.ReadAll(resp.Body) 100 | var data model.PlanModel 101 | _ = json.Unmarshal(result, &data) 102 | if data.Code == 200 { 103 | return data.Data[0].PlanID 104 | } 105 | } 106 | } 107 | return "" 108 | } 109 | 110 | // SignIn signIn Logic 111 | func (m moGuService) SignIn(token, planID, userId string) (bool, string) { 112 | address := os.Getenv("ADDRESS") 113 | city := os.Getenv("CITY") 114 | province := os.Getenv("PROVINCE") 115 | longitude := os.Getenv("LONGITUDE") 116 | latitude := os.Getenv("LATITUDE") 117 | if address == "" && longitude == "" && city == "" { 118 | log.Fatal("failed to Load secret ") 119 | } 120 | // 自动计算 上午 or 下午 121 | // 上午为 上班打卡;下午为 下班打卡 122 | types := utils.TimePicker() 123 | body := &model.SignInModel{ 124 | Device: "Android", 125 | PlanID: planID, 126 | Country: "中国", 127 | Type: types, // 默认打卡上班 128 | Description: "", 129 | State: "NORMAL", 130 | Address: address, 131 | Longitude: longitude, 132 | Latitude: latitude, 133 | City: city, 134 | Province: province, 135 | } 136 | client := &http.Client{} 137 | form, _ := json.Marshal(body) 138 | request, err := http.NewRequest( 139 | "POST", 140 | url+"/attendence/clock/v2/save", 141 | bytes.NewReader(form), 142 | ) 143 | if err == nil { 144 | str := body.Device + types + planID + userId + body.Address + salt 145 | sign := utils.CreateSign(str) 146 | request.Header.Add("user-agent", "Mozilla/5.0 (Linux; U; Android 9; zh-cn; ONEPLUS A6010 Build/PKQ1.180716.001) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1") 147 | request.Header.Add("content-type", "application/json; charset=UTF-8") 148 | request.Header.Add("Authorization", token) 149 | request.Header.Add("sign", sign) 150 | resp, err := client.Do(request) 151 | if err == nil { 152 | defer resp.Body.Close() 153 | result, _ := ioutil.ReadAll(resp.Body) 154 | var data map[string]interface{} 155 | _ = json.Unmarshal(result, &data) 156 | if data["code"].(float64) == 200 { 157 | return true, types 158 | } 159 | fmt.Println(data["msg"]) 160 | } 161 | } 162 | return false, utils.ERROR 163 | } 164 | 165 | // WeeklyDiary it will be automatic writing weekly diary. 166 | func (m moGuService) WeeklyDiary(token, planID string) (bool, string) { 167 | if !(time.Now().UTC().Weekday() == time.Saturday && utils.TimePicker() == utils.END) { 168 | return false, utils.NOWEEK 169 | } 170 | sentence, randomErr := utils.RandomSentence() 171 | fmt.Println(sentence) 172 | if randomErr != nil { 173 | log.Fatal(randomErr) 174 | } 175 | currentWeek, startTime, endTime := utils.WeeklyPicker(time.Now()) 176 | body := &model.WeekWriterModel{ 177 | AttachmentList: []string{}, 178 | Attachments: "", 179 | PlanID: planID, 180 | ReportType: "week", 181 | Title: fmt.Sprintf("第%v周周报", currentWeek), 182 | Content: sentence, 183 | Weeks: fmt.Sprintf("第%v周", currentWeek), 184 | StartTime: startTime, 185 | EndTime: endTime, 186 | } 187 | client := &http.Client{} 188 | form, _ := json.Marshal(body) 189 | request, err := http.NewRequest( 190 | "POST", 191 | url+"/practice/paper/v1/save", 192 | bytes.NewReader(form), 193 | ) 194 | if err == nil { 195 | request.Header.Add("user-agent", "Mozilla/5.0 (Linux; U; Android 9; zh-cn; ONEPLUS A6010 Build/PKQ1.180716.001) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1") 196 | request.Header.Add("content-type", "application/json; charset=UTF-8") 197 | request.Header.Add("Authorization", token) 198 | resp, err := client.Do(request) 199 | if err == nil { 200 | defer resp.Body.Close() 201 | result, _ := ioutil.ReadAll(resp.Body) 202 | var data map[string]interface{} 203 | _ = json.Unmarshal(result, &data) 204 | if data["code"].(float64) == 200 { 205 | return true, utils.WEEK 206 | } 207 | if data["code"].(float64) == 500 { 208 | fmt.Println(data["msg"]) 209 | return false, utils.NOWEEK 210 | } 211 | } 212 | } 213 | return false, utils.ERROR 214 | } 215 | -------------------------------------------------------------------------------- /service/serverChan.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | const ( 12 | serverChanAPI = "https://sctapi.ftqq.com/" 13 | ) 14 | 15 | // SendMessage we can use Server Chan to send message. 16 | // text: message title. 17 | // desp: message content. 18 | func SendMessage(text, desp string) bool { 19 | secretKey := os.Getenv("KEY") 20 | urlStr := fmt.Sprintf(serverChanAPI+"%v.send?text=%v&desp=%v", secretKey, text, desp) 21 | if resp, err := http.Get(urlStr); err == nil { 22 | defer resp.Body.Close() 23 | body, _ := ioutil.ReadAll(resp.Body) 24 | res := make(map[string]interface{}) 25 | json.Unmarshal(body, &res) 26 | if res["code"].(float64) == 0 { 27 | return true 28 | } 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /test/env_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/joho/godotenv" 9 | ) 10 | 11 | // local test 12 | func TestLocalEnv(t *testing.T) { 13 | err := godotenv.Load("../.env") 14 | if err == nil { 15 | fmt.Println(os.Getenv("ACCOUNT")) 16 | fmt.Println(os.Getenv("PASSWORD")) 17 | fmt.Println(os.Getenv("ADDRESS")) 18 | fmt.Println(os.Getenv("KEY")) 19 | } else { 20 | fmt.Println("环境变量读取失败") 21 | } 22 | } 23 | 24 | // remote test 25 | func TestRemoteEnv(t *testing.T) { 26 | address := os.Getenv("ADDRESS") 27 | city := os.Getenv("CITY") 28 | key := os.Getenv("KEY") 29 | if address == "" && city == "" && key == "" { 30 | fmt.Println("failed to Load secret ") 31 | } else { 32 | fmt.Println("Load secret success") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/ft_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "towelong/mogu/service" 7 | "towelong/mogu/utils" 8 | 9 | "github.com/joho/godotenv" 10 | ) 11 | 12 | func TestFT(t *testing.T) { 13 | godotenv.Load("../.env") 14 | types := utils.TimePicker() 15 | title, message := utils.EnumToMsg(types) 16 | b := service.SendMessage(title, message) 17 | if b { 18 | fmt.Println("Push Success!") 19 | } 20 | fmt.Println(b) 21 | } 22 | -------------------------------------------------------------------------------- /test/md5_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "towelong/mogu/utils" 7 | ) 8 | 9 | func TestMd5(t *testing.T) { 10 | // str := "102489283student3478cbbc33f84bd00d75d7dfa69e0daa" 11 | str2 := "AndroidSTARTa6870f0e4f8947f882f2d77fd302f0c6102489283北京市 · 北京市3478cbbc33f84bd00d75d7dfa69e0daa" 12 | md5Str := utils.CreateSign(str2) 13 | fmt.Println(md5Str) 14 | // planId a6870f0e4f8947f882f2d77fd302f0c6 15 | } 16 | -------------------------------------------------------------------------------- /test/time_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | "towelong/mogu/utils" 8 | ) 9 | 10 | func TestTime(t *testing.T) { 11 | types := "START" 12 | fmt.Println("local time:") 13 | fmt.Println(time.Now().Format("2006/1/2 15:04:05")) 14 | if time.Now().Hour() >= 12 { 15 | types = "END" 16 | fmt.Println("下班打卡啦~") 17 | } 18 | fmt.Println(types) 19 | } 20 | 21 | func TestWeek(t *testing.T) { 22 | utils.WeeklyPicker(time.Now()) 23 | fmt.Println(utils.RandomSentence()) 24 | } 25 | 26 | func TestRemoteTime(t *testing.T) { 27 | fmt.Println("local time:") 28 | fmt.Println(time.Now().Format("2006/1/2 15:04:05")) 29 | fmt.Println("UTC time:") 30 | fmt.Println(time.Now().UTC().Format("2006/1/2 15:04:05")) 31 | types := utils.TimePicker() 32 | fmt.Println(types) 33 | } 34 | -------------------------------------------------------------------------------- /test/week_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | "towelong/mogu/service" 8 | 9 | "github.com/joho/godotenv" 10 | ) 11 | 12 | func TestWeekDiary(t *testing.T) { 13 | godotenv.Load("../.env") 14 | moguding := service.NewMoGuService() 15 | token, userId := moguding.MoGuLogin(os.Getenv("ACCOUNT"), os.Getenv("PASSWORD")) 16 | planID := moguding.GetPlanID(token, userId) 17 | fmt.Println(moguding.WeeklyDiary(token, planID)) 18 | } 19 | -------------------------------------------------------------------------------- /utils/code.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "strings" 4 | 5 | const ( 6 | // START 上班 7 | START = "START" 8 | // END 下班 9 | END = "END" 10 | // ERROR 运行错误 11 | ERROR = "ERROR" 12 | // WEEK 周报 13 | WEEK = "WEEK" 14 | // NOWEEK 不用写周报 15 | NOWEEK = "NOWEEK" 16 | ) 17 | 18 | var statusMsg = map[string]string{ 19 | START: "上班打卡成功~ 上班签到成功", 20 | END: "下班打卡成功~ 下班签到成功", 21 | WEEK: "周报自动完成~ 已经完成周报了", 22 | NOWEEK: "不在周报时间内~ 今天不用写周报", 23 | ERROR: "系统崩溃了~ o(╥﹏╥)o", 24 | } 25 | 26 | // EnumToMsg 枚举转换成具有意义的字符串 27 | func EnumToMsg(enumValue string) (string, string) { 28 | str, ok := statusMsg[enumValue] 29 | if ok { 30 | slice := strings.Fields(str) 31 | return slice[0], slice[1] 32 | } 33 | errMsg := strings.Fields(statusMsg[ERROR]) 34 | return errMsg[0], errMsg[1] 35 | } 36 | -------------------------------------------------------------------------------- /utils/sentence.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "math/rand" 8 | "net/http" 9 | "time" 10 | "towelong/mogu/model" 11 | ) 12 | 13 | // RandomSentence 生成周报内容 14 | func RandomSentence() (string, error) { 15 | rand.Seed(time.Now().UnixNano()) 16 | var sentence model.SentenceModel 17 | resp, err := http.Get("https://raw.githubusercontent.com/ToWeLong/go-mogu/main/model/sentence.json") 18 | if err != nil { 19 | return "", err 20 | } 21 | defer resp.Body.Close() 22 | res, _ := ioutil.ReadAll(resp.Body) 23 | json.Unmarshal(res, &sentence) 24 | if len(sentence.Data) == 0 { 25 | return "", errors.New("数据总数为0") 26 | } 27 | r := rand.Intn(len(sentence.Data)) 28 | str := sentence.Data[r].Text 29 | return str, nil 30 | } 31 | -------------------------------------------------------------------------------- /utils/sign.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | ) 7 | 8 | // 签名算法 9 | func CreateSign(str string) string { 10 | data := []byte(str) 11 | hash := md5.Sum(data) 12 | return fmt.Sprintf("%x", hash) 13 | } 14 | -------------------------------------------------------------------------------- /utils/timePicker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "time" 4 | 5 | // TimePicker 转成UTC时间进行判断是否上班和下班 6 | func TimePicker() string { 7 | utcHour := time.Now().UTC().Hour() + 8 8 | // I will go off work at 18:00 9 | if utcHour >= 12 && utcHour <= 23 { 10 | // go off work sign 11 | return END 12 | } 13 | return START 14 | } 15 | -------------------------------------------------------------------------------- /utils/weekPicker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | var ( 9 | // WeekDay range[0-6] 10 | WeekDay = map[string]int{ 11 | time.Monday.String(): 1, 12 | time.Tuesday.String(): 2, 13 | time.Wednesday.String(): 3, 14 | time.Thursday.String(): 4, 15 | time.Friday.String(): 5, 16 | time.Saturday.String(): 6, 17 | time.Sunday.String(): 7, 18 | } 19 | ) 20 | 21 | // WeeklyPicker 计算当前为第几周,该周的第一天,该周的最后一天 22 | // return currentWeek, mondayTime, sundayTime 23 | func WeeklyPicker(end time.Time) (int, string, string) { 24 | // 周记开始时间 25 | startWeekTime := "2020-11-30" 26 | start, _ := time.Parse("2006-01-02", startWeekTime) 27 | currentWeek := int(end.Sub(start).Hours())/24/7 + 1 28 | weekDay := WeekDay[end.Weekday().String()] 29 | weekDay2 := WeekDay[end.Weekday().String()] 30 | fmt.Printf("当前为第%v周的第%v天\n", currentWeek, weekDay) 31 | // 计算离星期天还有几天 32 | last := 0 33 | for { 34 | if weekDay == 7 { 35 | break 36 | } 37 | weekDay++ 38 | last++ 39 | } 40 | // 计算离星期一还有几天 41 | first := 0 42 | for { 43 | if weekDay2 == 1 { 44 | break 45 | } 46 | weekDay2-- 47 | first-- 48 | } 49 | firstDay, _ := time.ParseDuration(fmt.Sprintf("%vh", first*24)) 50 | lastDay, _ := time.ParseDuration(fmt.Sprintf("%vh", last*24)) 51 | 52 | mondayTime := end.Add(firstDay).Format("2006-01-02") + " 00:00:00" 53 | sundayTime := end.Add(lastDay).Format("2006-01-02") + " 23:59:59" 54 | fmt.Printf("第%v周的第一天为%v\n第七天为%v\n", currentWeek, mondayTime, sundayTime) 55 | return currentWeek, mondayTime, sundayTime 56 | } 57 | --------------------------------------------------------------------------------