This repository has been archived by the owner on Jan 28, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
549 lines (500 loc) · 25.6 KB
/
Program.cs
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
using System;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Runtime;
using System.Collections.Generic;
using System.Threading.Tasks;
using Tweetinvi;
using Tweetinvi.Models;
using System.IO;
using System.Threading;
using System.Linq;
namespace RandomRSSTwitterBot
{
class RRTB
{
// Variables for use throughout
private static string path = Directory.GetCurrentDirectory();
private static string logsPath = path + "/logs/";
private static string startTime = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
private static List<string> titles = new List<string>();
private static List<string> links = new List<string>();
private static int urlNum;
private static int i;
private static bool failure = false;
private static int attempts = 0;
private static int attemptMax;
private static bool test = false;
private static bool queueRun = false;
private static int queueAttempt = 0;
private static int queueGap;
private static string url;
private static int hour = DateTime.Now.Hour;
private static int value;
private static int timerMS;
private static double threshold;
private static string[] keys = { "", "", "", "" };
private static Random rand = new Random();
static async Task Main()
{
// Checking if log folder exists, creating it if not
if (!Directory.Exists(path + "/logs"))
{
Directory.CreateDirectory(path + "/logs");
}
File.WriteAllText(logsPath + startTime + ".txt", "");
// Boot message
Console.WriteLine(DateTime.Now + ": RandomRSSTwitterBot v0.5.3 booting up!" + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": RandomRSSTwitterBot v0.5.3 booting up!" + Environment.NewLine + Environment.NewLine);
// Checking if config file exists, creating it if not and exiting to allow user to input keys
if (!File.Exists(path + "/config.txt"))
{
File.WriteAllText(path + "/config.txt",
"INPUT ALL ENTRIES ON NEXT LINE" + Environment.NewLine + Environment.NewLine + Environment.NewLine +
"===== GENERAL =====" + Environment.NewLine + Environment.NewLine +
"POST HOW OFTEN (IN HOURS)? 0 TO TEST ONCE AND EXIT" + Environment.NewLine + "[DEFAULT: 1] [INTEGER]" + Environment.NewLine + "1" + Environment.NewLine + Environment.NewLine +
"CHECK TIMER HOW OFTEN (IN MILLISECONDS)?" + Environment.NewLine + "[DEFAULT: 30000] [INTEGER]" + Environment.NewLine + "30000" + Environment.NewLine + Environment.NewLine +
"HOW MUCH OF GAP BETWEEN CYCLES TO CHECK QUEUE (1 = EVERY OTHER CYCLE, 2 = EVERY THIRD, ETC.)?" + Environment.NewLine + "[DEFAULT: 0] [INTEGER]" + Environment.NewLine + "0" + Environment.NewLine + Environment.NewLine +
"NUMBER OF TIMES TO RUN DUPLICATE PREVENTION BEFORE GIVING UP UNTIL NEXT CYCLE?" + Environment.NewLine + "[DEFAULT: 10] [INTEGER]" + Environment.NewLine + "10" + Environment.NewLine + Environment.NewLine +
"STATISTICAL STANDARD SCORE THRESHOLD FOR FREQUENCY CHECKS? WORKS ONLY POSITIVE, NOT NEGATIVE (FOR MORE INFO: https://en.wikipedia.org/wiki/Standard_score)" + Environment.NewLine + "[DEFAULT: 1] [DOUBLE (DECIMAL UP TO 15 DIGITS)]" + Environment.NewLine + "1" + Environment.NewLine + Environment.NewLine + Environment.NewLine +
"===== KEYS =====" + Environment.NewLine + Environment.NewLine +
"CONSUMER KEY" + Environment.NewLine + "<INPUT KEY HERE>" + Environment.NewLine + Environment.NewLine +
"CONSUMER SECRET" + Environment.NewLine + "<INPUT KEY HERE>" + Environment.NewLine + Environment.NewLine +
"ACCESS TOKEN" + Environment.NewLine + "<INPUT KEY HERE>" + Environment.NewLine + Environment.NewLine +
"ACCESS TOKEN SECRET" + Environment.NewLine + "<INPUT KEY HERE>"
);
Console.WriteLine(DateTime.Now + ": You need to input your authentication keys into config.txt!" + Environment.NewLine + "Press any key to exit...");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": You need to input your authentication keys into config.txt!" + Environment.NewLine + "Press any key to exit...");
Console.ReadKey();
Environment.Exit(0);
}
// Interprets config file for later use
else
{
string[] config = File.ReadAllLines(path + "/config.txt");
foreach (string item in config)
{
value = Int32.Parse(config[7]);
timerMS = Int32.Parse(config[11]);
queueGap = Int32.Parse(config[15]);
attemptMax = Int32.Parse(config[19]);
threshold = Double.Parse(config[23]);
keys[0] = config[29];
keys[1] = config[32];
keys[2] = config[35];
keys[3] = config[38];
}
}
// Checking if other required files and mult folder exist, creating them if not
if (!File.Exists(path + "/sources.txt"))
{
CreateFile("sources", "https://news.google.com/rss/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRGRqTVhZU0FtVnVHZ0pWVXlnQVAB");
}
if (!File.Exists(path + "/postings.txt"))
{
CreateFile("postings", "");
}
if (!File.Exists(path + "/queue.txt"))
{
CreateFile("queue", "");
}
if (!Directory.Exists(path + "/mult"))
{
Directory.CreateDirectory(path + "/mult");
}
if (!File.Exists(path + "/frequency.txt"))
{
CreateFile("frequency", "0");
// Adding new entries for all sources at start, in case file was deleted
bool runOnce = false;
foreach (string item in File.ReadAllLines(path + "/sources.txt"))
{
if (runOnce == false)
{
runOnce = true;
}
else
{
File.AppendAllText(path + "/frequency.txt", Environment.NewLine + "0");
}
}
}
// Cleaning up frequency.txt in case new sources were added, to prevent crashing
else
{
UpdateFrequency();
}
// Running bot test if config specified
if (value == 0)
{
Console.WriteLine(DateTime.Now + ": Running bot test!" + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Running bot test!" + Environment.NewLine + Environment.NewLine);
test = true;
await Seek();
}
// Running bot as normal if no test being ran
else
{
// Properly formatting hour for use
hour = hour + value - 1;
if (hour >= 24)
{
hour = hour - 24;
}
// Initiation message
Console.WriteLine(DateTime.Now + ": Running bot every " + value + " hour(s)!" + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Running bot every " + value + " hour(s)!" + Environment.NewLine + Environment.NewLine);
// Checking change of hour per timer length as defined in config, aligning with frequency in hours to run and initiate next cycle
while (true)
{
await Timer();
// Cleaning up for next cycle, to preserve variables used in multiple places and keep memory usage low
attempts = 0;
failure = false;
queueRun = false;
titles.Clear();
links.Clear();
}
}
}
// Seeking article to tweet
static async Task Seek(int seekNum = -1)
{
// Cleaning up frequency.txt in case new sources were added, to prevent crashing
UpdateFrequency();
try
{
// Ensures method is being ran first time in cycle, as opposed to if duplicate was found
if (seekNum == -1)
{
Console.WriteLine(DateTime.Now + ": Seeking article to post...");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Seeking article to post..." + Environment.NewLine);
}
// Checking if queue has any entries in it and using first one if so then bypassing rest of method
string[] queue = File.ReadAllLines(path + "/queue.txt");
if (queue.Length > 1 && seekNum == -1)
{
// Checking if enough cycles have passed since last queue pull
if (queueAttempt >= queueGap)
{
queueAttempt = 0;
titles.Add(queue[0]);
url = queue[1];
queueRun = true;
// Removing first item from queue and moving everything else up behind it
File.WriteAllText(path + "/queue.txt", "");
bool runOnce = false;
for (int r = 2; r < queue.Length; r++)
{
if (runOnce == false)
{
File.AppendAllText(path + "/queue.txt", queue[r]);
runOnce = true;
}
else
{
File.AppendAllText(path + "/queue.txt", Environment.NewLine + queue[r]);
}
}
// Tweeting from queue, bypassing rest of method
await Tweet();
return;
}
// What happens if enough cycles have not passed since last queue pull
else
{
queueAttempt = queueAttempt + 1;
Console.WriteLine(DateTime.Now + ": Bot will use queue in " + (queueGap - queueAttempt + 1) + " cycle(s)!");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Bot will use queue in " + (queueGap - queueAttempt + 1) + " cycle(s)!" + Environment.NewLine);
}
}
// Reset queue gap counter if queue is empty, in case queue is manually cleared
else if (queue.Length <= 1 && queueAttempt > 0 && seekNum == -1)
{
queueAttempt = 0;
Console.WriteLine(DateTime.Now + ": Queue is empty, resetting queue attempts...");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Queue is empty, resetting queue attempts..." + Environment.NewLine);
}
// What happens if queue is empty
// Finding random source if method is being ran first time in cycle
string[] sources = File.ReadAllLines(path + "/sources.txt");
if (seekNum == -1)
{
urlNum = rand.Next(0, sources.Length);
url = sources[urlNum];
// Checking if source has multiple feeds
CheckMult(seekNum);
}
// Using source chosen in first cycle if duplicate was found
else
{
urlNum = seekNum;
url = sources[urlNum];
// Checking if source has multiple feeds
CheckMult(seekNum);
}
// Calculating frequency of source via statistical standard score
string[] freq = File.ReadAllLines(path + "/frequency.txt");
List<double> freqD = new List<double>();
foreach (string item in freq)
{
freqD.Add((double)Int32.Parse(item));
}
double avg = freqD.Average();
double sum = freqD.Sum(d => Math.Pow(d - avg, 2));
double sd = Math.Sqrt((sum) / freqD.Count());
double z = ((double)Int32.Parse(freq[urlNum]) - avg) / sd;
freqD.Clear();
int urlCurrent = urlNum;
// What happens if source is too frequent
if (z >= threshold)
{
Console.WriteLine(DateTime.Now + ": Source too frequent, choosing again...");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Source too frequent, choosing again..." + Environment.NewLine);
// Sifting through other sources until one is found that is not same as first chosen or another that is also too frequent
while (urlNum == urlCurrent)
{
urlNum = rand.Next(0, sources.Length);
url = sources[urlNum];
z = ((double)Int32.Parse(freq[urlNum]) - avg) / sd;
if (z >= threshold)
{
urlNum = urlCurrent;
}
}
// Checking if source has multiple feeds
CheckMult();
}
// Pulling feed from source
XmlReader read = XmlReader.Create(url);
SyndicationFeed rss = SyndicationFeed.Load(read);
read.Close();
// Extracting only relevant info from feed for use
foreach (SyndicationItem item in rss.Items)
{
titles.Add(item.Title.Text);
links.Add(item.Links[0].Uri.ToString());
}
// Finding random item from feed
i = rand.Next(0, titles.Count);
// Ensuring item has not been posted prior
foreach (string item in File.ReadAllLines(path + "/postings.txt"))
{
if (titles[i] == item)
{
failure = true;
}
}
// Tweeting item if not duplicate
if (failure == false)
{
await Tweet();
read.Dispose();
}
// What happens if item is duplicate
else
{
// Attempts to find new item in same source up to ten times before giving up until next cycle
if (attempts < attemptMax)
{
failure = false;
attempts = attempts + 1;
titles.Clear();
links.Clear();
Console.WriteLine(DateTime.Now + ": Duplicate chosen, trying again... (Attempt number: " + attempts + ")");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Duplicate chosen, trying again... (Attempt number: " + attempts + ")" + Environment.NewLine);
read.Dispose();
await Seek(urlNum);
}
// What happens when finding new item in same source is unsuccessful after ten attempts and bot has given up until next cycle
else
{
Console.WriteLine(DateTime.Now + ": Failure to find suitable article within 10 attempts, press any key to exit..." + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Failure to find suitable article within 10 attempts, will try again later..." + Environment.NewLine + Environment.NewLine);
}
}
}
catch (IOException error)
{
Console.Error.WriteLine(error);
}
}
// Tweeting found article
static async Task Tweet()
{
// Checks if tweeting from queue, uses relevant message if so
if (queueRun == true)
{
Console.WriteLine(DateTime.Now + ": Tweeting from queue!" + Environment.NewLine + titles[0] + Environment.NewLine + url);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Tweeting from queue!" + Environment.NewLine + titles[0] + Environment.NewLine + url + Environment.NewLine);
}
// Relevant message for tweeting from random source
else
{
Console.WriteLine(DateTime.Now + ": Suitable article found!" + Environment.NewLine + titles[i] + Environment.NewLine + links[i]);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Suitable article found!" + Environment.NewLine + titles[i] + Environment.NewLine + links[i] + Environment.NewLine);
File.AppendAllText(path + "/postings.txt", titles[i] + Environment.NewLine);
}
// Tweeting
try
{
Console.WriteLine(DateTime.Now + ": Tweeting article..." + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Tweeting article..." + Environment.NewLine + Environment.NewLine);
// Authenticating account
var userClient = new TwitterClient(keys[0], keys[1], keys[2], keys[3]);
var user = await userClient.Users.GetAuthenticatedUserAsync();
// Tweeting from queue
if (queueRun == true)
{
var tweet = await userClient.Tweets.PublishTweetAsync(titles[0] + "\n" + url);
}
// Tweeting from random source
else
{
var tweet = await userClient.Tweets.PublishTweetAsync(titles[i] + "\n" + links[i]);
// Updating frequency.txt
string[] freq = File.ReadAllLines(path + "/frequency.txt");
freq[urlNum] = (Int32.Parse(freq[urlNum]) + 1).ToString();
File.WriteAllText(path + "/frequency.txt", "");
bool runOnce = false;
foreach (string item in freq)
{
if (runOnce == false)
{
File.AppendAllText(path + "/frequency.txt", item);
runOnce = true;
}
else
{
File.AppendAllText(path + "/frequency.txt", Environment.NewLine + item);
}
}
}
// Checking if bot was being tested, exiting if so
if (test == true)
{
Console.WriteLine(DateTime.Now + ": Press any key to exit...");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Press any key to exit...");
Console.ReadKey();
Environment.Exit(0);
}
// Returning to beginning for timer to check for next cycle initiation
else
{
return;
}
}
catch (IOException error)
{
Console.Error.WriteLine(error);
}
}
// Method for checking change of hour per timer length as defined in config, aligning with frequency in hours to run and initiate next cycle
static async Task Timer()
{
try
{
Thread.Sleep(timerMS);
if (hour < DateTime.Now.Hour || (hour <= 23 && DateTime.Now.Hour == hour + value - 24))
{
hour = DateTime.Now.Hour + value - 1;
if (hour >= 24)
{
hour = hour - 24;
}
await Seek();
}
else
{
return;
}
}
catch (IOException error)
{
Console.Error.WriteLine(error);
}
}
// Method for cleaning up frequency.txt in case new sources were added, to prevent crashing
static void UpdateFrequency()
{
try
{
bool runOnce = false;
string[] freq = File.ReadAllLines(path + "/frequency.txt");
string[] sources = File.ReadAllLines(path + "/sources.txt");
if (sources.Length != freq.Length)
{
Console.WriteLine(DateTime.Now + ": Adding new lines for new sources to frequency.txt file..." + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Adding new lines for new sources to frequency.txt file..." + Environment.NewLine + Environment.NewLine);
File.WriteAllText(path + "/frequency.txt", "");
int r = 0;
foreach (string item in sources)
{
if (runOnce == false)
{
File.AppendAllText(path + "/frequency.txt", freq[r]);
runOnce = true;
}
else if (r < freq.Length)
{
File.AppendAllText(path + "/frequency.txt", Environment.NewLine + freq[r]);
}
else
{
File.AppendAllText(path + "/frequency.txt", Environment.NewLine + "0");
}
r = r++;
}
}
}
catch (IOException error)
{
Console.Error.WriteLine(error);
}
}
// Method for checking if source has multiple feeds
static void CheckMult(int checkNum = -1)
{
try
{
// Checking if source has file with multiple feeds specified
if (File.Exists(path + "/mult/" + urlNum + ".txt"))
{
Console.WriteLine(DateTime.Now + ": Chose source #" + urlNum + " (" + url + ")!");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Chose source #" + urlNum + " (" + url + ")!" + Environment.NewLine);
Console.WriteLine(DateTime.Now + ": Source has multiple feeds, choosing one...");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Source has multiple feeds, choosing one..." + Environment.NewLine);
string[] multSources = File.ReadAllLines(path + "/mult/" + urlNum + ".txt");
int urlNumMult = rand.Next(0, multSources.Length);
url = multSources[urlNumMult];
Console.WriteLine(DateTime.Now + ": Chose source feed #" + urlNumMult + " (" + url + ")!");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Chose source feed #" + urlNumMult + " (" + url + ")!" + Environment.NewLine);
}
// What happens if no file exists specifying multiple feeds
else if (checkNum == -1)
{
Console.WriteLine(DateTime.Now + ": Chose source #" + urlNum + " (" + url + ")!");
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Chose source #" + urlNum + " (" + url + ")!" + Environment.NewLine);
}
}
catch (IOException error)
{
Console.Error.WriteLine(error);
}
}
// Method for creating required files if missing
static void CreateFile(string fileName, string extraInfo)
{
try
{
Console.WriteLine(DateTime.Now + ": Creating " + fileName + ".txt file..." + Environment.NewLine);
File.AppendAllText(logsPath + startTime + ".txt", DateTime.Now + ": Creating " + fileName + ".txt file..." + Environment.NewLine + Environment.NewLine);
File.WriteAllText(path + "/" + fileName + ".txt", extraInfo);
}
catch (IOException error)
{
Console.Error.WriteLine(error);
}
}
}
}