Skip to content

Commit

Permalink
Merge pull request #33 from kounoike:feature/autosearch-only-ok-reaction
Browse files Browse the repository at this point in the history
OKリアクションをしたときにEPG検索しに行くように変更
  • Loading branch information
kounoike authored Mar 26, 2023
2 parents 0f34510 + abca435 commit 04360f3
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 69 deletions.
10 changes: 9 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"dockerComposeFile": "docker-compose.yml",
"service": "devcontainer",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
Expand All @@ -16,4 +16,12 @@
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
,
"customizations": {
"vscode": {
"extensions": [
"github.vscode-pull-request-github"
]
}
}
}
4 changes: 0 additions & 4 deletions .devcontainer/devcontainer/Dockerfile

This file was deleted.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ mirakc/dtv-discord-goなどの更新は./docker-composeディレクトリで`./u

### 自動検索

`録画-通知・予約`というカテゴリーの下に`自動検索`というチャンネルが作成されています。このチャンネルに特定の形式でスレッドを投稿すると、その内容が自動検索機能の対象になります。投稿のタイトルは何でも構いません。本文は以下のように指定します。
`録画-通知・予約`というカテゴリーの下に`自動検索`というチャンネルが作成されています。このチャンネルに特定の形式でスレッドを投稿し、🆗(`:ok:`)でリアクションすると、その内容が自動検索機能の対象になります。投稿のタイトルは何でも構いません。本文は以下のように指定します。

例1: 「NHK」を含む名前のチャンネルでタイトルに「ニュース」をタイトルに含む番組を自動検索する

Expand All @@ -116,7 +116,7 @@ mirakc/dtv-discord-goなどの更新は./docker-composeディレクトリで`./u

EPGが更新される度にこれらのルールがチェックされ、ルールに合う番組が見つかると、そのスレッドにぶら下がる発言で見つかった番組の情報が投稿されます。

スレッドの発言に対して📼リアクションをしても録画はされないので注意してください。発言の中にある URL が`録画-番組情報`の発言へのリンクなので、そちらのメッセージに対してリアクションをしてください
スレッドの発言に対して📼リアクションをしても録画はされないので注意してください。発言の中にある URL が`録画-番組情報`の発言へのリンクなので、そちらのメッセージに対してリアクションするか、スレッドの最初の発言に📼でリアクションしてください


### 自動検索・通知
Expand All @@ -129,7 +129,7 @@ EPGが更新される度にこれらのルールがチェックされ、ルー

## 注意事項

現時点では番組情報投稿、自動検索はEPGの更新で追加された番組情報に対してだけ有効です。途中で書き換わった番組情報については対処していません。また、自動検索のスレッドの投稿時に既に番組情報に投稿されている番組についてはまだ検索するようになっていません。
現時点では番組情報投稿、自動検索はEPGの更新で追加された番組情報に対してだけ有効です。途中で書き換わった番組情報については対処していません。

録画予約について、チューナーの数を考慮していないため、チューナー数より多い数の番組を同時に録画予約した場合録画できない番組が出てきます。特に警告などもないので注意してください。

Expand All @@ -139,7 +139,7 @@ EPGが更新される度にこれらのルールがチェックされ、ルー

https://github.com/users/kounoike/projects/2/views/1 にもありますが、大きなものは以下

- 自動検索スレッドに追加したときにその時点のEPGに対して検索をかけたい
- EPG更新時にも自動検索を走らせる
- 録画完了・失敗時の通知・発言を追加する
- チューナー不足時の処理を考える
- 過去の番組情報をどうするか考える
Expand Down
31 changes: 24 additions & 7 deletions discord/discord_client/discordclient.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package discord_client

