-
Notifications
You must be signed in to change notification settings - Fork 1
/
transcript.go
361 lines (300 loc) · 8.35 KB
/
transcript.go
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package main
import (
"fmt"
"io"
"net/http"
"os"
"regexp"
"sort"
"strconv"
"strings"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/log"
"github.com/disgoorg/snowflake/v2"
"github.com/rotisserie/eris"
"github.com/spf13/viper"
)
type MsgAttachment struct {
ID snowflake.ID
Filename string
ContentType string
URL string
Size int
}
type Msg struct {
Content string
ID snowflake.ID
Attachments []MsgAttachment
}
type MsgBlock struct {
UserId snowflake.ID
Name string
Messages []Msg
}
type Transcript struct {
Blocks []MsgBlock
nameOverride map[string]interface{}
}
type TranscriptStats struct {
TotalMessages int
TotalUsers int
StartDate string
EndDate string
}
// Adds a message to the transcript
func (t *Transcript) AddMessage(m discord.Message) {
// we get messages in the reverse order they were send,
// so we need to add them in the reverse order we get them
msg := Msg{
Content: m.Content,
ID: m.ID,
Attachments: []MsgAttachment{},
}
if len(m.Attachments) > 0 {
for _, a := range m.Attachments {
msg.Attachments = append(msg.Attachments, MsgAttachment{
ID: a.ID,
Filename: a.Filename,
ContentType: *a.ContentType,
URL: a.URL,
Size: a.Size,
})
}
}
// if first block is the same user
if len(t.Blocks) > 0 && t.Blocks[0].UserId == m.Author.ID {
// just put the message in the first block
t.Blocks[0].Messages = append([]Msg{msg}, t.Blocks[0].Messages...)
return
}
var name string
// check if we have a name override
if t.nameOverride[m.Author.ID.String()] != nil {
name = t.nameOverride[m.Author.ID.String()].(string)
} else {
name = m.Author.Username
t.nameOverride[m.Author.ID.String()] = name
}
// create a new block
msgBlock := MsgBlock{
UserId: m.Author.ID,
Name: name,
Messages: []Msg{msg},
}
t.Blocks = append([]MsgBlock{msgBlock}, t.Blocks...)
}
func (t *Transcript) AddMessages(client rest.Rest, channelId, startId snowflake.ID) {
messages := client.GetMessagesPage(channelId, startId, 50)
t.addMessagesPage(messages)
if channelId == startId {
// probably a thread, so we need to get the parent message
parentMsg, err := client.GetMessage(channelId, startId)
if err != nil {
err = eris.Wrap(err, "failed to get parent message")
log.Panic(err)
}
// add parent message to transcript
t.AddMessage(*parentMsg)
}
}
// Gets messages from a page and adds them to the transcript
func (t *Transcript) addMessagesPage(messages rest.Page[discord.Message]) {
count := 0
// page through messages
for messages.Next() {
log.Infof("Processing page %d...", count+1)
for _, m := range messages.Items {
t.AddMessage(m)
}
count++
}
t.Sort()
}
// brute force sorting after inital sort
func (t *Transcript) Sort() {
sort.Slice(t.Blocks, func(i, j int) bool {
iValue := t.Blocks[i].Messages[0].ID.Time().Compare(t.Blocks[j].Messages[0].ID.Time())
if iValue == 0 {
// same time??
return false
} else if iValue == 1 {
// i comes before j
return false
} else {
// j comes before i
return true
}
})
}
// Gets basic stats about the transcript
func (t *Transcript) GetStats() TranscriptStats {
totalMessages := 0
seenUsers := []snowflake.ID{}
startTime := t.Blocks[0].Messages[0].ID.Time().Format("Mon, 02 Jan 2006 15:04:05 MST")
endTime := t.Blocks[len(t.Blocks)-1].Messages[len(t.Blocks[len(t.Blocks)-1].Messages)-1].ID.Time().Format("Mon, 02 Jan 2006 15:04:05 MST")
for _, b := range t.Blocks {
// check if user has been seen
seenUser := false
for _, u := range seenUsers {
if b.UserId == u {
seenUser = true
continue
}
}
if !seenUser {
seenUsers = append(seenUsers, b.UserId)
}
// tally up msg stats
totalMessages += len(b.Messages)
}
return TranscriptStats{
TotalMessages: totalMessages,
TotalUsers: len(seenUsers),
StartDate: startTime,
EndDate: endTime,
}
}
// prints transcript to console
func (t *Transcript) PrintTranscript() {
println("----------------- Transcript -----------------")
for _, b := range t.Blocks {
println(b.Name)
for _, m := range b.Messages {
println(m.Content)
}
println("---")
}
}
// writes transcript to a file
func (t *Transcript) SaveTranscript() {
log.Info("Saving transcript...")
trialName := viper.GetString("TRIAL_NAME")
// format trial name for use as filename
trailNameFormated := t.FormatFileName(trialName)
folderPath := fmt.Sprintf("transcripts/%s", trailNameFormated)
// check if folder exists
if _, err := os.Stat(folderPath); eris.Is(err, os.ErrNotExist) {
// if eror is that folder doesn't exist, create it
err := os.MkdirAll(folderPath, os.ModePerm)
if err != nil {
// failed to create folder
err := eris.Wrap(err, "failed to create folder")
log.Panic(err)
}
}
// create file
f, err := os.Create(folderPath + "/" + trailNameFormated + ".md")
if err != nil {
eris.Wrap(err, "failed to create file")
log.Panic(err)
}
// remember to close the file
defer f.Close()
// scaffold the file
writeToFile(f, "# "+trialName)
writeToFile(f, "## Case")
writeToFile(f, "_REPLACE ME: need a sumarry of the case here_")
writeToFile(f, "## Proceedings")
// write transcript
for _, b := range t.Blocks {
writeToFile(f, "**"+b.Name+"**:")
writeToFile(f, "")
// println(b.Name)
for _, m := range b.Messages {
attachments := ""
for _, a := range m.Attachments {
// construct attachment path for use on the website
attachmentPath := fmt.Sprintf("../../assets/judiciary/%s/%s", trailNameFormated, a.Filename)
attachments += fmt.Sprintf("![%s](%s)\n", a.Filename, attachmentPath)
t.saveAttachment(a, folderPath)
}
// preprocess message to handle newlines
content := strings.ReplaceAll(m.Content, "\n", "\n> ")
content = t.replaceMentions(content)
writeToFile(f, "> "+content)
// writeToFile(f, "")
// only write attachments if there are any
if len(attachments) > 0 {
writeToFile(f, "")
writeToFile(f, attachments)
}
}
writeToFile(f, "")
}
log.Info("Transcript saved!")
}
func (t *Transcript) saveAttachment(attachment MsgAttachment, folderPath string) {
// Create blank file
file, err := os.Create(folderPath + "/" + attachment.Filename)
if err != nil {
err := eris.Wrap(err, "failed to create attachment file")
log.Fatal(err)
}
defer file.Close()
// http client to download attachment
client := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
// Put content on file
resp, err := client.Get(attachment.URL)
if err != nil {
err := eris.Wrap(err, "failed to download attachment")
log.Fatal(err)
}
defer resp.Body.Close()
size, err := io.Copy(file, resp.Body)
if err != nil {
err := eris.Wrap(err, "failed to write attachment to file")
log.Fatal(err)
}
log.Debugf("Saved attachment: %s (%d)", attachment.Filename, size)
}
// creates a new transcript
func NewTranscript(nameOverride map[string]interface{}) *Transcript {
return &Transcript{
Blocks: []MsgBlock{},
nameOverride: nameOverride,
}
}
// simpe util to append a line to a file
func writeToFile(file *os.File, content string) {
_, err := file.WriteString(content + "\n")
if err != nil {
eris.Wrap(err, "failed to write to file")
log.Panic(err)
}
}
func (t *Transcript) replaceMentions(content string) string {
outerPart := regexp.MustCompile("<@!*&*|>")
return regexp.MustCompile("<@!*&*[0-9]+>").ReplaceAllStringFunc(content, func(s string) string {
idStr := outerPart.ReplaceAllString(s, "")
idNum, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
eris.Wrap(err, "failed to parse id")
log.Panic(err)
}
id := snowflake.ID(idNum)
if val, ok := t.nameOverride[id.String()].(string); ok {
return "`@" + val + "`"
} else {
return s
}
})
}
// formats the trial name for use as the filename of the transcript
func (t *Transcript) FormatFileName(trialName string) (fileName string) {
// force lowercase
fileName = strings.ToLower(trialName)
// clear whitespace
fileName = strings.ReplaceAll(fileName, " ", "_")
// remove special characters
fileName = strings.ReplaceAll(fileName, ".", "")
fileName = strings.ReplaceAll(fileName, ",", "")
fileName = strings.ReplaceAll(fileName, ":", "")
fileName = strings.ReplaceAll(fileName, ";", "")
return
}