-
Notifications
You must be signed in to change notification settings - Fork 6
/
main.go
169 lines (148 loc) · 4.9 KB
/
main.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
package main
import (
"fmt"
"log"
"os"
"regexp"
"strings"
"time"
"github.com/dghubble/go-twitter/twitter"
)
var (
CONSUMER_KEY = os.Getenv("CONSUMER_KEY")
CONSUMER_SECRET = os.Getenv("CONSUMER_SECRET")
ACCESS_KEY = os.Getenv("ACCESS_KEY")
ACCESS_SECRET = os.Getenv("ACCESS_SECRET")
CHECK_FOR_BLOCKED_SCREENNAMES_INTERVAL = 15 * time.Minute
)
// (?i) -- case-insensitive
// ^ -- start *required
// (?:#100daysofcode\W+)? -- #100DaysOfCode *optional
// (?:r(?:ounds?)?\W*\d+)? -- round(s?) ? OR r ? *optional
// \W* -- separator *optional
// d(?:ays?)?\W*(\d+) -- day(s?) ? OR d ? *required
// (?:\W+|$) -- separator or EOF *required
//
// https://regex101.com/r/VCM8l4/3
var progressRegex = regexp.MustCompile(`(?i)^(?:#100DaysOfCode\W+)?(?:r(?:ounds?)?\W*\d+)?\W*d(?:ays?)?\W*(\d+)(?:\W+|$)`)
////////////////////////////////////////////////////////////////////////////////
type BlockedService struct {
interval time.Duration
shouldRefreshBlockedScreenNames bool
blockedScreenNamesMap map[string]struct{}
*TwitterOAuth1Authentication
}
func newBlockedService(twitterOAuth1Auth *TwitterOAuth1Authentication, interval time.Duration) *BlockedService {
srv := &BlockedService{
interval: interval,
shouldRefreshBlockedScreenNames: true,
blockedScreenNamesMap: map[string]struct{}{},
TwitterOAuth1Authentication: twitterOAuth1Auth,
}
srv.Refresh()
go func() {
ticker := time.NewTicker(interval)
for ; true; <-ticker.C {
srv.shouldRefreshBlockedScreenNames = true
}
}()
return srv
}
func (b *BlockedService) Refresh() error {
if !b.shouldRefreshBlockedScreenNames {
return nil
}
screenNames, err := b.GetBlockedScreenNames()
if err != nil {
return err
}
for _, screenName := range screenNames {
b.blockedScreenNamesMap[strings.ToLower(screenName)] = struct{}{}
}
b.shouldRefreshBlockedScreenNames = false
return nil
}
func (b *BlockedService) IsBlocked(screenName string) bool {
_, ok := b.blockedScreenNamesMap[strings.ToLower(screenName)]
return ok
}
////////////////////////////////////////////////////////////////////////////////
func init() {
log.Println("initializing")
if CONSUMER_KEY == "" {
log.Fatal("env CONSUMER_KEY cannot be empty")
} else if CONSUMER_SECRET == "" {
log.Fatal("env CONSUMER_SECRET cannot be empty")
} else if ACCESS_KEY == "" {
log.Fatal("env ACCESS_KEY cannot be empty")
} else if ACCESS_SECRET == "" {
log.Fatal("env ACCESS_SECRET cannot be empty")
}
}
func isRelevant(tweet *twitter.Tweet) bool {
return strings.HasPrefix(tweet.Text, "I'm publicly committing to the 100DaysOfCode") ||
progressRegex.MatchString(tweet.Text)
}
func main() {
// Connect to the Twitter API
api := newTwitterAPIAuthentication(OAuth1AuthenticationParameters{
ConsumerKey: CONSUMER_KEY,
ConsumerSecret: CONSUMER_SECRET,
AccessKey: ACCESS_KEY,
AccessSecret: ACCESS_SECRET,
})
if api == nil {
panic("failed to authenticate twitter api")
}
log.Println("connected to twitter api")
// Connect to the Twitter OAuth1 API (for checking for blocker screen names)
twitterOAuth1Auth := newTwitterOAuth1Authentication(OAuth1AuthenticationParameters{
ConsumerKey: CONSUMER_KEY,
ConsumerSecret: CONSUMER_SECRET,
AccessKey: ACCESS_KEY,
AccessSecret: ACCESS_SECRET,
})
if twitterOAuth1Auth == nil {
panic("failed to authenticate twitter oauth1 api")
}
log.Println("connected to twitter oauth1 api")
// Create a blocked service and start streaming `"#100DaysOfCode"` tweets
blockedService := newBlockedService(twitterOAuth1Auth, CHECK_FOR_BLOCKED_SCREENNAMES_INTERVAL)
for tweet := range api.MustStream([]string{"#100DaysOfCode"}) {
var (
username = strings.ToLower(tweet.User.ScreenName)
url = fmt.Sprintf("https://twitter.com/%s/status/%s", username, fmt.Sprint(tweet.ID))
)
// Check for blocked users (takes precedence)
if err := blockedService.Refresh(); err != nil {
panic(fmt.Sprintf("failed to refresh blocked service; %s", err))
}
if blockedService.IsBlocked(tweet.User.ScreenName) {
log.Printf("ignored blocked user @%s tweet %s\n",
username, url)
continue
}
// Check for irrelevant tweets
if !isRelevant(tweet) {
log.Printf("ignored irrelevant user @%s tweet %s\n",
username, url)
continue
}
// Retweet tweet (no-ops if already retweeted)
if err := api.Retweet(tweet); err != nil {
log.Printf("cannot retweet user @%s tweet %s; %s\n",
username, url, err)
continue
}
log.Printf("retweeted user @%s tweet %s\n",
username, url)
// // Follow user (no-ops if already following)
// if err := api.Follow(tweet); err != nil {
// log.Printf("cannot follow user @%s tweet %s; %s\n",
// username, url, err)
// continue
// }
// log.Printf("followed user @%s tweet %s\n",
// username, url)
}
}