-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlisten.js
140 lines (123 loc) · 5.86 KB
/
listen.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
const twitterConfig = require("./twitter_config");
const {TwitterApi} = require("twitter-api-v2");
const {matchAssets, getSwappedCount, getSwaps, getBurnedCount, getBurns} = require("./db");
const {sumReducer} = require("./common");
const BigNumber = require("bignumber.js");
const twitterApiApp = new TwitterApi(process.env.TWITTER_APP_BEARER_TOKEN);
const twitterBotApp = new TwitterApi(twitterConfig);
const twitterBotClient = twitterBotApp.v2;
const assetStats = (assetCode) => {
return matchAssets(assetCode, true)
.map(asset => {
const assetCode = `${asset.code}:${asset.issuer}`;
return {...asset,
swappedCount: getSwappedCount(assetCode),
burnedCount: getBurnedCount(assetCode),
swappedAmount: getSwaps(assetCode).map(swap => swap.amount).reduce(sumReducer, new BigNumber(0)).toFormat(),
burnedAmount: getBurns(assetCode).map(burn => burn.amount).reduce(sumReducer, new BigNumber(0)).toFormat()
};
});
};
const shortIssuer = (issuer) => issuer.substring(0, 3) + '…' + issuer.substring(53)
const hashtagsToCashtags = (hashtags) => {
const matches = [...new Set(
hashtags
.filter(hashtag => hashtag.tag.length > 6 && hashtag.tag.length <= 12)
.map(hashtag => matchAssets(hashtag.tag, false).map(a => a.code))
.flat()
)];
return matches.filter(match => hashtags.map(hashtag => hashtag.tag.toLowerCase()).includes(match.toLowerCase()))
.map(match => ({tag: match}))
};
const renderTag = (tag) => {
return (tag.length < 6 ? '$' : '#') + tag;
};
const statToTweet = (assetStat) => {
return '' +
`🧹 ${assetStat.code} (by ${shortIssuer(assetStat.issuer)}) has been cleaned ${assetStat.swappedCount + assetStat.burnedCount} times on #stellar network:\n` +
`🔥 ${assetStat.burnedCount} burns` + ((assetStat.burnedAmount === "0") ? "\n" : ` burned ${assetStat.burnedAmount} ${renderTag(assetStat.code)}\n`) +
`💱 ${assetStat.swappedCount} swaps yielded ${assetStat.swappedAmount} $XLM\n\n` +
'#trashtocash #stellarclaim\n\n' +
`https://stellar.expert/explorer/public/asset/${assetStat.code}-${assetStat.issuer}`;
}
const getCashtags = (data, repliedTo) => {
return (((data.entities.cashtags?.length)
? data.entities.cashtags
: repliedTo?.entities.cashtags) ?? [])
.concat(hashtagsToCashtags({...repliedTo?.entities, ...data.entities}.hashtags ?? []));
};
let twitterStream;
const main = async () => {
const me = await twitterBotClient.me();
console.log("Logged in to twitter as", me.data.username, me.data.id);
const rules = await twitterApiApp.v2.streamRules();
console.log(rules.data.map(r => r.id))
await twitterApiApp.v2.updateStreamRules({
delete: {ids: rules.data.map(r => r.id) }
});
console.log("clear")
const rulesUpdated = await twitterApiApp.v2.updateStreamRules({
add: [
{"value": "@" + me.data.username, "tag": "account mentions"},
//{"value": "has:cashtags", "tag": "cashtag"}
]
});
console.log("Rules updated", rulesUpdated.meta);
twitterApiApp.v2.searchStream({
expansions: "entities.mentions.username,author_id,referenced_tweets.id",
"tweet.fields": "entities"
}).then(stream => {
twitterStream = stream;
stream.on('data event content', (event) => {
if (event.matching_rules.find(r => r.tag === "account mentions")) {
if (event.data.author_id === me.data.id) {
console.log("Not reacting to myself");
return;
}
if (event.data.referenced_tweets?.find(r => r.type === "retweeted")) {
console.log("Not replying to retweet", event.data.id);
return;
}
let cashtags = [];
try {
cashtags = getCashtags(event.data, getReplyFromIncludes(event));
} catch (e) {
console.warn(e);
}
console.log("Received mention", {tweetId: event.data.id, tags: cashtags.map(t => t.tag)});
const dontReplyToUsers = event.includes.users.map(u => u.id);
if (cashtags.length === 0) {
twitterBotClient.tweet(
"🤷 I have not processed such asset(s).",
{reply: {in_reply_to_tweet_id: event.data.id, exclude_reply_user_ids: dontReplyToUsers}}
);
} else {
const stats = cashtags.map(cashtag => assetStats(cashtag.tag)).flat();
// in order to make this a thread, the reply-to id must be updated after each post
let replyTo = event.data.id;
(async () => {
const replyStatuses = [];
for (const stat of stats) {
const tweetStatus = await twitterBotClient.tweet(
statToTweet(stat),
{reply: {in_reply_to_tweet_id: replyTo, exclude_reply_user_ids: dontReplyToUsers}}
);
replyTo = tweetStatus.data.id;
replyStatuses[`${stat.code}:${shortIssuer(stat.issuer)}`] = replyTo;
}
return replyStatuses;
})().then(replyStatuses => console.log(`replied to ${event.data.id}`, replyStatuses));
}
}
});
});
};
const getReplyFromIncludes = event => {
const isReplyTo = event.data.referenced_tweets?.find(ref => ref.type === 'replied_to')?.id;
return event.includes.tweets?.find(tweet => tweet.id === isReplyTo);
}
process.on("SIGINT", () => {
console.log("bye");
twitterStream?.close();
});
main();