-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmonitor.go
More file actions
286 lines (260 loc) · 8.9 KB
/
monitor.go
File metadata and controls
286 lines (260 loc) · 8.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package mastobots
import (
"context"
"log"
"math/rand"
"regexp"
"runtime"
"strings"
"time"
mastodon "github.com/hanage999/go-mastodon"
)
// moitorは、websocketでタイムラインを監視して反応する。
func (bot *Persona) monitor(ctx context.Context) {
log.Printf("trace: Goroutines: %d", runtime.NumGoroutine())
log.Printf("info: %s がタイムライン監視を開始しました", bot.Name)
newCtx, cancel := context.WithCancel(ctx)
defer cancel()
evch, err := bot.openStreaming(newCtx)
if err != nil {
log.Printf("info: %s がストリーミングを受信開始できませんでした", bot.Name)
return
}
ers := ""
for ev := range evch {
switch t := ev.(type) {
case *mastodon.UpdateEvent:
go func() {
if err := bot.respondToUpdate(newCtx, t); err != nil {
log.Printf("info: %s がトゥートに反応できませんでした", bot.Name)
}
}()
case *mastodon.NotificationEvent:
go func() {
if err := bot.respondToNotification(newCtx, t); err != nil {
log.Printf("info: %s が通知に反応できませんでした", bot.Name)
}
}()
case *mastodon.ErrorEvent:
ers = t.Error()
log.Printf("info: %s がエラーイベントを受信しました:%s", bot.Name, ers)
}
}
if ctx.Err() != nil {
log.Printf("info: %s が今日のタイムライン監視を終了しました:%s", bot.Name, ctx.Err())
} else {
itvl := rand.Intn(4000) + 1000
log.Printf("info: %s の接続が切れました。%dミリ秒後に再接続します:%s", bot.Name, itvl, ers)
time.Sleep(time.Duration(itvl) * time.Millisecond)
go bot.monitor(ctx)
}
}
// openStreamingは、HTLのストリーミング接続を開始する。失敗したらmaxRetryを上限に再試行する。
func (bot *Persona) openStreaming(ctx context.Context) (evch chan mastodon.Event, err error) {
wsc := bot.Client.NewWSClient()
for i := 0; i < bot.commonSettings.maxRetry; i++ {
evch, err = wsc.StreamingWSUser(ctx)
if err == nil {
log.Printf("trace: %s のストリーミング受信に成功しました", bot.Name)
return
}
log.Printf("info: %s のストリーミング受信が開始できません:%s", bot.Name, err)
time.Sleep(bot.commonSettings.retryInterval)
}
log.Printf("info: %s のストリーミング受信開始がリトライ上限に達しました:%s", bot.Name, err)
return
}
// respondToUpdateは、statusに反応する。
func (bot *Persona) respondToUpdate(ctx context.Context, ev *mastodon.UpdateEvent) (err error) {
orig := ev.Status
rebl := false
if orig.Reblog != nil {
orig = orig.Reblog
rebl = true
}
// メンションは無視(ブーストされたのものは見る)
if len(ev.Status.Mentions) != 0 && !rebl {
return
}
// 自分のトゥートは無視
if orig.Account.ID == bot.MyID {
return
}
// トゥートを形態素解析
text := textContent(orig.Content)
if text == "" {
return
}
result, err := parse(bot.commonSettings.langJobPool, text)
if err != nil {
return
}
// キーワードを検知したらふぁぼる。同じ鯖のbotならブースト+引用コメントする
for _, w := range bot.Keywords {
if result.contain(w) {
if err = bot.fav(ctx, ev.Status.ID); err != nil {
log.Printf("info: %s がふぁぼを諦めました", bot.Name)
}
if !strings.Contains(ev.Status.Account.Acct, "@") && ev.Status.Account.Bot {
if err = bot.boost(ctx, ev.Status.ID); err != nil {
log.Printf("info: %s がブーストを諦めました", bot.Name)
}
if err = bot.quoteComment(ctx, result, orig.URL); err != nil {
log.Printf("info: %s が引用+コメントを諦めました", bot.Name)
}
}
break
}
}
return
}
// quoteCommentは、トゥートを引用コメントする
func (bot *Persona) quoteComment(ctx context.Context, result parseResult, url string) (err error) {
msg, err := bot.messageFromParseResult(result, url)
if err != nil || msg == "" {
log.Printf("info: %s が引用コメントを作成できませんでした", bot.Name)
return
}
toot := mastodon.Toot{Status: msg}
if err := bot.post(ctx, toot); err != nil {
log.Printf("info: %s が引用コメントできませんでした。今回は諦めます……", bot.Name)
}
return
}
// messageFromParseResultは、パース結果とURLから投稿文を作成する。
func (bot *Persona) messageFromParseResult(result parseResult, url string) (msg string, err error) {
// トゥートに使う単語の選定
cds := result.candidates()
best, err := bestCandidate(cds)
if err != nil {
log.Printf("info: %s が引用コメントの単語選定に失敗しました", bot.Name)
return
}
// コメントの生成
idx := 0
if len(bot.Comments) > 1 {
idx = rand.Intn(len(bot.Comments))
}
msg = bot.Comments[idx]
msg = strings.Replace(msg, "_keyword1_", best.surface, -1)
msg = strings.Replace(msg, "_topkana1_", best.firstKana, -1)
// リンクを追加
msg += "\n\n" + url
log.Printf("trace: %s のトゥート内容:\n\n%s", bot.Name, msg)
return
}
// respondToNotificationは、通知に反応する。
func (bot *Persona) respondToNotification(ctx context.Context, ev *mastodon.NotificationEvent) (err error) {
switch ev.Notification.Type {
case "mention":
if err = bot.respondToMention(ctx, ev.Notification.Account, ev.Notification.Status); err != nil {
log.Printf("info: %s がメンションに反応できませんでした:%s", bot.Name, err)
return
}
case "reblog":
// TODO
case "favourite":
// TODO
case "follow":
// TODO
}
if err = bot.dismissNotification(ctx, ev.Notification.ID); err != nil {
log.Printf("info: %s が id:%s の通知を削除できませんでした:%s", bot.Name, string(ev.Notification.ID), err)
return
}
return
}
// respondToMentionは、メンションに反応する。
func (bot *Persona) respondToMention(ctx context.Context, account mastodon.Account, status *mastodon.Status) (err error) {
r := regexp.MustCompile(`:.*:\z`)
name := account.DisplayName
if r.MatchString(name) {
name = name + " "
}
if status == nil {
return
}
txt := textContent(status.Content)
res, err := parse(bot.commonSettings.langJobPool, txt)
if err != nil {
return
}
// メンションありがとうのふぁぼ
if err = bot.fav(ctx, status.ID); err != nil {
log.Printf("info: %s がふぁぼを諦めました", bot.Name)
}
var jm jumanResult
var ok bool
if jm, ok = res.(jumanResult); !ok {
log.Printf("info: %sに送られたメッセージは日本語ではありません", bot.Name)
return
}
msg := ""
switch {
case strings.Contains(txt, "フォロー"):
rel, err := bot.relationWith(ctx, account.ID)
if err != nil {
log.Printf("info: %s が関係取得に失敗しました", bot.Name)
return err
}
if (*rel[0]).Following {
msg = "@" + account.Acct + " " + name + "さんはもうフォローしてるから大丈夫" + bot.Assertion + "よー"
} else {
if err = bot.follow(ctx, account.ID); err != nil {
log.Printf("info: %s がフォローに失敗しました", bot.Name)
return err
}
msg = "@" + account.Acct + " わーい、お友達" + bot.Assertion + "ね!これからは、" + name + "さんのトゥートを生温かく見守っていく" + bot.Assertion + "よー"
}
if msg != "" {
toot := mastodon.Toot{Status: msg, Visibility: status.Visibility, InReplyToID: status.ID}
if err = bot.post(ctx, toot); err != nil {
log.Printf("info: %s がリプライに失敗しました", bot.Name)
return err
}
}
case strings.Contains(txt, "いい"+bot.Assertion):
yon := "だめ" + bot.Assertion + "よ"
if rand.Intn(2) == 1 {
yon = "いい" + bot.Assertion + "よ"
}
msg = "@" + account.Acct + " " + bot.Starter + name + bot.Title + "。" + yon
if msg != "" {
toot := mastodon.Toot{Status: msg, Visibility: status.Visibility, InReplyToID: status.ID}
if err = bot.post(ctx, toot); err != nil {
log.Printf("info: %s がリプライに失敗しました", bot.Name)
return err
}
}
}
if jm.isWeatherRelated() {
lc, dt, fl, err := jm.judgeWeatherRequest()
if err != nil {
return err
}
placeName, lat, lng, err := getLocDataFromString(bot.commonSettings.yahooClientID, lc)
unknownmsg := ""
botLoc := false
if err != nil {
unknownmsg = "ちょっと何言ってるか分からない" + bot.Assertion + "。でも、"
placeName = bot.PlaceName
lat = bot.Latitude
lng = bot.Longitude
botLoc = true
}
wdata, err := GetLocationWeather(bot.commonSettings.weatherKey, lat, lng, dt)
if err != nil {
log.Printf("info: %s が天気の取得に失敗しました", bot.Name)
return err
}
msg = "@" + account.Acct + " " + unknownmsg + forecastMessage(placeName, wdata, dt, bot.Assertion, botLoc, fl)
if msg != "" {
toot := mastodon.Toot{Status: msg, Visibility: status.Visibility, InReplyToID: status.ID}
if err = bot.post(ctx, toot); err != nil {
log.Printf("info: %s がリプライに失敗しました", bot.Name)
return err
}
}
}
return
}