diff --git a/model/facebook_page.go b/model/facebook_page.go index 0cece42..91da826 100644 --- a/model/facebook_page.go +++ b/model/facebook_page.go @@ -34,11 +34,11 @@ func GetFacebookPage(id string) (page *FacebookPage, err error) { // AddPage - func (p *FacebookPage) AddPage() (err error) { - rows, err := x.NamedQuery(`insert into "public"."facebook_page" ("id", "lastpost") values (:id, :lastpost) returning *`, p) + stmt, err := x.PrepareNamed(`insert into "public"."facebook_page" ("id", "lastpost") values (:id, :lastpost) returning *`) if err != nil { return err } - err = rows.StructScan(&p) + err = stmt.Get(p, p) return } diff --git a/model/rt_table.go b/model/rt_table.go index d41f857..14d1948 100644 --- a/model/rt_table.go +++ b/model/rt_table.go @@ -25,14 +25,42 @@ func (p *LineFacebookRT) AddRT() (err error) { return } +// DelRT - +func (p *LineFacebookRT) DelRT() (err error) { + _, err = x.NamedExec(`delete from "public"."line_fb_rt" where "line" = :line and "facebook" = :facebook`, p) + return +} + +// GetRT - +func (p *LineFacebookRT) GetRT() (err error) { + stmt, err := x.PrepareNamed(`select * from "public"."line_fb_rt" where "line" = :line and "facebook" = :facebook`) + if err != nil { + return err + } + err = stmt.Get(p, p) + return +} + // AddRT - add twitch line rt func (p *LineTwitchRT) AddRT() (err error) { _, err = x.NamedExec(`insert into "public"."line_twitch_rt" ("line", "twitch", "type", "tmpl") values (:line, :twitch, :type, :tmpl)`, p) return } +// DelRT - +func (p *LineTwitchRT) DelRT() (err error) { + _, err = x.NamedExec(`delete from "public"."line_twitch_rt" where "line" = :line and "twitch" = :twitch and "type" = :type`, p) + return +} + // AddRT - add youtube line rt func (p *LineYoutubeRT) AddRT() (err error) { _, err = x.NamedExec(`insert into "public"."line_youtube_rt" ("line", "youtube", "tmpl") values (:line, :youtube, :tmpl)`, p) return } + +// DelRT - +func (p *LineYoutubeRT) DelRT() (err error) { + _, err = x.NamedExec(`delete from "public"."line_youtube_rt" where "line" = :line and "youtube" = :youtube`, p) + return +} diff --git a/model/twitch_channel.go b/model/twitch_channel.go index 2fc14f5..b5e2d5f 100644 --- a/model/twitch_channel.go +++ b/model/twitch_channel.go @@ -32,13 +32,24 @@ func GetJoinChatChannel() (channels []*TwitchChannel, err error) { return } -// Add - -func (p *TwitchChannel) Add() (err error) { - rows, err := x.NamedQuery(`insert into "public"."twitch_channel" ("name", "laststream", "join", "opayid") values (:name, :laststream, :join, :opayid) returning *`, p) +// GetWithName - +func (p *TwitchChannel) GetWithName() (err error) { + stmt, err := x.PrepareNamed(`select * from "public"."twitch_channel" where "name" = :name`) if err != nil { return err } - err = rows.StructScan(p) + + err = stmt.Get(p, p) + return +} + +// Add - +func (p *TwitchChannel) Add() (err error) { + stmt, err := x.PrepareNamed(`insert into "public"."twitch_channel" ("name", "laststream", "join", "opayid") values (:name, :laststream, :join, :opayid) returning *`) + if err != nil { + return err + } + err = stmt.Get(p, p) return } diff --git a/model/youtube_channel.go b/model/youtube_channel.go index 3559411..6a22d44 100644 --- a/model/youtube_channel.go +++ b/model/youtube_channel.go @@ -2,12 +2,55 @@ package model import "time" +// YoutubeGroup - +type YoutubeGroup struct { + *LineGroup + Tmpl string `db:"tmpl"` +} + // YoutubeChannel - type YoutubeChannel struct { - ID string `db:"id" cc:"id"` - Name string `db:"name" cc:"name"` - LastVideo string `db:"lastvideo" cc:"lastvideo"` - Expire int32 `db:"expire" cc:"expire"` - Ctime time.Time `db:"ctime" cc:"ctime"` - Mtime time.Time `db:"mtime" cc:"mtime"` + ID string `db:"id" cc:"id"` + Name string `db:"name" cc:"name"` + LastVideo string `db:"lastvideo" cc:"lastvideo"` + Expire int64 `db:"expire" cc:"expire"` + Ctime time.Time `db:"ctime" cc:"ctime"` + Mtime time.Time `db:"mtime" cc:"mtime"` + Groups []*YoutubeGroup `db:"-"` +} + +// GetYoutubeChannelWithID - +func GetYoutubeChannelWithID(id string) (yt *YoutubeChannel, err error) { + err = x.Get(&yt, `select * from "public"."youtube_channel" where "id" = $1`, id) + return +} + +// UpdateLastVideo - +func (p *YoutubeChannel) UpdateLastVideo(vid string) (err error) { + p.LastVideo = vid + _, err = x.NamedExec(`update "public"."youtube_channel" set "lastvideo" = :lastvideo where "id" = :id`, p) + return +} + +// UpdateExpire - +func (p *YoutubeChannel) UpdateExpire(t int64) (err error) { + p.Expire = t + _, err = x.NamedExec(`update "public"."youtube_channel" set "expire" = :expire where "id" = :id`, p) + if err != nil { + return err + } + return +} + +// GetGroups - +func (p *YoutubeChannel) GetGroups() (err error) { + query := `select g.*, rt.tmpl as tmpl from "public"."youtube_channel" yt + left join "public"."line_youtube_rt" rt + on rt.youtube = yt.id + left join "public"."line_group" g + on g.id = rt.line + where + yt.id = $1` + err = x.Select(&p.Groups, query, p.ID) + return } diff --git a/module/message-command/line-group.go b/module/message-command/line-group.go index 3d3b829..a0c604f 100644 --- a/module/message-command/line-group.go +++ b/module/message-command/line-group.go @@ -18,6 +18,10 @@ func selectAct(cmd, sub, txt string, s *lineobj.SourceObject) (res string) { return addFacebookPage(sub, txt, s) case "addtwitch": return addTwitchChannel(sub, txt, s) + case "delpage": + return delFacebookPage(sub, txt, s) + case "deltwitch": + return delTwitchChannel(sub, txt, s) } return } @@ -48,18 +52,29 @@ func addLineGroup(sub, txt string, s *lineobj.SourceObject) (res string) { return "Success" } -func addFacebookPage(sub, txt string, s *lineobj.SourceObject) (res string) { - // args = pageid tmpl +func checkGroupOwner(s *lineobj.SourceObject) (ok bool, err error) { exists, err := model.CheckGroup(s.GroupID) if err != nil { - return "run check group error" + return false, err } if !exists { - return "group not exists" + return false, nil } - ok, err := model.CheckGroupOwner(s.UserID, s.GroupID) + ok, err = model.CheckGroupOwner(s.UserID, s.GroupID) if err != nil { - return "run check group owner fail" + return false, err + } + if !ok { + return false, nil + } + return true, nil +} + +func addFacebookPage(sub, txt string, s *lineobj.SourceObject) (res string) { + // args = pageid tmpl + ok, err := checkGroupOwner(s) + if err != nil { + return "check group fail" } if !ok { return "not owner" @@ -98,18 +113,47 @@ func addFacebookPage(sub, txt string, s *lineobj.SourceObject) (res string) { return "Success" } +func delFacebookPage(sub, txt string, s *lineobj.SourceObject) (res string) { + // args = pageid + ok, err := checkGroupOwner(s) + if err != nil { + return "check group fail" + } + if !ok { + return "not owner" + } + + args := strings.Split(strings.Trim(txt, " "), " ") + if len(args) < 1 { + return "commage arg not match" + } + + rt := &model.LineFacebookRT{ + Line: s.GroupID, + Facebook: args[0], + } + err = rt.DelRT() + if err != nil { + return "remove facebook page fail" + } + + return "Success" +} + +func checkTwitchType(t string) bool { + switch t { + case "live": + default: + return false + } + return true +} + func addTwitchChannel(sub, txt string, s *lineobj.SourceObject) (res string) { // args = twitchLogin type tmpl - exists, err := model.CheckGroup(s.GroupID) + ok, err := checkGroupOwner(s) if err != nil { - return "run check group error" - } - if !exists { - return "group not exists" - } - ok, err := model.CheckGroupOwner(s.UserID, s.GroupID) - if err != nil { - return "run check group owner fail" + return "check group fail" } if !ok { return "not owner" @@ -120,6 +164,10 @@ func addTwitchChannel(sub, txt string, s *lineobj.SourceObject) (res string) { return "command args not match" } + if !checkTwitchType(args[1]) { + return "type not allow" + } + info := twitch.GetUserDataByName(args[0]) if info == nil { return "get twitch user id fail" @@ -137,6 +185,7 @@ func addTwitchChannel(sub, txt string, s *lineobj.SourceObject) (res string) { rt := &model.LineTwitchRT{ Line: s.GroupID, Twitch: info.ID, + Type: args[1], Tmpl: strings.Join(args[2:], " "), } err = rt.AddRT() @@ -146,3 +195,47 @@ func addTwitchChannel(sub, txt string, s *lineobj.SourceObject) (res string) { return "Success" } + +func delTwitchChannel(sub, txt string, s *lineobj.SourceObject) (res string) { + // args = twitchLogin type + ok, err := checkGroupOwner(s) + if err != nil { + return "check group fail" + } + if !ok { + return "not owner" + } + + args := strings.Split(strings.Trim(txt, " "), " ") + if len(args) < 2 { + return "command arg not match" + } + + if !checkTwitchType(args[1]) { + return "type not allow" + } + + ch := &model.TwitchChannel{ + Name: args[0], + } + err = ch.GetWithName() + if err != nil { + return "get channel data fail" + } + if ch == nil { + return "Success" + } + + rt := &model.LineTwitchRT{ + Line: s.GroupID, + Twitch: ch.ID, + Type: args[1], + } + + err = rt.DelRT() + if err != nil { + return "delete rt fail" + } + + return "Success" +} diff --git a/router/google/youtube.go b/router/google/youtube.go new file mode 100644 index 0000000..b94b742 --- /dev/null +++ b/router/google/youtube.go @@ -0,0 +1,135 @@ +package google + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "strings" + "time" + + "git.trj.tw/golang/mtfosbot/model" + lineapi "git.trj.tw/golang/mtfosbot/module/apis/line" + "git.trj.tw/golang/mtfosbot/module/context" +) + +type feed struct { + XMLName xml.Name `xml:"feed"` + Entry []entry `xml:"entry"` +} +type entry struct { + XMLName xml.Name `xml:"entry"` + Title string `xml:"title"` + ID string `xml:"id"` + Link link `xml:"link"` + Author []author `xml:"author"` +} +type link struct { + XMLName xml.Name `xml:"link"` + Href string `xml:"href,attr"` +} +type author struct { + XMLName xml.Name `xml:"author"` + Name string `xml:"name"` + URI string `xml:"uri"` +} + +// VerifyWebhook - +func VerifyWebhook(c *context.Context) { + hubMode, ok := c.GetQuery("hub.mode") + if !ok { + c.DataFormat(nil) + return + } + challenge, ok := c.GetQuery("hub.challenge") + if !ok { + c.DataFormat(nil) + return + } + id, ok := c.GetQuery("id") + if !ok { + c.DataFormat(nil) + return + } + if hubMode == "subscribe" { + t := time.Now().Unix() + 86400 + yt, err := model.GetYoutubeChannelWithID(id) + if err != nil { + c.ServerError(nil) + return + } + if yt == nil { + c.NotFound("channel not found") + return + } + err = yt.UpdateExpire(t) + if err != nil { + c.ServerError(nil) + } + } + c.String(200, challenge) +} + +// GetNotifyWebhook - +func GetNotifyWebhook(c *context.Context) { + byteBody, err := ioutil.ReadAll(c.Request.Body) + defer c.Request.Body.Close() + if err != nil { + c.DataFormat(nil) + return + } + + id, ok := c.GetQuery("id") + if !ok { + c.DataFormat(nil) + return + } + + hook := &feed{} + err = xml.Unmarshal(byteBody, &hook) + if err != nil { + c.DataFormat(nil) + return + } + + if len(hook.Entry) == 0 { + c.Success(nil) + return + } + + yt, err := model.GetYoutubeChannelWithID(id) + if err != nil || yt == nil { + c.ServerError(nil) + return + } + + if hook.Entry[0].ID == yt.LastVideo { + c.Success(nil) + return + } + + err = yt.UpdateLastVideo(hook.Entry[0].ID) + if err != nil { + c.ServerError(nil) + return + } + + for _, v := range yt.Groups { + if v.Notify { + str := v.Tmpl + if len(str) == 0 { + str = fmt.Sprintf("%s\n%s", hook.Entry[0].Title, hook.Entry[0].Link.Href) + } else { + str = strings.Replace(str, "{link}", hook.Entry[0].Link.Href, -1) + str = strings.Replace(str, "{txt}", hook.Entry[0].Title, -1) + } + + msg := &lineapi.TextMessage{ + Text: str, + } + + lineapi.PushMessage(v.ID, msg) + } + } + + c.Success(nil) +} diff --git a/router/routes/routes.go b/router/routes/routes.go index f81a4c4..1d60866 100644 --- a/router/routes/routes.go +++ b/router/routes/routes.go @@ -5,6 +5,8 @@ import ( "git.trj.tw/golang/mtfosbot/module/context" "git.trj.tw/golang/mtfosbot/router/api" + "git.trj.tw/golang/mtfosbot/router/google" + "git.trj.tw/golang/mtfosbot/router/line" "github.com/gin-contrib/cors" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" @@ -43,4 +45,15 @@ func SetRoutes(r *gin.Engine) { { apiGroup.POST("/login", context.PatchCtx(api.UserLogin)) } + + lineApis := r.Group("/line") + { + lineApis.POST("/", context.PatchCtx(line.GetRawBody), context.PatchCtx(line.VerifyLine), context.PatchCtx(line.GetLineMessage)) + } + + googleApis := r.Group("/google") + { + googleApis.GET("/youtube/webhook", context.PatchCtx(google.VerifyWebhook)) + googleApis.POST("/youtube/webhook", context.PatchCtx(google.GetNotifyWebhook)) + } }