-
-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathmodules.go
265 lines (241 loc) · 7.21 KB
/
modules.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
package igopher
import (
"encoding/csv"
"errors"
"os"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/tebeka/selenium"
)
const (
fileBlacklistPath = "data/blacklist.csv"
)
/* Quota manager */
// QuotaManager data
type QuotaManager struct {
// HourTimestamp: hourly timestamp used to handle hour limitations
HourTimestamp time.Time
// DayTimestamp: daily timestamp used to handle day limitations
DayTimestamp time.Time
// DmSent: quantity of dm sent in the last hour
DmSent int
// DmSentDay: quantity of dm sent in the last day
DmSentDay int
// MaxDmHour: maximum dm quantity per hour
MaxDmHour int `yaml:"dm_per_hour" validate:"numeric"`
// MaxDmDay: maximum dm quantity per day
MaxDmDay int `yaml:"dm_per_day" validate:"numeric"`
// Activated: quota manager activation boolean
Activated bool `yaml:"activated"`
}
// InitializeQuotaManager initialize Quota manager with user settings
func (qm *QuotaManager) InitializeQuotaManager() {
qm.HourTimestamp = time.Now()
qm.DayTimestamp = time.Now()
}
// ResetDailyQuotas reset daily dm counter and update timestamp
func (qm *QuotaManager) ResetDailyQuotas() {
qm.DmSentDay = 0
qm.DayTimestamp = time.Now()
}
// ResetHourlyQuotas reset hourly dm counter and update timestamp
func (qm *QuotaManager) ResetHourlyQuotas() {
qm.DmSent = 0
qm.HourTimestamp = time.Now()
}
// AddDm report to the manager a message sending. It increment dm counter and check if quotas are still valid.
func (qm *QuotaManager) AddDm() {
qm.DmSent++
qm.DmSentDay++
qm.CheckQuotas()
}
// CheckQuotas check if quotas have not been exceeded and pauses the program otherwise.
func (qm *QuotaManager) CheckQuotas() {
// Hourly quota checking
if qm.DmSent >= qm.MaxDmHour && qm.Activated {
if time.Since(qm.HourTimestamp).Seconds() < 3600 {
sleepDur := 3600 - time.Since(qm.HourTimestamp).Seconds()
logrus.Infof("Hourly quota reached, sleeping %f seconds...", sleepDur)
time.Sleep(time.Duration(sleepDur) * time.Second)
} else {
qm.ResetHourlyQuotas()
logrus.Info("Hourly quotas resetted.")
}
}
// Daily quota checking
if qm.DmSentDay >= qm.MaxDmDay && qm.Activated {
if time.Since(qm.DayTimestamp).Seconds() < 86400 {
sleepDur := 86400 - time.Since(qm.DayTimestamp).Seconds()
logrus.Infof("Daily quota reached, sleeping %f seconds...", sleepDur)
time.Sleep(time.Duration(sleepDur) * time.Second)
} else {
qm.ResetDailyQuotas()
logrus.Info("Daily quotas resetted.")
}
}
}
/* Schedule manager */
// SchedulerManager data
type SchedulerManager struct {
// BeginAt: Begin time setting
BeginAt string `yaml:"begin_at" validate:"contains=:"`
// EndAt: End time setting
EndAt string `yaml:"end_at" validate:"contains=:"`
// BeginAtTimestamp: begin timestamp
BeginAtTimestamp time.Time
// EndAtTimestamp: end timestamp
EndAtTimestamp time.Time
// Activated: quota manager activation boolean
Activated bool `yaml:"activated"`
}
// InitializeScheduler convert string time from config to time.Time instances
func (s *SchedulerManager) InitializeScheduler() error {
ttBegin, err := time.Parse("15:04", strings.TrimSpace(s.BeginAt))
if err != nil {
return err
}
s.BeginAtTimestamp = ttBegin
ttEnd, err := time.Parse("15:04", strings.TrimSpace(s.EndAt))
if err != nil {
return err
}
s.EndAtTimestamp = ttEnd
return nil
}
// CheckTime check scheduler and pause the bot if it's not working time
func (s *SchedulerManager) CheckTime() error {
if !s.Activated {
return nil
}
res, err := s.isWorkingTime()
if err == nil {
if res {
return nil
}
logrus.Info("Reached end of service. Sleeping...")
for {
if res, _ = s.isWorkingTime(); res {
break
}
if BotStruct.exitCh != nil {
select {
case <-BotStruct.hotReloadCallback:
if err = BotStruct.HotReload(); err != nil {
logrus.Errorf("Bot hot reload failed: %v", err)
BotStruct.hotReloadCallback <- false
} else {
logrus.Info("Bot hot reload successfully.")
BotStruct.hotReloadCallback <- true
}
break
case <-BotStruct.exitCh:
logrus.Info("Bot process successfully stopped.")
return errStopBot
default:
break
}
}
time.Sleep(10 * time.Second)
}
logrus.Info("Back to work!")
}
return nil
}
// Check if current time is between scheduler working interval
func (s *SchedulerManager) isWorkingTime() (bool, error) {
if s.BeginAtTimestamp.Equal(s.EndAtTimestamp) {
return false, errors.New("Bad scheduler configuration")
}
currentTime := time.Date(0, time.January, 1, time.Now().Hour(), time.Now().Minute(), 0, 0, time.Local)
if s.BeginAtTimestamp.Before(s.EndAtTimestamp) {
return !currentTime.Before(s.BeginAtTimestamp) && !currentTime.After(s.EndAtTimestamp), nil
}
return !s.BeginAtTimestamp.After(currentTime) || !s.EndAtTimestamp.Before(currentTime), nil
}
/* Blacklist manager */
// BlacklistManager data
type BlacklistManager struct {
// BlacklistedUsers: list of all blacklisted usernames
BlacklistedUsers [][]string
// Activated: quota manager activation boolean
Activated bool `yaml:"activated"`
}
// InitializeBlacklist check existence of the blacklist csv file and initialize it if it doesn't exist.
func (bm *BlacklistManager) InitializeBlacklist() error {
var err error
// Check if blacklist csv exist
_, err = os.Stat(fileBlacklistPath)
if err != nil {
if os.IsNotExist(err) {
// Create data folder if not exist
if _, err = os.Stat("data/"); os.IsNotExist(err) {
os.Mkdir("data/", os.ModePerm)
}
// Create and open csv blacklist
var f *os.File
f, err = os.OpenFile(fileBlacklistPath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return err
}
defer f.Close()
// Write csv header
writer := csv.NewWriter(f)
err = writer.Write([]string{"Username"})
defer writer.Flush()
if err != nil {
return err
}
} else {
return err
}
} else {
// Open existing blacklist and recover blacklisted usernames
f, err := os.OpenFile(fileBlacklistPath, os.O_RDONLY, 0644)
if err != nil {
return err
}
defer f.Close()
reader := csv.NewReader(f)
bm.BlacklistedUsers, err = reader.ReadAll()
if err != nil {
return err
}
}
return nil
}
// AddUser add argument username to the blacklist
func (bm *BlacklistManager) AddUser(user string) {
bm.BlacklistedUsers = append(bm.BlacklistedUsers, []string{user})
f, err := os.OpenFile(fileBlacklistPath, os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
logrus.Errorf("Failed to blacklist current user: %v", err)
}
defer f.Close()
writer := csv.NewWriter(f)
err = writer.Write([]string{user})
defer writer.Flush()
if err != nil {
logrus.Errorf("Failed to blacklist current user: %v", err)
}
}
// IsBlacklisted check if the given user is already blacklisted
func (bm *BlacklistManager) IsBlacklisted(user string) bool {
for _, username := range bm.BlacklistedUsers {
if username[0] == user {
return true
}
}
return false
}
// FilterScrappedUsers remove blacklisted users from WebElement slice and return it
func (bm *BlacklistManager) FilterScrappedUsers(users []selenium.WebElement) []selenium.WebElement {
var filteredUsers []selenium.WebElement
for _, user := range users {
username, err := user.Text()
if !bm.IsBlacklisted(username) && err == nil {
filteredUsers = append(filteredUsers, user)
}
}
return filteredUsers
}