import (
"errors"
"fmt"
"strings"

Expand All @@ -26,7 +27,7 @@ func NewDiscordClient(cfg config.Config, queries *db.Queries, logger *zap.Logger
if err != nil {
return nil, err
}
session.Identify.Intents = discordgo.IntentsMessageContent
session.Identify.Intents |= discordgo.IntentsMessageContent
return &DiscordClient{
cfg: cfg,
queries: queries,
Expand All @@ -41,12 +42,17 @@ func (d *DiscordClient) Session() *discordgo.Session {
return d.session
}

func (d *DiscordClient) GetChannel(channelID string) (*discordgo.Channel, error) {
return d.session.Channel(channelID)
}

func (d *DiscordClient) GetChannelMessage(channelID string, messageID string) (*discordgo.Message, error) {
return d.session.ChannelMessage(channelID, messageID)
}

func (d *DiscordClient) MessageReactionAdd(channelID string, messageID string, emoji string) error {
return d.session.MessageReactionAdd(channelID, messageID, emoji)
err := d.session.MessageReactionAdd(channelID, messageID, emoji)
return err
}

func (d *DiscordClient) MessageReactionRemove(channelID string, messageID string, emoji string) error {
Expand All @@ -66,6 +72,9 @@ func (d *DiscordClient) Open() error {
if err != nil {
return err
}
if len(d.session.State.Guilds) != 1 {
return errors.New("bot must join exactly one server")
}
return nil
}

Expand Down Expand Up @@ -143,7 +152,7 @@ func (d *DiscordClient) GetCachedChannel(origCategory string, origChannelName st

func (d *DiscordClient) SendMessage(category string, channel string, message string) (*discordgo.Message, error) {
if len(d.session.State.Guilds) != 1 {
return nil, fmt.Errorf("discord app must join one server")
return nil, fmt.Errorf("discord app must join one server [%d]", len(d.session.State.Guilds))
}
ch, err := d.GetCachedChannel(category, channel)
if err != nil {
Expand Down Expand Up @@ -210,7 +219,7 @@ func (d *DiscordClient) CreateNotifyAndScheduleChannel() (*discordgo.Channel, er
return d.createChannelWithTopic(discord.NotifyAndScheduleCategory, discord.AutoActionChannelName, discord.AutoActionChannelTopic)
}

func (d *DiscordClient) ListAutoSearchChannelThredFirstMessageContents(channelID string) ([]*discordgo.Message, error) {
func (d *DiscordClient) ListAutoSearchChannelThredOkReactionedFirstMessageContents(channelID string) ([]*discordgo.Message, error) {
threadsList, err := d.session.GuildThreadsActive(d.session.State.Guilds[0].ID)
if err != nil {
return nil, err
Expand All @@ -220,10 +229,18 @@ func (d *DiscordClient) ListAutoSearchChannelThredFirstMessageContents(channelID
if th.ParentID == channelID {
thMsgs, err := d.session.ChannelMessages(th.ID, 1, "", "0", "")
if err != nil {
d.logger.Warn("can't get messages in thred", zap.String("th.ID", th.ID), zap.String("th.Name", th.Name))
d.logger.Warn("can't get messages in thred", zap.Error(err), zap.String("th.ID", th.ID), zap.String("th.Name", th.Name))
continue
}
if len(thMsgs) == 1 {
messages = append(messages, thMsgs[0])
if len(thMsgs) > 0 {
users, err := d.session.MessageReactions(th.ID, thMsgs[0].ID, discord.OkReactionEmoji, 1, "", "")
if err != nil {
d.logger.Warn("can't get message's reactions", zap.Error(err), zap.String("th.ID", th.ID), zap.String("msgID", thMsgs[0].ID))
continue
}
if len(users) > 0 {
messages = append(messages, thMsgs[0])
}
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion discord/discord_handler/discordhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,21 @@ func NewDiscordHandler(dtv *dtv.DTVUsecase, session *discordgo.Session, logger *
func (h *DiscordHandler) reactionAdd(session *discordgo.Session, reaction *discordgo.MessageReactionAdd) {
h.logger.Debug("add reaction emoji", zap.String("emoji", reaction.Emoji.Name), zap.String("UserID", reaction.UserID), zap.String("ChannelID", reaction.ChannelID), zap.String("MessageID", reaction.MessageID))

if reaction.Emoji.Name == discord.RecordingReactionEmoji {
switch reaction.Emoji.Name {
case discord.RecordingReactionEmoji:
ctx := context.Background()
err := h.dtv.OnRecordingEmojiAdd(ctx, reaction)
if err != nil {
h.logger.Error("onrecording emoji add error", zap.Error(err), zap.String("UserID", reaction.UserID), zap.String("ChannelID", reaction.ChannelID), zap.String("MessageID", reaction.MessageID))
}
case discord.OkReactionEmoji:
ctx := context.Background()
err := h.dtv.OnOkEmojiAdd(ctx, reaction)
if err != nil {
h.logger.Error("OnOkEmojiAdd error", zap.Error(err), zap.String("UserID", reaction.UserID), zap.String("ChannelID", reaction.ChannelID), zap.String("MessageID", reaction.MessageID))
}
default:
h.logger.Debug("no intent for this Emoji", zap.String("emojiName", reaction.Emoji.Name))
}
}

Expand Down
57 changes: 36 additions & 21 deletions dtv/auto_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,41 @@ func (a *AutoSearch) IsMatchProgram(program *AutoSearchProgram) bool {
}
}

func (a *AutoSearch) IsMatchService(serviceName string) bool {
if a.Channel == "" || strings.Contains(normalizeString(serviceName), normalizeString(a.Channel)) {
return true
} else {
return false
}
}

func (dtv *DTVUsecase) getAutoSeachFromMessage(msg *discordgo.Message) (*AutoSearch, error) {
content := []byte(msg.Content)
var autoSearch AutoSearch
err := yaml.Unmarshal(content, &autoSearch)
if err != nil {
return nil, err
}
notifyUsers, err := dtv.discord.GetMessageReactions(msg.ChannelID, msg.ID, discord.NotifyReactionEmoji)
if err != nil {
dtv.logger.Warn("can't get message reactions", zap.Error(err), zap.String("msg.ChannelID", msg.ChannelID), zap.String("msg.ID", msg.ID), zap.String("emoji", discord.NotifyReactionEmoji))
notifyUsers = []*discordgo.User{}
}
autoSearch.NotifyUsers = notifyUsers

recordingUsers, err := dtv.discord.GetMessageReactions(msg.ChannelID, msg.ID, discord.RecordingReactionEmoji)
if err != nil {
dtv.logger.Warn("can't get message reactions", zap.Error(err), zap.String("msg.ChannelID", msg.ChannelID), zap.String("msg.ID", msg.ID), zap.String("emoji", discord.RecordingReactionEmoji))
recordingUsers = []*discordgo.User{}
}
autoSearch.RecordingUsers = recordingUsers
autoSearch.ThreadID = msg.ChannelID

return &autoSearch, nil
}

func (dtv *DTVUsecase) ListAutoSearchForServiceName(serviceName string) ([]*AutoSearch, error) {
msgs, err := dtv.discord.ListAutoSearchChannelThredFirstMessageContents(dtv.autoSearchChannel.ID)
msgs, err := dtv.discord.ListAutoSearchChannelThredOkReactionedFirstMessageContents(dtv.autoSearchChannel.ID)
if err != nil {
return nil, err
}
Expand All @@ -51,32 +84,14 @@ func (dtv *DTVUsecase) ListAutoSearchForServiceName(serviceName string) ([]*Auto
autoSearchList := make([]*AutoSearch, 0)

for _, msg := range msgs {
content := []byte(msg.Content)
var autoSearch AutoSearch
err := yaml.Unmarshal(content, &autoSearch)
autoSearch, err := dtv.getAutoSeachFromMessage(msg)
if err != nil {
dtv.logger.Warn("thread message yaml unmarshal error", zap.Error(err))
continue
}
if autoSearch.Channel == "" || strings.Contains(serviceNameNormalized, normalizeString(autoSearch.Channel)) {
autoSearch.Title = normalizeString(autoSearch.Title)

notifyUsers, err := dtv.discord.GetMessageReactions(msg.ChannelID, msg.ID, discord.NotifyReactionEmoji)
if err != nil {
dtv.logger.Warn("can't get message reactions", zap.Error(err), zap.String("msg.ChannelID", msg.ChannelID), zap.String("msg.ID", msg.ID), zap.String("emoji", discord.NotifyReactionEmoji))
notifyUsers = []*discordgo.User{}
}
autoSearch.NotifyUsers = notifyUsers

recordingUsers, err := dtv.discord.GetMessageReactions(msg.ChannelID, msg.ID, discord.RecordingReactionEmoji)
if err != nil {
dtv.logger.Warn("can't get message reactions", zap.Error(err), zap.String("msg.ChannelID", msg.ChannelID), zap.String("msg.ID", msg.ID), zap.String("emoji", discord.RecordingReactionEmoji))
recordingUsers = []*discordgo.User{}
}
autoSearch.RecordingUsers = recordingUsers
autoSearch.ThreadID = msg.ChannelID

autoSearchList = append(autoSearchList, &autoSearch)
autoSearchList = append(autoSearchList, autoSearch)
}
}
return autoSearchList, nil
Expand Down
96 changes: 96 additions & 0 deletions dtv/on_ok_emoji_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package dtv

import (
"context"
"database/sql"

"github.com/bwmarrin/discordgo"
"github.com/kounoike/dtv-discord-go/discord"
"github.com/pkg/errors"
"go.uber.org/zap"
)

func (dtv *DTVUsecase) OnOkEmojiAdd(ctx context.Context, reaction *discordgo.MessageReactionAdd) error {
users, err := dtv.discord.GetMessageReactions(reaction.ChannelID, reaction.MessageID, reaction.Emoji.Name)
if err != nil {
return err
}
if len(users) != 1 {
// NOTE: 最初のOKリアクション以外は無視
return nil
}
th, err := dtv.discord.GetChannel(reaction.ChannelID)
if err != nil {
return err
}
if th.Type != discordgo.ChannelTypeGuildPublicThread {
// NOTE: 自動検索チャンネルじゃないのでリターン
return nil
}
asCh, err := dtv.discord.GetCachedChannel(discord.NotifyAndScheduleCategory, discord.AutoActionChannelName)
if err != nil {
return err
}
if th.ParentID != asCh.ID {
// NOTE: チャンネルが違うのでリターン
return nil
}

// 自動検索を登録済みのEPGに対して実行する
threadMsg, err := dtv.discord.GetChannelMessage(reaction.ChannelID, reaction.MessageID)
if err != nil {
return err
}
autoSearch, err := dtv.getAutoSeachFromMessage(threadMsg)
if err != nil {
return err
}

services, err := dtv.mirakc.ListServices()
if err != nil {
return err
}

for _, service := range services {
if autoSearch.IsMatchService(service.Name) {
programs, err := dtv.mirakc.ListPrograms(uint(service.ID))
if err != nil {
dtv.logger.Warn("ListPrograms error", zap.Error(err))
continue
}
for _, program := range programs {
asp := NewAutoSearchProgram(program)
if autoSearch.IsMatchProgram(asp) {
// NOTE: DBに入ってるか確認する
_, err := dtv.queries.GetProgram(ctx, program.ID)
if errors.Cause(err) == sql.ErrNoRows {
// NOTE: DBに入ってないプログラムは後で検索が走るはずなのでそっちで通知
continue
}
programMessage, err := dtv.queries.GetProgramMessageByProgramID(ctx, program.ID)
if err != nil {
dtv.logger.Warn("GetProgramMessageByProgramID error", zap.Error(err))
continue
}
ch, err := dtv.discord.GetCachedChannel(discord.ProgramInformationCategory, service.Name)
if err != nil {
dtv.logger.Warn("GetCachedChannel error", zap.Error(err))
continue
}
msg, err := dtv.discord.GetChannelMessage(ch.ID, programMessage.MessageID)
if err != nil {
dtv.logger.Warn("GetChannelMessage error", zap.Error(err))
continue
}
err = dtv.sendAutoSearchMatchMessage(ctx, msg, program, &service, autoSearch)
if err != nil {
dtv.logger.Warn("sendAutoSearchMatchMessage error", zap.Error(err))
continue
}
}
}
}
}

return nil
}
Loading

0 comments on commit 04360f3

Please sign in to comment.