-
Notifications
You must be signed in to change notification settings - Fork 3
/
link-publisher.js
executable file
·166 lines (139 loc) · 5.23 KB
/
link-publisher.js
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
#!/usr/bin/env node
const truncate = require('lodash/truncate')
const toLower = require('lodash/toLower')
const {GoogleSpreadsheet} = require('google-spreadsheet')
const Twitter = require('twitter')
const fetch = require('node-fetch')
const moment = require('moment-timezone')
const FROM_SHEETS = process.env.FROM_SHEETS.split('|').map(s => s.split(','))
const TO_SHEET_ID = process.env.TO_SHEET_ID
const TO_TAB_NAME = process.env.TO_TAB_NAME
const FLAGGED_SHEET_ID = process.env.FLAGGED_SHEET_ID
const FLAGGED_TAB_NAME = process.env.FLAGGED_TAB_NAME
const PUBLISHED_COL_NAME = process.env.PUBLISHED_COL_NAME
const VETTED_COL_NAME = process.env.VETTED_COL_NAME
const ANNOUNCE_WEBHOOK_URL = process.env.ANNOUNCE_WEBHOOK_URL
const ANNOUNCE_DETAILS_WEBHOOK_URL = process.env.ANNOUNCE_DETAILS_WEBHOOK_URL
const SLEEP_SECONDS = process.env.SLEEP_SECONDS
const SHEET_CREDS = require('./gs-creds.json')
const TIMEZONE = 'America/Chicago'
const DATE_FORMAT = 'M/D/YY HH:mm:ss'
let TWITTER_CREDS
try {
TWITTER_CREDS = require('./twitter-creds.json')
} catch (err) {
console.warn('failed to load twitter credentials', err)
}
const {doWithRetry, sleep, getLinkInfo, getSheetTab} = require('./utils')
async function announce(row) {
await fetch(ANNOUNCE_WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'New Stream',
content: `**${row.Source}** — ${row.City}, ${row.State} (${row.Type}, ${row.View})${row.Notes ? ' ' + row.Notes : ''} <${row.Link}>`,
}),
})
}
async function announceDetails(row, linkInfo) {
if (!ANNOUNCE_DETAILS_WEBHOOK_URL) {
return
}
const {streamType, embed} = linkInfo
const msgParts = []
msgParts.push(`**${row.Source}** — ${row.City}, ${row.State} (${row.Type}, ${row.View})${row.Notes ? ' ' + row.Notes : ''}`)
msgParts.push(`:link: <${row.Link}>`)
if (embed) {
msgParts.push(`:gear: <${embed}>`)
}
await fetch(ANNOUNCE_DETAILS_WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'New Stream',
content: msgParts.join('\n'),
}),
})
}
async function tweet(row) {
if (!TWITTER_CREDS) {
return
}
const tag = str => `#${str.toLowerCase().replace(' ', '')}`
const client = new Twitter(TWITTER_CREDS)
const statusStart = `${row.Source} ${tag(row.City)} ${tag(row.State)} #live`
const statusEnd = `\n${row.Link}`
const maxLen = 280
const notesLength = maxLen - statusStart.length - statusEnd.length - 1
const status = `${statusStart}${row.Notes ? '(' + truncate(row.Notes, {length: notesLength}) + ')' : ''}${statusEnd}`
try {
await client.post('statuses/update', {status})
} catch (err) {
console.error('failed to tweet', row.Link, err)
}
}
async function runPublish() {
const toSheet = await getSheetTab(SHEET_CREDS, TO_SHEET_ID, TO_TAB_NAME)
const toRows = await doWithRetry(() => toSheet.getRows())
const publishedURLs = new Set(toRows.map(r => r.Link))
const flaggedSheet = await getSheetTab(SHEET_CREDS, FLAGGED_SHEET_ID, FLAGGED_TAB_NAME)
const flaggedRows = await doWithRetry(() => flaggedSheet.getRows())
const flaggedSources = new Set(flaggedRows.map(r => r.Source).filter(x => x).map(toLower))
const flaggedURLs = new Set(flaggedRows.map(r => r.Link).filter(x => x).map(toLower))
for (const docInfo of FROM_SHEETS) {
const [sheetID, ...tabNames] = docInfo
const doc = new GoogleSpreadsheet(sheetID)
await doc.useServiceAccountAuth(SHEET_CREDS)
await doc.loadInfo()
const sheets = Object.values(doc.sheetsById).filter(s => tabNames.includes(s.title))
for (const sheet of sheets) {
const rows = await doWithRetry(() => sheet.getRows())
for (const row of rows) {
if (!row.Link || row[PUBLISHED_COL_NAME] !== '') {
continue
}
if (row.hasOwnProperty(VETTED_COL_NAME) && row[VETTED_COL_NAME] !== 'x') {
continue
}
await sheet.loadCells(row.a1Range)
const publishedCell = sheet.getCell(row.rowNumber - 1, sheet.headerValues.indexOf(PUBLISHED_COL_NAME))
const linkInfo = await getLinkInfo(row.Link)
if (linkInfo.normalizedURL) {
row.Link = linkInfo.normalizedURL
}
if (flaggedURLs.has(toLower(row.Link)) || flaggedSources.has(toLower(row.Source))) {
publishedCell.value = 'flagged'
await doWithRetry(() => publishedCell.save())
console.log(`skipped flagged ${row.Link}`)
continue
}
if (publishedURLs.has(row.Link)) {
publishedCell.value = 'dupe'
await doWithRetry(() => publishedCell.save())
console.log(`skipped dupe ${row.Link}`)
continue
}
row['Published (CST)'] = moment().tz(TIMEZONE).format(DATE_FORMAT)
await doWithRetry(() => toSheet.addRow(row))
publishedCell.value = 'x'
await doWithRetry(() => publishedCell.save())
publishedURLs.add(row.Link)
await announce(row)
await announceDetails(row, linkInfo)
await tweet(row)
console.log(`published ${row.Link}`)
}
}
}
}
async function main() {
while (true) {
await runPublish()
await sleep(SLEEP_SECONDS * 1000)
}
}
main()