This is just a program that displays summary statistics about discord messages sent to a certain user using a Discord data package. The CSV of messages to a user is "c{idOfChannel}\messages.csv"
. It's formatted as:
ID,Timestamp,Contents,Attachments
I'm semi-new to programming and have always been bad. I never bothered to make things look good in any way, but I want to improve my problem solving and, more importantly, code writing (so it's readable, maintainable, etc.). This is my first attempt, and I guess the only change I think I should make is maybe putting certain groups of methods into some specific class so it's less convoluted. I'm not sure.
using CsvHelper;
using CsvHelper.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
static class Discord_Data_Analyser
{
static string messagesFolderPath;
static string channelId;
static string configPath;
static dynamic records;
static List<string> words;
static void Main()
{
channelId = GetChannelID();
configPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\x.config";
messagesFolderPath = GetMessagesFolderPath() + $"\\c{channelId}\\messages.csv";
records = GetRecords();
words = GetWords();
while (true)
{
Console.Clear();
ConsoleKey dataQueryChoice = GetDataQueryChoice();
switch (dataQueryChoice)
{
case ConsoleKey.D1: // instances of word
string wordToCount = GetWordForInstanceQuery();
int instancesOfWord = GetInstancesOfWord(wordToCount);
Console.Clear();
Console.WriteLine($"{wordToCount} appears {instancesOfWord} times.");
break;
case ConsoleKey.D2: // top used words
var wordCounts = GetWordCounts();
int wordsToDisplay = GetNumberOfTopWordsToDisplay();
PrintTopWords(wordCounts, wordsToDisplay);
break;
case ConsoleKey.D3: // message increase per month
Dictionary<string, int> messagesPerMonth = GetMessagesPerMonth();
PrintMessagesPerMonthTable(messagesPerMonth);
break;
}
Console.WriteLine("\nPress enter to restart.");
Console.ReadLine();
}
}
static void PrintTopWords(IOrderedEnumerable<KeyValuePair<string, int>> wordCounts, int wordsToDisplay)
{
Console.Clear();
int i = 0;
int sum = 0;
foreach (var pair in wordCounts)
{
i++;
sum += pair.Value;
Console.WriteLine($"{pair.Key}: {pair.Value}");
if (i >= wordsToDisplay && wordsToDisplay != -1)
break;
}
int percent = (int)Math.Round((double)(100 * sum) / words.Count);
Console.WriteLine($"Top {wordsToDisplay} words account for {percent}% of all words.");
}
static void PrintMessagesPerMonthTable(Dictionary<string, int> messagesPerMonth)
{
Console.Clear();
Console.WriteLine("Date Messages Sent Increase/Decrease Since Last Month");
Console.WriteLine("+-------+--------------+---------------------------------+");
string previousMonth = null;
int previousCount = 0;
int messagesSum = 0;
foreach (var pair in messagesPerMonth.Reverse())
{
messagesSum += pair.Value;
string currentMonth = pair.Key;
int currentCount = pair.Value;
if (previousMonth != null)
{
char plusSign = new char();
double percentChange = Math.Round(((double)currentCount - previousCount) / previousCount * 100);
if (percentChange >= 0)
{
plusSign = '+';
}
Console.Write($"{currentMonth} ");
Console.Write(currentCount + new string(' ', 15 - currentCount.ToString().Length));
Console.Write($"{plusSign}{percentChange}%\n");
}
else
{
Console.WriteLine($"{currentMonth} {currentCount}");
}
previousMonth = currentMonth;
previousCount = currentCount;
}
Console.WriteLine($"Total: {messagesSum}");
}
static List<dynamic> GetRecords()
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{ Delimiter = "," };
List<dynamic> recordsList = new List<dynamic>();
using (StreamReader reader = new StreamReader(messagesFolderPath))
using (CsvReader csv = new CsvReader(reader, config))
{
IEnumerable<dynamic> records = csv.GetRecords<dynamic>();
recordsList.AddRange(records);
}
return recordsList;
}
static Dictionary<string, int> GetMessagesPerMonth()
{
var messagesPerMonth = new Dictionary<string, int>();
foreach (dynamic record in records)
{
string month = record.Timestamp.Substring(0, 7);
if (messagesPerMonth.ContainsKey(month))
{
messagesPerMonth[month]++;
}
else
{
messagesPerMonth.Add(month, 1);
}
}
return messagesPerMonth;
}
static string FilterContents(string contents)
{
// replace with space otherwise "COCK!^!" turns into "cock^" and fails the "cock" comparison
return contents
.Replace("!", " ")
.Replace("@", " ")
.Replace("#", " ")
.Replace("?", " ")
.Replace("(", " ")
.Replace(")", " ")
.Replace("\\", " ")
.Replace("[", " ")
.Replace("]", " ")
.Replace(";", " ")
.Replace("'", " ")
.Replace("/", " ");
}
static List<string> GetWords()
{
var words = new List<string>();
foreach (dynamic record in records)
{
string contents = FilterContents(record.Contents).ToLower();
string[] wordsInCurrentRecord = contents.Split(new char[] { ' ', ',', '.' }, StringSplitOptions.RemoveEmptyEntries);
words.AddRange(wordsInCurrentRecord);
}
return words;
}
static IOrderedEnumerable<KeyValuePair<string, int>> GetWordCounts()
{
var wordCounts = new Dictionary<string, int>();
foreach (string word in words)
{
if (wordCounts.ContainsKey(word))
{
wordCounts[word]++;
}
else
{
wordCounts[word] = 1;
}
}
return wordCounts.OrderByDescending(pair => pair.Value);
}
static int GetInstancesOfWord(string wordToCount)
{
int instancesOfWord = 0;
foreach (string word in words)
{
if (word == wordToCount)
{
instancesOfWord++;
}
}
return instancesOfWord;
}
static string GetWordForInstanceQuery()
{
Console.Clear();
Console.Write("Word: ");
return Console.ReadLine().Trim().ToLower();
}
static int GetNumberOfTopWordsToDisplay()
{
while (true)
{
Console.Clear();
Console.Write("Number of top words (-1 for all): ");
string input = Console.ReadLine().Trim();
if (int.TryParse(input, out int numberOfTopWords))
{
return numberOfTopWords;
}
else
{
Console.WriteLine("Invalid input. Press Enter.");
Console.ReadLine();
}
}
}
static string GetChannelID()
{
while (true)
{
Console.Clear();
Console.Write("Channel ID: ");
string channelId = Console.ReadLine().Trim();
if (channelId.All(char.IsDigit))
{
return channelId;
}
else
{
Console.WriteLine("Invalid input. Press Enter.");
Console.ReadLine();
}
}
}
static string GetMessagesFolderPathFromConfig()
{
foreach (string line in File.ReadAllLines(configPath))
{
if (line.StartsWith("MessagesFolderPath="))
{
return line.Substring("MessagesFolderPath=".Length);
}
}
return "";
}
static void GenerateConfigFile(string data)
{
File.Create(configPath).Close();
File.WriteAllText(configPath, data);
}
static ConsoleKey GetDataQueryChoice()
{
while (true)
{
Console.Clear();
Console.WriteLine(@"Query:
[1] Total instances of a word
[2] Top used words
[3] Message increase per month
");
ConsoleKey key = Console.ReadKey().Key;
if (key == ConsoleKey.D1 || key == ConsoleKey.D2 || key == ConsoleKey.D3)
{
return key;
}
else
{
Console.Clear();
Console.WriteLine("Invalid input. Press Enter.");
Console.ReadLine();
}
}
}
static string GetMessagesFolderPath()
{
while (true)
{
if (!File.Exists(configPath))
{
Console.Clear();
Console.Write("Path to 'messages' folder: ");
string messagesFolderPath = Console.ReadLine().Trim().ToLower();
char lastCharOfPath = messagesFolderPath.Last();
if (lastCharOfPath == '\\' || lastCharOfPath == '/')
{
messagesFolderPath = messagesFolderPath.Remove(messagesFolderPath.Length - 1);
}
if (!Directory.Exists(messagesFolderPath))
{
Console.WriteLine("Path doesn't exist. Press Enter.");
Console.ReadLine();
continue;
}
GenerateConfigFile("Config data for discord data app\n\nMessagesFolderPath=" + messagesFolderPath);
return messagesFolderPath;
}
else
{
return GetMessagesFolderPathFromConfig();
}
}
}
}