diff --git a/README.md b/README.md index 740eac0..dcc4709 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,14 @@ E5订阅为开发者订阅,只要调用相关API就有可能续期 ## 使用方法 -在机器人对话框输入**/bind**,进入授权页面,用E5订阅的账户登录。 +1. 在机器人对话框输入 **/bind** +2. 注册应用,使用E5主账号或同域账号登录 +3. 复制client_secret和client_id,以 `client_id client_secret`格式回复 +4. 获得授权链接,使用E5主账号或同域账号登录 +5. 授权后会跳转至`http://localhost/e5sub……` +6. 复制整个浏览框内容,在机器人对话框回复 `链接+空格+别名(用于管理账户)` -授权后会跳转至`http://localhost/e5sub……` - -复制整个浏览框内容,在机器人对话框回复 `链接+空格+别名(用于管理账户)` - -例如:`http://localhost/e5sub/?code=abcd mye5`,等待机器人绑定后即完成 +例如:`http://localhost/e5sub/?code=abcd MyE5`,等待机器人绑定后即完成 ## 自行部署 @@ -70,8 +71,6 @@ go build main.go bot_token: xxxxx #不需要socks5代理删去即可 socks5: 127.0.0.1:1080 -#auth_url需要自己去Azure注册应用配置 -auth_url: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?…… #最大可绑定数 bindmax: 3 #mysql配置 @@ -109,18 +108,12 @@ Go在寒假里看过一段时间的《Golang核心编程》+Go官方的教程, 自行部署可能会有报错崩溃什么的,谅解一下吧。。用DEMO也行 -如果有兴趣的大佬欢迎加入群组交流,但有些东西我可能听不太懂。 - ------ 一开始用的sqlite数据库,加载的是 驱动,结果等我写完了,CGO各种编译错误。 最后实在折腾不起来,只好改用MySQL(VPS上都是一键,Win上装MySQL也折腾了半天) ------- - -虽然自知写的垃圾,但也是第一个正式的Go Project…… - ## License GPLv3 \ No newline at end of file diff --git a/control.go b/control.go index 188ea78..a6d1003 100644 --- a/control.go +++ b/control.go @@ -9,18 +9,18 @@ import ( ) //If Successfully return "",else return error information -func BindUser(m *tb.Message) string { +func BindUser(m *tb.Message, cid, cse string) string { fmt.Printf("%d Begin Bind\n", m.Chat.ID) tmp := strings.Split(m.Text, " ") - fmt.Println("alias: " + tmp[1]) if len(tmp) != 2 { fmt.Printf("%d Bind error:Wrong Bind Format\n", m.Chat.ID) return "授权格式错误" } + fmt.Println("alias: " + tmp[1]) alias := tmp[1] code := GetURLValue(tmp[0], "code") fmt.Println(code) - access, refresh := MSFirGetToken(code) + access, refresh := MSFirGetToken(code, cid, cse) if refresh == "" { fmt.Printf("%d Bind error:GetRefreshToken\n", m.Chat.ID) return "获取RefreshToken失败" @@ -42,12 +42,15 @@ func BindUser(m *tb.Message) string { u.msId = Get16MD5Encode(gjson.Get(info, "id").String()) u.uptime = time.Now().Unix() fmt.Println(u.uptime) - u.other = alias + u.alias = alias + u.clientId = cid + u.clientSecret = cse + u.other = "" //u.other = SetJsonValue(u.other, "sign", Get16MD5Encode(u.msId)) //MS User Is Exist - if MSUserIsExist(u.tgId, u.msId) { + if MSAppIsExist(u.tgId, u.clientId) { fmt.Printf("%d Bind error:MSUserHasExisted\n", m.Chat.ID) - return "该ID对应的用户已经绑定过了" + return "该应用已经绑定过了,无需重复绑定" } //MS information has gotten bot.Send(m.Chat, "MS_ID(MD5): "+u.msId+"\nuserPrincipalName: "+gjson.Get(info, "userPrincipalName").String()+"\ndisplayName: "+gjson.Get(info, "displayName").String()+"\n") @@ -66,11 +69,11 @@ func GetBindNum(tgId int64) int { } //return true => exist -func MSUserIsExist(tgId int64, msId string) bool { +func MSAppIsExist(tgId int64, clientId string) bool { data := QueryDataByTG(db, tgId) var res MSData for _, res = range data { - if res.msId == msId { + if res.msId == clientId { return true } } @@ -83,7 +86,7 @@ func SignTask() { fmt.Println("Time:" + time.Now().Format("2006-01-02 15:04:05")) data := QueryDataAll(db) for _, u := range data { - access := MSGetToken(u.refreshToken) + access := MSGetToken(u.refreshToken, u.clientId, u.clientSecret) if access == "" { fmt.Println(u.msId + "Sign ERROR:AccessTokenGet") continue diff --git a/handle.go b/handle.go index d60da0e..aa0a4fd 100644 --- a/handle.go +++ b/handle.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/viper" tb "gopkg.in/tucnak/telebot.v2" "strconv" + "strings" "time" ) @@ -12,6 +13,7 @@ const ( bStartContent string = "欢迎使用E5SubBot!\n请输入\\help查看帮助" bHelpContent string = ` 命令: + /notice 查看最新公告 /my 查看已绑定账户信息 /bind 绑定新账户 /unbind 解绑账户 @@ -21,15 +23,16 @@ const ( ) var ( - UserStatus map[int64]int - BindMaxNum int + UserStatus map[int64]int + UserCid map[int64]string + UserCSecret map[int64]string + BindMaxNum int ) const ( USNone = iota - USUnbind - USWillBind - USBind + USBind1 + USBind2 ) func init() { @@ -42,17 +45,21 @@ func init() { BindMaxNum = viper.GetInt("bindmax") UserStatus = make(map[int64]int) + UserCid = make(map[int64]string) + UserCSecret = make(map[int64]string) } func bStart(m *tb.Message) { bot.Send(m.Sender, bStartContent) + bNotice(m) } func bMy(m *tb.Message) { data := QueryDataByTG(db, m.Chat.ID) var inlineKeys [][]tb.InlineButton for _, u := range data { + fmt.Println(u) inlineBtn := tb.InlineButton{ Unique: "my" + u.msId, - Text: u.other, + Text: u.alias, Data: u.msId, } bot.Handle(&inlineBtn, bMyInlineBtn) @@ -63,26 +70,45 @@ func bMy(m *tb.Message) { func bMyInlineBtn(c *tb.Callback) { r := QueryDataByMS(db, c.Data) u := r[0] - bot.Send(c.Message.Chat, "信息\n别名:"+u.other+"\nMS_ID(MD5): "+u.msId+"\n最近更新时间: "+time.Unix(u.uptime, 0).Format("2006-01-02 15:04:05")) + bot.Send(c.Message.Chat, "信息\n别名:"+u.alias+"\nMS_ID(MD5): "+u.msId+"\nclient_id: "+u.clientId+"\n最近更新时间: "+time.Unix(u.uptime, 0).Format("2006-01-02 15:04:05")) bot.Respond(c) } -func bBind(m *tb.Message) { - tgId := m.Chat.ID - fmt.Println("Auth: " + strconv.FormatInt(tgId, 10)) - bot.Send(m.Chat, "授权链接: [点击直达]("+authUrl+")", tb.ModeMarkdown) - _, err := bot.Send(m.Chat, "回复格式:http://localhost/...+空格+别名(用于管理)", &tb.ReplyMarkup{ForceReply: true}) +func bBind1(m *tb.Message) { + fmt.Println("ReApp: " + strconv.FormatInt(m.Chat.ID, 10)) + bot.Send(m.Chat, "应用注册: [点击直达]("+MSGetReAppUrl()+")", tb.ModeMarkdown) + _, err := bot.Send(m.Chat, "请回复client_id+空格+client_secret", &tb.ReplyMarkup{ForceReply: true}) if err == nil { - UserStatus[m.Chat.ID] = USWillBind + UserStatus[m.Chat.ID] = USBind1 + UserCid[m.Chat.ID] = m.Text } } +func bBind2(m *tb.Message) { + fmt.Println("Auth: " + strconv.FormatInt(m.Chat.ID, 10)) + tmp := strings.Split(m.Text, " ") + if len(tmp) != 2 { + fmt.Printf("%d Bind error:Wrong Bind Format\n", m.Chat.ID) + bot.Send(m.Chat, "错误的格式") + return + } + fmt.Println("client_id: " + tmp[0] + " client_secret" + tmp[1]) + cid := tmp[0] + cse := tmp[1] + bot.Send(m.Chat, "授权账户: [点击直达]("+MSGetAuthUrl(cid)+")", tb.ModeMarkdown) + _, err := bot.Send(m.Chat, "请回复http://localhost/…… + 空格 + 别名(用于管理)", &tb.ReplyMarkup{ForceReply: true}) + if err == nil { + UserStatus[m.Chat.ID] = USBind2 + UserCid[m.Chat.ID] = cid + UserCSecret[m.Chat.ID] = cse + } +} func bUnBind(m *tb.Message) { data := QueryDataByTG(db, m.Chat.ID) var inlineKeys [][]tb.InlineButton for _, u := range data { inlineBtn := tb.InlineButton{ Unique: "unbind" + u.msId, - Text: u.other, + Text: u.alias, Data: u.msId, } bot.Handle(&inlineBtn, bUnBindInlineBtn) @@ -112,7 +138,15 @@ func bOnText(m *tb.Message) { bot.Send(m.Chat, "发送/bind开始绑定嗷") return } - case USWillBind: + case USBind1: + { + if !m.IsReply() { + bot.Send(m.Chat, "请通过回复方式绑定") + return + } + bBind2(m) + } + case USBind2: { if !m.IsReply() { bot.Send(m.Chat, "请通过回复方式绑定") @@ -123,7 +157,7 @@ func bOnText(m *tb.Message) { return } bot.Send(m.Chat, "正在绑定中……") - info := BindUser(m) + info := BindUser(m, UserCid[m.Chat.ID], UserCSecret[m.Chat.ID]) if info == "" { bot.Send(m.Chat, "绑定成功!") } else { diff --git a/main.go b/main.go index 13d39a4..c7c30fc 100644 --- a/main.go +++ b/main.go @@ -90,7 +90,7 @@ func MakeHandle() { fmt.Println("Make Handle……") bot.Handle("/start", bStart) bot.Handle("/my", bMy) - bot.Handle("/bind", bBind) + bot.Handle("/bind", bBind1) bot.Handle("/unbind", bUnBind) bot.Handle("/notice", bNotice) bot.Handle("/help", bHelp) diff --git a/mysql.go b/mysql.go index 7efe02c..17fd02f 100644 --- a/mysql.go +++ b/mysql.go @@ -13,6 +13,9 @@ type MSData struct { refreshToken string msId string uptime int64 + alias string + clientId string + clientSecret string other string } @@ -40,12 +43,12 @@ func init() { //update data by msId func UpdateData(db *sql.DB, u MSData) (bool, error) { - sqlString := `UPDATE users set tg_id=?,refresh_token=?,uptime=?,other=? where ms_id=?` + sqlString := `UPDATE users set tg_id=?,refresh_token=?,uptime=?,alias=?,client_id=?,client_secret=?,other=? where ms_id=?` stmt, err := db.Prepare(sqlString) if err != nil { return false, err } - res, err := stmt.Exec(u.tgId, u.refreshToken, u.uptime, u.other, u.msId) + res, err := stmt.Exec(u.tgId, u.refreshToken, u.uptime, u.alias, u.clientId, u.clientSecret, u.other, u.msId) if err != nil { return false, err } @@ -56,13 +59,13 @@ func UpdateData(db *sql.DB, u MSData) (bool, error) { //add data func AddData(db *sql.DB, u MSData) (bool, error) { sqlString := ` - INSERT INTO users (tg_id, refresh_token,ms_id, uptime,other) - VALUES (?,?,?,?,?)` + INSERT INTO users (tg_id, refresh_token,ms_id, uptime,alias,client_id,client_secret,other) + VALUES (?,?,?,?,?,?,?,?)` stmt, err := db.Prepare(sqlString) if err != nil { return false, err } - _, err = stmt.Exec(u.tgId, u.refreshToken, u.msId, u.uptime, u.other) + _, err = stmt.Exec(u.tgId, u.refreshToken, u.msId, u.uptime, u.alias, u.clientId, u.clientSecret, u.other) if err != nil { return false, err } @@ -86,51 +89,38 @@ func DelData(db *sql.DB, msId string) (bool, error) { } return true, nil } -func QueryDataByMS(db *sql.DB, msId string) []MSData { - rows, err := db.Query("select * from users where ms_id = ?", msId) - CheckErr(err) +func QueryData(rows *sql.Rows) []MSData { + var result = make([]MSData, 0) defer rows.Close() for rows.Next() { var ( - tgIdt, uptimet int64 - refresht, othert, msidt string + tgIdt, uptimet int64 + refresht, othert, msidt, aliast, clientIdt, clientSet string ) - rows.Scan(&tgIdt, &refresht, &msidt, &uptimet, &othert) + rows.Scan(&tgIdt, &refresht, &msidt, &uptimet, &aliast, &clientIdt, &clientSet, &othert) //fmt.Println(string(tgNamet) + "=>" + uptimet.Format("2006-01-02 15:04:05")) - result = append(result, MSData{tgIdt, refresht, msidt, uptimet, othert}) + result = append(result, MSData{tgIdt, refresht, msidt, uptimet, aliast, clientIdt, clientSet, othert}) } return result } +func QueryDataByMS(db *sql.DB, msId string) []MSData { + rows, err := db.Query("select * from users where ms_id = ?", msId) + CheckErr(err) + return QueryData(rows) +} func QueryDataAll(db *sql.DB) []MSData { rows, err := db.Query("select * from users ") CheckErr(err) - var result = make([]MSData, 0) - defer rows.Close() - for rows.Next() { - var refresht, othert, msidt string - var tgIdt, uptimet int64 - rows.Scan(&tgIdt, &refresht, &msidt, &uptimet, &othert) - //fmt.Println(string(tgNamet) + "=>" + uptimet.Format("2006-01-02 15:04:05")) - result = append(result, MSData{tgIdt, refresht, msidt, uptimet, othert}) - } - return result + return QueryData(rows) } //query data by tg_id func QueryDataByTG(db *sql.DB, tgId int64) []MSData { rows, err := db.Query("select * from users where tg_id = ?", tgId) CheckErr(err) - var result = make([]MSData, 0) - defer rows.Close() - for rows.Next() { - var refresht, othert, msidt string - var tgIdt, uptimet int64 - rows.Scan(&tgIdt, &refresht, &msidt, &uptimet, &othert) - result = append(result, MSData{tgIdt, refresht, msidt, uptimet, othert}) - } - return result + return QueryData(rows) } func CreateTB(db *sql.DB) (bool, error) { @@ -139,8 +129,11 @@ func CreateTB(db *sql.DB) (bool, error) { ( tg_id INTEGER, refresh_token TEXT, - ms_id TEXT, + ms_id VARCHAR(255), uptime INTEGER, + alias VARCHAR(255), + client_id VARCHAR(255), + client_secret VARCHAR(255), other TEXT );` _, err := db.Exec(sqltable) diff --git a/outlook.go b/outlook.go index b711784..dab0e26 100644 --- a/outlook.go +++ b/outlook.go @@ -11,15 +11,10 @@ import ( ) const ( - MsApiUrl string = "https://login.microsoftonline.com" - MsGraUrl string = "https://graph.microsoft.com" -) - -var ( - cliId string - redirectUri string - scope string - authUrl string + MsApiUrl string = "https://login.microsoftonline.com" + MsGraUrl string = "https://graph.microsoft.com" + redirectUri string = "http://localhost/e5sub" + scope string = "openid offline_access mail.read user.read" ) func init() { @@ -29,22 +24,24 @@ func init() { if err != nil { panic(fmt.Errorf("Fatal error config file: %s \n", err)) } - authUrl = viper.GetString("auth_url") - cliId = GetURLValue(authUrl, "client_id") - redirectUri, _ = url.QueryUnescape(GetURLValue(authUrl, "redirect_uri")) - scope, _ = url.QueryUnescape(GetURLValue(authUrl, "scope")) - //refreshtoken := "xxxx" - //fmt.Println(MSGetUserInfo(MSGetToken(refreshtoken,"user.read mail.read"))) - //code := "xxx" - //fmt.Println(MSFirGetToken(code)) +} +func MSGetAuthUrl(cid string) string { + return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + cid + "&response_type=code&redirect_uri=" + url.QueryEscape(redirectUri) + "&response_mode=query&scope=" + url.QueryEscape(scope) +} +func MSGetReAppUrl() string { + ru := "https://developer.microsoft.com/en-us/graph/quick-start?appID=_appId_&appName=_appName_&redirectUrl=http://localhost:8000&platform=option-windowsuniversal" + deeplink := "/quickstart/graphIO?publicClientSupport=false&appName=e5sub&redirectUrl=http://localhost/e5sub&allowImplicitFlow=false&ru=" + url.QueryEscape(ru) + app_url := "https://apps.dev.microsoft.com/?deepLink=" + url.QueryEscape(deeplink) + return app_url } //return access_token and refresh_token -func MSFirGetToken(code string) (access string, refresh string) { +func MSFirGetToken(code, cid, cse string) (access string, refresh string) { var r http.Request client := &http.Client{} r.ParseForm() - r.Form.Add("client_id", cliId) + r.Form.Add("client_id", cid) + r.Form.Add("client_secret", cse) r.Form.Add("grant_type", "authorization_code") r.Form.Add("scope", scope) r.Form.Add("code", code) @@ -54,6 +51,7 @@ func MSFirGetToken(code string) (access string, refresh string) { resp, err := client.Do(req) defer resp.Body.Close() content, err := ioutil.ReadAll(resp.Body) + fmt.Println(string(content)) if err != nil { fmt.Println("Fatal error ") } @@ -66,11 +64,12 @@ func MSFirGetToken(code string) (access string, refresh string) { } //return access_token -func MSGetToken(refreshtoken string) (access string) { +func MSGetToken(refreshtoken, cid, cse string) (access string) { var r http.Request client := &http.Client{} r.ParseForm() - r.Form.Add("client_id", cliId) + r.Form.Add("client_id", cid) + r.Form.Add("client_secret", cse) r.Form.Add("grant_type", "refresh_token") r.Form.Add("scope", scope) r.Form.Add("refresh_token", refreshtoken)