go-common/app/job/main/reply/service/notify.go
2019-04-22 18:49:16 +08:00

569 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
artmdl "go-common/app/interface/openplatform/article/model"
model "go-common/app/job/main/reply/model/reply"
accmdl "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
arcmdl "go-common/app/service/main/archive/model/archive"
epmdl "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
"go-common/library/log"
)
const (
_mcReply = "1_1_1"
_mcCntArticle = "1_1_4"
_mcCntDynamic = "1_1_5"
_mcCntClip = "1_1_6"
_mcCntAlbum = "1_1_7"
_mcCntArchive = "1_1_8"
_msgTitleSize = 40
_msgContentSize = 80
)
func (s *Service) notifyReply(c context.Context, sub *model.Subject, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if len(rp.Content.Ats) > 0 {
title, link, jump, nativeJump, msg := s.messageInfo(c, rp)
if link != "" {
atmt := fmt.Sprintf("#{%s}{\"%s\"}评论中@了你", title, link)
cont := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
if err := s.messageDao.At(c, rp.Mid, rp.Content.Ats, atmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.At failed , mid(%d) err(%v)", rp.Mid, err)
}
}
}
if err := s.notifyCnt(c, sub, rp); err != nil {
log.Error("s.notifyCnt(%v,%v) error(%v)", sub, rp, err)
}
})
}
func (s *Service) notifyReplyReply(c context.Context, sub *model.Subject, rootRp, parentRp, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if err := s.notifyCnt(c, sub, rp); err != nil {
log.Error("s.notifyCnt(%v,%v) error(%v)", sub, rp, err)
}
// notify parent reply
if rp.Mid == rootRp.Mid && rp.Root == rp.Parent && len(rp.Content.Ats) == 0 {
return
}
title, link, jump, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
// 5.29 改为根评论内容推送
//rpmt = fmt.Sprintf("#{%s}{\"%s\"}评论中回复了你", title, link)
rpmt := []rune(parentRp.Content.Message)
if len(rpmt) > _msgContentSize {
rpmt = rpmt[:_msgContentSize]
}
atmt := fmt.Sprintf("#{%s}{\"%s\"}评论中@了你", title, link)
cont := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
// notify
if rp.Mid != rootRp.Mid && !s.getBlackListRelation(c, rootRp.Mid, rp.Mid) {
if err := s.messageDao.Reply(c, _mcReply, "", rp.Mid, rootRp.Mid, string(rpmt), cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", rp.Mid, rootRp.Mid, err)
}
}
if rp.Root != rp.Parent {
if parentRp != nil && rootRp.Mid != parentRp.Mid && rp.Mid != parentRp.Mid && !s.getBlackListRelation(c, parentRp.Mid, rp.Mid) {
if err := s.messageDao.Reply(c, _mcReply, "", rp.Mid, parentRp.Mid, string(rpmt), cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", rp.Mid, parentRp.Mid, err)
}
}
}
var ats []int64
for _, mid := range rp.Content.Ats {
if mid != parentRp.Mid {
ats = append(ats, mid)
}
}
if len(ats) > 0 {
if err := s.messageDao.At(c, rp.Mid, ats, atmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.At failed , mid(%d), err(%v)", rp.Mid, err)
}
}
})
}
func (s *Service) notifyLike(c context.Context, mid int64, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if ok, num := s.notifyLikeNum(c, rp, mid); ok {
_, _, jump, nativeJump, msg := s.messageInfo(c, rp)
if jump == "" {
return
}
// NOTE content and title is opposite
cont := fmt.Sprintf("等%d人赞了你的回复", num)
rpmt := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
if err := s.messageDao.Like(c, mid, rp.Mid, rpmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", mid, rp.Mid, err)
}
} else {
log.Warn("Didn't satify notify condition, omit notify!")
}
})
}
// notifyLike check if need notify user when receive like
func (s *Service) notifyLikeNum(c context.Context, rp *model.Reply, mid int64) (ok bool, num int64) {
if rp.Mid == mid || rp.Like <= 0 {
ok = false
return
}
num = int64(rp.Like)
// NOTE if num >1000 send when num%1000==0
if num < 10 || (num < 100 && num%10 == 0) || (num < 1000 && num%100 == 0) || num%1000 == 0 {
ok = true
}
return
}
func (s *Service) notifyCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
max, err := s.dao.Redis.NotifyCnt(c, sub.Oid, sub.Type)
if err != nil {
log.Error("redis.NotifyCnt(%d,%d) error(%v)", sub.Oid, sub.Type, err)
return
}
if sub.ACount <= max {
log.Warn("notifyCnt ignore oid:%d type:%d current:%d max:%d", sub.Oid, sub.Type, sub.ACount, max)
return
}
if err = s.dao.Redis.SetNotifyCnt(c, sub.Oid, sub.Type, sub.ACount); err != nil {
log.Error("redis.SetNotifyCnt(%d,%d,%d) error(%v)", sub.Oid, sub.Type, sub.ACount, err)
return
}
switch sub.Type {
case model.SubTypeVideo:
return s.notifyArchiveCnt(c, sub, rp)
case model.SubTypeArticle:
return s.notifyArticleCnt(c, sub, rp)
case model.SubTypeDynamic:
return s.notifyDynamicCnt(c, sub, rp, _mcCntDynamic)
case model.SubTypeLiveVideo:
return s.notifyDynamicCnt(c, sub, rp, _mcCntClip)
case model.SubTypeLivePicture:
return s.notifyDynamicCnt(c, sub, rp, _mcCntAlbum)
default:
return
}
}
func (s *Service) notifyDynamicCnt(c context.Context, sub *model.Subject, rp *model.Reply, mc string) (err error) {
if !shouldNotifyLow(sub.ACount) {
return
}
title, link, _, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("#{%s}{\"%s\"}收到了第%d条评论", title, link, sub.ACount)
notifyContent := fmt.Sprintf("#{%s}{\"%s\"}", msg, link)
if err = s.messageDao.Reply(c, mc, resID, rp.Mid, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func (s *Service) notifyArchiveCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !shouldNotifyLow(sub.ACount) {
return
}
title, link, _, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("你的投稿收到了第%d条评论", sub.ACount)
notifyContent := fmt.Sprintf("你投稿的视频“#{%s}{\"%s\"}”收到了第%d条评论『%s』", title, link, sub.ACount, msg)
if err = s.messageDao.System(c, _mcCntArchive, resID, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.System(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func (s *Service) notifyArticleCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !shouldNotifyMiddle(sub.ACount) {
return
}
title, link, _, nativeJump, _ := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("你的专栏文章评论数达到了%d", sub.ACount)
notifyContent := fmt.Sprintf("你投稿的专栏文章“#{%s}{\"%s\"}”评论数达到了%d去回应一下大家的评论吧 #{点击前往}{\"%s\"}", title, link, sub.ACount, link)
if err = s.messageDao.System(c, _mcCntArticle, resID, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.System(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func shouldNotifyLow(n int) (ok bool) {
switch {
case n <= 0:
ok = false
case n == 1 || n == 10 || n == 30 || n == 50:
ok = true
case n <= 1000:
ok = (n%100 == 0)
default:
ok = (n%10000 == 0)
}
return
}
func shouldNotifyMiddle(n int) (ok bool) {
switch {
case n <= 0:
ok = false
case n <= 10:
ok = true
case n <= 100:
ok = (n%10 == 0)
case n <= 1000:
ok = (n%100 == 0)
default:
ok = (n%10000 == 0)
}
return
}
// filterViolationMsg every two characters, the third character processing for *.
func filterViolationMsg(msg string) string {
s := []rune(msg)
for i := 0; i < len(s); i++ {
if i%3 != 0 {
s[i] = '*'
}
}
return string(s)
}
// moralAndNotify del moral and notify user.
func (s *Service) moralAndNotify(c context.Context, rp *model.Reply, moral int, notify bool, rptMid, adid int64, adname, remark string, reason, freason int8, ftime int64, isPunish bool) (err error) {
title, link, _, _, msg := s.messageInfo(c, rp)
smsg := []rune(msg)
if len(smsg) > 50 {
smsg = smsg[:50]
}
if moral > 0 {
reason := "发布的评论违规并被管理员删除 - " + string(smsg)
if rptMid > 0 {
reason = "发布的评论被举报并被管理员删除 - " + string(smsg)
}
arg := &accmdl.MoralReq{
Mid: rp.Mid,
Moral: -float64(moral),
Oper: adname,
Reason: reason,
Remark: remark,
}
if _, err = s.accSrv.AddMoral3(c, arg); err != nil {
log.Error("s.accSrv.AddMoral3(%d) error(%v)", rp.Mid, err)
}
}
msg = filterViolationMsg(msg)
if title != "" && link != "" && rptMid > 0 {
if err = s.reportNotify(c, rp, title, link, msg, ftime, reason, freason, isPunish); err != nil {
log.Error("s.reportNotify(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
}
}
if !notify {
return
}
if title != "" && link != "" {
// notify message
mt := "评论违规处理通知"
mc := fmt.Sprintf("您好,您在#{%s}{\"%s\"}下的评论 『%s』 ", title, link, msg)
if rptMid > 0 {
mc = fmt.Sprintf("您好,根据用户举报,您在#{%s}{\"%s\"}下的评论 『%s』 ", title, link, msg)
}
if isPunish {
mc += ",已被处罚"
} else {
mc += ",已被移除"
}
// forbidden
if ftime > 0 {
mc += fmt.Sprintf(",并被封禁%d天。", ftime)
} else if ftime == -1 {
mc += ",并被永久封禁。"
} else {
mc += "。"
}
// forbid reason
if ar, ok := model.ForbidReason[freason]; ok {
mc += "理由:" + ar + "。"
// community rules
switch {
case freason == model.ForbidReasonSpoiler || freason == model.ForbidReasonAd || freason == model.ForbidReasonUnlimitedSign || freason == model.ForbidReasonMeaningless:
mc += model.NotifyComRules
case freason == model.ForbidReasonProvoke || freason == model.ForbidReasonAttack:
mc += model.NotifyComProvoke
default:
mc += model.NofityComProhibited
}
} else { // report reason
if ar, ok := model.ReportReason[reason]; ok {
mc += "理由:" + ar + "。"
}
// community rules
switch {
case reason == model.ReportReasonSpoiler || reason == model.ReportReasonAd || reason == model.ReportReasonUnlimitedSign || reason == model.ReportReasonMeaningless:
mc += model.NotifyComRules
case reason == model.ReportReasonUnrelated:
mc += model.NotifyComUnrelated
case reason == model.ReportReasonProvoke || reason == model.ReportReasonAttack:
mc += model.NotifyComProvoke
default:
mc += model.NofityComProhibited
}
}
// send the message
if err = s.messageDao.DeleteReply(c, rp.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.messageDao.DeleteReply failed, (%d) error(%v)", rp.Mid, err)
}
log.Info("notify oid:%d type:%d rpID:%d reason:%d content:%s", rp.Oid, rp.Type, rp.RpID, reason, mc)
} else {
log.Warn("no notify oid:%d type:%d rpid:%d", rp.Oid, rp.Type, rp.RpID)
}
return
}
func (s *Service) reportNotify(c context.Context, rp *model.Reply, title, link, msg string, ftime int64, reason, freason int8, isPunish bool) (err error) {
var (
rptUser *model.ReportUser
rptUsers map[int64]*model.ReportUser
)
mt := "举报处理结果通知"
mc := fmt.Sprintf("您好,您在#{%s}{\"%s\"}下举报的评论 『%s』 ", title, link, msg)
if isPunish {
mc += "已被处罚"
} else {
mc += "已被移除"
}
// forbidden
if ftime > 0 {
mc += fmt.Sprintf(",并被封禁%d天。", ftime)
} else if ftime == -1 {
mc += ",该用户已被永久封禁。"
} else {
mc += "。"
}
// forbid reason
if ar, ok := model.ForbidReason[freason]; ok {
mc += "理由:" + ar + "。"
} else if ar, ok := model.ReportReason[reason]; ok {
mc += "理由:" + ar + "。"
}
// community rules
mc += model.NotifyComRulesReport
if rptUsers, err = s.dao.Report.GetUsers(c, rp.Oid, rp.Type, rp.RpID); err != nil {
log.Error("reportUser.GetUsers(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
return
}
for _, rptUser = range rptUsers {
// send the message
if err = s.messageDao.AcceptReport(c, rptUser.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.messageDao.DeleteReply failed, (%d) error(%v)", rp.Mid, err)
}
}
if _, err = s.dao.Report.SetUserReported(c, rp.Oid, rp.Type, rp.RpID, rp.MTime.Time()); err != nil {
log.Error("s.dao.Report.SetUserReported(%d, %d, %d) error(%v)", rp.Oid, rp.Type, rp.RpID)
}
return
}
func (s *Service) messageInfo(c context.Context, rp *model.Reply) (title, link, jump, nativeJump, msg string) {
var (
err error
native bool
subType int
extraIntentID int64
)
switch rp.Type {
case model.SubTypeVideo:
var (
m *api.Arc
uri *url.URL
)
arg := &arcmdl.ArgAid2{
Aid: rp.Oid,
}
m, err = s.arcSrv.Archive3(c, arg)
if err != nil || m == nil {
log.Error("s.arcSrv.Archive3(%v) ret:%v error(%v)", arg, m, err)
return
}
if m.AttrVal(arcmdl.AttrBitIsBangumi) == 1 {
req := &epmdl.EpAidReq{
Aids: []int32{int32(rp.Oid)},
}
resp, err1 := s.bangumiSrv.ListByAids(c, req)
if err1 != nil {
log.Error("s.bangumiSrv.ListByAids(%v, %v) error(%v)", c, req, err1)
return
}
if resp.Infos[int32(rp.Oid)] != nil {
extraIntentID = int64(resp.Infos[int32(rp.Oid)].EpisodeId)
}
subType = 1
}
if m.RedirectURL != "" {
// NOTE mobile jump
if uri, err = url.Parse(m.RedirectURL); err == nil {
q := uri.Query()
q.Set("aid", strconv.FormatInt(rp.Oid, 10))
uri.RawQuery = q.Encode()
link = uri.String()
}
} else {
link = fmt.Sprintf("http://www.bilibili.com/video/av%d/", rp.Oid)
}
title = m.Title
native = true
case model.SubTypeTopic:
if title, link, err = s.noticeDao.Topic(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Topic(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeDrawyoo:
if title, link, err = s.noticeDao.Drawyoo(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Drawyoo(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeActivity:
if title, link, err = s.noticeDao.Activity(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Activity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeForbiden:
title, link, err = s.noticeDao.Ban(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeNotice:
title, link, err = s.noticeDao.Notice(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeActArc:
if title, link, err = s.noticeDao.ActivitySub(c, rp.Oid); err != nil {
log.Error("s.noticeDao.ActivitySub(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeArticle:
var m map[int64]*artmdl.Meta
arg := &artmdl.ArgAids{
Aids: []int64{rp.Oid},
}
m, err = s.articleSrv.ArticleMetas(c, arg)
if err != nil || m == nil {
log.Error("s.articleSrv.ArticleMetas(%v) ret:%v error(%v)", arg, m, err)
return
}
if meta, ok := m[rp.Oid]; ok {
title = meta.Title
link = fmt.Sprintf("http://www.bilibili.com/read/cv%d", rp.Oid)
}
case model.SubTypeLiveVideo:
if title, link, err = s.noticeDao.LiveSmallVideo(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveSmallVideo(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeLiveAct:
if title, link, err = s.noticeDao.LiveActivity(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveActivity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeLiveNotice:
//if title, link, err = s.noticeDao.LiveNotice(c, rp.Oid); err != nil {
// log.Error("s.noticeDao.LiveNotice(%d) error(%v)", rp.Oid, err)
// return
//}
// NOTE 忽略直播公告跳转链接
return
case model.SubTypeLivePicture:
if title, link, err = s.noticeDao.LivePicture(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LivePiture(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeCredit:
if title, link, err = s.noticeDao.Credit(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Credit(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeDynamic:
if title, link, err = s.noticeDao.Dynamic(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Dynamic(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeAudio:
if title, link, err = s.noticeDao.Audio(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Audio(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeAudioPlaylist:
if title, link, err = s.noticeDao.AudioPlayList(c, rp.Oid); err != nil {
log.Error("s.noticeDao.AudioPlayList(%d) error(%v)", rp.Oid, err)
return
}
native = true
default:
return
}
tmp := []rune(title)
if len(tmp) > _msgTitleSize {
title = string(tmp[:_msgTitleSize])
}
jump = fmt.Sprintf("%s#reply%d", link, rp.RpID)
tmp = []rune(rp.Content.Message)
if len(tmp) > _msgContentSize {
msg = string(tmp[:_msgContentSize])
} else {
msg = rp.Content.Message
}
if native {
rootID := rp.Root
if rootID == 0 {
rootID = rp.RpID
}
nativeJump = fmt.Sprintf("bilibili://comment/detail/%d/%d/%d/?subType=%d&anchor=%d&showEnter=1&extraIntentId=%d", rp.Type, rp.Oid, rootID, subType, rp.RpID, extraIntentID)
}
return
}
func extraInfo(newJump string) string {
var a = struct {
CmNewURL struct {
Title string `json:"title"`
Content string `json:"content"`
} `json:"cm_new_url"`
}{
CmNewURL: struct {
Title string `json:"title"`
Content string `json:"content"`
}{
Title: newJump,
Content: newJump,
},
}
b, _ := json.Marshal(a)
return string(b)
}