1package main
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "strings"
8 "time"
9
10 comatproto "github.com/bluesky-social/indigo/api/atproto"
11 toolsozone "github.com/bluesky-social/indigo/api/ozone"
12 "github.com/bluesky-social/indigo/util"
13 "github.com/bluesky-social/indigo/xrpc"
14
15 "github.com/urfave/cli/v2"
16)
17
18func pollNewReports(cctx *cli.Context) error {
19 ctx := context.Background()
20 logger := configLogger(cctx, os.Stdout)
21 slackWebhookURL := cctx.String("slack-webhook-url")
22
23 // record last-seen report timestamp
24 since := time.Now()
25 // NOTE: uncomment this for testing
26 //since = time.Now().Add(time.Duration(-12) * time.Hour)
27 period := time.Duration(cctx.Int("poll-period")) * time.Second
28
29 // create a new session
30 xrpcc := &xrpc.Client{
31 Client: util.RobustHTTPClient(),
32 Host: cctx.String("pds-host"),
33 Auth: &xrpc.AuthInfo{Handle: cctx.String("handle")},
34 }
35
36 auth, err := comatproto.ServerCreateSession(ctx, xrpcc, &comatproto.ServerCreateSession_Input{
37 Identifier: xrpcc.Auth.Handle,
38 Password: cctx.String("password"),
39 })
40 if err != nil {
41 return err
42 }
43 xrpcc.Auth.AccessJwt = auth.AccessJwt
44 xrpcc.Auth.RefreshJwt = auth.RefreshJwt
45 xrpcc.Auth.Did = auth.Did
46 xrpcc.Auth.Handle = auth.Handle
47
48 adminToken := cctx.String("admin-password")
49 if len(adminToken) > 0 {
50 xrpcc.AdminToken = &adminToken
51 }
52 logger.Info("report polling bot starting up...")
53 // can flip this bool to false to prevent spamming slack channel on startup
54 if true {
55 err := sendSlackMsg(ctx, fmt.Sprintf("restarted bot, monitoring for reports since `%s`...", since.Format(time.RFC3339)), slackWebhookURL)
56 if err != nil {
57 return err
58 }
59 }
60 for {
61 // refresh session
62 xrpcc.Auth.AccessJwt = xrpcc.Auth.RefreshJwt
63 refresh, err := comatproto.ServerRefreshSession(ctx, xrpcc)
64 if err != nil {
65 return err
66 }
67 xrpcc.Auth.AccessJwt = refresh.AccessJwt
68 xrpcc.Auth.RefreshJwt = refresh.RefreshJwt
69
70 // query just new reports (regardless of resolution state)
71 var limit int64 = 50
72 me, err := toolsozone.ModerationQueryEvents(
73 cctx.Context,
74 xrpcc,
75 nil, // addedLabels []string
76 nil, // addedTags []string
77 "", // ageAssuranceState
78 nil, // collections []string
79 "", // comment string
80 "", // createdAfter string
81 "", // createdBefore string
82 "", // createdBy string
83 "", // cursor string
84 false, // hasComment bool
85 true, // includeAllUserRecords bool
86 limit, // limit int64
87 nil, // modTool
88 nil, // policies []string
89 nil, // removedLabels []string
90 nil, // removedTags []string
91 nil, // reportTypes []string
92 "", // sortDirection string
93 "", // subject string
94 "", // subjectType string
95 []string{"tools.ozone.moderation.defs#modEventReport"}, // types []string
96 )
97 if err != nil {
98 return err
99 }
100 // this works out to iterate from newest to oldest, which is the behavior we want (report only newest, then break)
101 for _, evt := range me.Events {
102 report := evt.Event.ModerationDefs_ModEventReport
103 // TODO: filter out based on subject state? similar to old "report.ResolvedByActionIds"
104 createdAt, err := time.Parse(time.RFC3339, evt.CreatedAt)
105 if err != nil {
106 return fmt.Errorf("invalid time format for 'createdAt': %w", err)
107 }
108 if createdAt.After(since) {
109 shortType := ""
110 if report.ReportType != nil && strings.Contains(*report.ReportType, "#") {
111 shortType = strings.SplitN(*report.ReportType, "#", 2)[1]
112 }
113 // ok, we found a "new" report, need to notify
114 msg := fmt.Sprintf("⚠️ New report at `%s` ⚠️\n", evt.CreatedAt)
115 msg += fmt.Sprintf("report id: `%d`\t", evt.Id)
116 msg += fmt.Sprintf("instance: `%s`\n", cctx.String("pds-host"))
117 msg += fmt.Sprintf("reasonType: `%s`\t", shortType)
118 msg += fmt.Sprintf("Admin: %s/reports/%d\n", cctx.String("admin-host"), evt.Id)
119 //msg += fmt.Sprintf("reportedByDid: `%s`\n", report.ReportedByDid)
120 logger.Info("found new report, notifying slack", "report", report)
121 err := sendSlackMsg(ctx, msg, slackWebhookURL)
122 if err != nil {
123 return fmt.Errorf("failed to send slack message: %w", err)
124 }
125 since = createdAt
126 break
127 } else {
128 logger.Debug("skipping report", "report", report)
129 }
130 }
131 logger.Info("... sleeping", "period", period)
132 time.Sleep(period)
133 }
134}