2
\$\begingroup\$

It might help to check my previous question out, its part of the same application. The only minor changes are with the entity class names. They now each have their own entities, and I use interfaces for polymorphism so each site has its own entity, rather than ambious class names like ProfilePost, ProfileConnection, which represented all implementations (sites).

This class is probably the worst area of the whole application, as time developed it got larger and larger, and I wasn't at a point where I could stop for long enough to completely refactor it.

What does this class do?

It implements the ISocialEventHandler, and each implementation of my scraper has its own event handler for each site. This allows each site to have slightly different ways of doing things to scrape it. If you see my previous question, you'll see how this class gets used in practice.

What do I want help with?

I would like to try and get some advice on how I can reduce the size of it, and what areas of the class shouldn't be part of the event handler. I guess I sort of used this class as "everything that needs to be done for each implementation of a scraper type".

I already sort of know what to do, although it's hard to apply that in practice when you have never done it before, it is also hard to know which areas to focus on, what is bad code if you were never taught what bad code was?

When giving suggestions, it would help me to know what sort of class names I should give new classes, its an area I struggle most in and I usually end up making classes that don't have descriptive names, or don't explain what they are doing.

Please also give any other general recommendations, to the class in question or associated classes.

So TLDR: Can someone give me a practical example (in code) of how I can I abstract areas the following class, into their own areas (SRP).

Here is the class in question (500+ lines):

public class DellyEventHandler : AbstractSocialEventHandler, ISocialEventHandler
{
    private bool _loginNeeded = true;

    private readonly IWebDriver _webDriver;
    private readonly ScraperAccountDao _accountDao;
    private readonly DellyProfileDao _profileDao;
    private readonly ScraperSettings _scraperSettings;
    private readonly IScraperValidator _scraperValidator;
    private readonly ILogger _logger;
    private readonly Client _bugSnagClient;
    private readonly NotificationSender _notificationSender;

    private ScraperAccount _currentAccount;
    private string _currentItem;
    private int _itemsProcessedSinceLogin;

    public DellyEventHandler(
        IWebDriver webDriver, 
        ScraperAccountDao accountRepository, 
        DellyProfileDao profileDao, 
        ScraperSettings scraperSettings, 
        IScraperValidator scraperValidator, 
        ILogger logger,
        Client bugsnagClient,
        NotificationSender notificationSender)
    {
        _webDriver = webDriver;
        _accountDao = accountRepository;
        _profileDao = profileDao;
        _scraperSettings = scraperSettings;
        _scraperValidator = scraperValidator;
        _logger = logger;
        _bugSnagClient = bugsnagClient;
        _notificationSender = notificationSender;
    }

    public override string GetLoginPageUrl()
    {
        return "https://delly.test/accounts/login";
    }

    public override string GetLogoutPageUrl()
    {
        return "https://delly.test/accounts/logout";
    }

    public override void NavigateToProfile()
    {
        _webDriver.Navigate().GoToUrl(_currentItem);
    }

    public override bool TryWaitForProfileToLoad()
    {
        return _webDriver.WaitUntilVisible(By.XPath(ScraperDomSelectors.DellyProfileFollowers), 3) != null;
    }

    public override bool IsLoginNeeded()
    {
        return _loginNeeded;
    }

    public override void Login()
    {
        if (!_loginNeeded)
        {
            return;
        }

        if (_webDriver.PageSource.Contains("js logged-in"))
        {
            Logout();
        }

        _webDriver.Navigate().GoToUrl(GetLoginPageUrl());

        if (!TryGetAccount(out _currentAccount))
        {
            WaitUntilAccountAvailable();
            Login();
            return;
        }

        var usernameField = _webDriver.WaitUntilVisible(By.Name("username"), 5);
        var passwordField = _webDriver.WaitUntilVisible(By.Name("password"), 0);

        if (usernameField == null || passwordField == null)
        {
            throw new Exception("Failed to find the required login form fields.");
        }

        usernameField.SendKeys(_currentAccount.Username);
        passwordField.SendKeys(_currentAccount.Password);
        passwordField.SendKeys(Convert.ToString(Convert.ToChar((object)57351)));

        if (_webDriver.WaitUntilVisible(By.Id("slfErrorAlert"), 3) != null)
        {
            OnFailedLogin();
            return;
        }

        if (_webDriver.PageSource.Contains("challengeType") || _webDriver.PageSource.Contains("RecaptchaChallengeForm") || _webDriver.PageSource.Contains("Help Us Confirm It's You"))
        {
            OnFailedLogin();
            return;
        }

        _itemsProcessedSinceLogin = 0;
        _loginNeeded = false;
    }

    private void OnFailedLogin()
    {
        _accountDao.MarkAccountAsDisabled(_currentAccount.Id);
        
        Logout();
        Login();
    }

    private void WaitUntilAccountAvailable()
    {
        _logger.Warning($"Worker {StaticState.WorkerId} is waiting for an available account.");

        var waitingSince = DateTime.Now;
        var waitTicks = 0;
        
        while (!TryGetAccount(out var _, false))
        {
            // If we've been waiting for an account for longer than 30 minutes, lets send a notification as a warning.
            
            if (waitTicks >= 60)
            {
                _notificationSender.SendNotification($"Worker {StaticState.WorkerId} has been waiting for an account since {waitingSince.ToShortTimeString()}.", "829YaL");
                waitTicks = 0;
            }
            
            Thread.Sleep(TimeSpan.FromSeconds(30));
            waitTicks++;
        }
        
        _logger.Success("We found an available account, lets continue.");
    }

    public override ISocialProfile CreateProfile()
    {
        var bio = GetBio();
        var name = GetName();

        var profile =  new DellyProfile(_profileDao, _scraperSettings, _scraperValidator)
        {
            Url = GetUrl(),
            Username = GetUsername(),
            Name = name,
            Picture = GetProfilePicture(),
            Bio = bio,
            IsPrivate = GetIsPrivate(),
            FollowerCount = GetFollowerCount(),
            FollowingCount = GetFollowingCount(),
            SnapchatFound = 
                _scraperValidator.StringContainsSnapchatUsername(bio.ToLower()) || 
                _scraperValidator.StringContainsSnapchatUsername(name.ToLower())
        };

        if (_scraperValidator.TryExtractAgeFromString(bio, out var ageGuessed))
        {
            profile.AgeGuessed = ageGuessed;
        }

        if (_scraperValidator.StringContainsFemaleName(name.CleanQueueItem()) || _scraperValidator.StringContainsFemaleName(profile.Username.CleanQueueItem()))
        {
            profile.GenderGuessed = 'F';
        }
        else
        {
            profile.GenderGuessed = 'M';
        }

        if (!profile.SnapchatFound)
        {
            return profile;
        }
        
        if (_scraperValidator.TryExtractSnapchatUsernameFromString(bio.ToLower(), out var snapchatUsername) ||
            _scraperValidator.TryExtractSnapchatUsernameFromString(name.ToLower(), out snapchatUsername))
        {
            profile.SnapchatUsername = snapchatUsername;
        }

        return profile;
    }

    private bool TryGetAccount(out ScraperAccount profile, bool markAsFetched = true)
    {
        return _accountDao.TryFindAccount(
            out profile,
            1,
            int.Parse(_scraperSettings["minutes_to_wait_since_account_throttled"]),
            int.Parse(_scraperSettings["minutes_to_wait_since_account_fetched"]),
            markAsFetched);
    }

    public override void Logout()
    {
        _loginNeeded = true;
        _webDriver.Navigate().GoToUrl(GetLogoutPageUrl());
    }

    private long GetProfileId()
    {
        var jse = (IJavaScriptExecutor) _webDriver;
        var profileIdFromSharedData = jse.ExecuteScript("return window._sharedData.entry_data.ProfilePage[0].logging_page_id;")?.ToString();

        if (profileIdFromSharedData != null && long.TryParse(profileIdFromSharedData.Split("_").Last(), out var profileId))
        {
            return profileId;
        }
        
        var username = new Uri(_currentItem).AbsolutePath.Replace("/", "");
            
        var possibleProfileId = _webDriver.PageSource.GetInbetween(
            "window.__additionalDataLoaded('/" + username + "/',{\"logging_page_id\":\"", "\",");

        if (string.IsNullOrEmpty(possibleProfileId) || !long.TryParse(possibleProfileId.Split("_").Last(), out profileId))
        {
            throw new Exception("Failed to find the unique ID for the profile.");
        }

        return profileId;
    }
    
    public override List<DellyConnection> GetConnections()
    {
        var connections = new List<DellyConnection>();
        var endCursor = "";

        while (true)
        {
            if (connections.Count >= int.Parse(_scraperSettings["maximum_connections_to_scrape_for_profile"]))
            {
                break; // We've got enough
            }

            var profileId = GetProfileId();

            if (profileId < 1)
            {
                throw new Exception("Failed to find the unique ID for the profile.");
            }
            
            var jse = (IJavaScriptExecutor) _webDriver;
            var csrfToken = jse.ExecuteScript("return window._sharedData.config.csrf_token;")?.ToString();
            
            HttpResponseMessage httpResponse;
            
            try
            {
                httpResponse = DellyConnectionCollector.GetConnectionsFromApi(profileId, csrfToken, _webDriver.Manage().Cookies.AllCookies, endCursor);
            }
            catch (AggregateException)
            {
                SwitchAccount(true);
                continue;
            }

            if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests)
            {
                _logger.Warning($"Connections have been throttled for {_currentAccount.Username} ({_currentAccount.Id}).");
                
                SwitchAccount(true);
                continue;
            }

            var responseText = httpResponse.Content.ReadAsStringAsync().Result;

            if (string.IsNullOrEmpty(responseText))
            {
                _logger.Warning("The Delly testing API returned an empty string for connection nodes.");
                return new List<DellyConnection>();
            }

            if (responseText.Contains("https://www.delly.test/challenge/"))
            {
                _logger.Warning($"Connections have been challenged for {_currentAccount.Username} ({_currentAccount.Id}).");
                
                _accountDao.MarkAccountAsDisabled(_currentAccount.Id);
                
                Logout();
                Login();
                
                NavigateToProfile();
                continue;
            }
            
            // TODO: Validate if it contains valid json.

            JObject responseJsonObject = JObject.Parse(responseText);
            
            var connectionNodes = JsonConvert.DeserializeObject<JArray>(responseJsonObject["data"]["user"]["followed_by"].ToString());

            foreach (var connection in connectionNodes)
            {
                connections.Add(new DellyConnection
                {
                    Item = "https://delly.test/" + connection["node"]["username"],
                    IsPrivate = connection["node"]["is_private"].ToString() == "True",
                });
            }

            if (responseJsonObject["data"]["user"]["followed_by"]["page_info"]["has_next_page"].ToString().ToLower() != "true")
            {
                break;
            }

            endCursor = responseJsonObject["data"]["user"]["followed_by"]["page_info"]["end_cursor"].ToString();

            Thread.Sleep(1000);
        }

        return connections;
    }
    
    public override string GetUrl()
    {
        return _webDriver.Url;
    }

    public override string GetUsername()
    {
        return _webDriver.WaitUntilVisible(By.XPath(ScraperDomSelectors.DellyProfileUsername), 0)?.GetAttribute("innerHTML") ?? "";
    }

    public override string GetName()
    {
        return _webDriver.WaitUntilVisible(By.XPath(ScraperDomSelectors.DellyProfileName), 0)?.GetAttribute("innerHTML") ?? GetUsername();
    }

    public override string GetProfilePicture()
    {
        return _webDriver.WaitUntilVisible(By.XPath(ScraperDomSelectors.DellyProfilePicture), 0).GetAttribute("src");
    }

    public override string GetBio()
    {
        var bioElement = _webDriver.WaitUntilVisible(By.XPath(ScraperDomSelectors.DellyProfileBio), 0);
        return bioElement == null ? "" : bioElement.GetAttribute("innerText").Replace(Environment.NewLine, " ");
    }
    public override bool GetIsPrivate()
    {
        return _webDriver.PageSource.Contains("This Account is Private");
    }

    public override int GetFollowerCount()
    {
        return GetFollowBoxCount(ScraperDomSelectors.DellyProfileFollowers);
    }

    public override int GetFollowingCount()
    {
        return GetFollowBoxCount(ScraperDomSelectors.DellyProfileFollowing);
    }

    private int GetFollowBoxCount(string xpathSelector)
    {
        var followingElement = _webDriver.WaitUntilVisible(By.XPath(xpathSelector), 0);
        var titleAttributeValue = followingElement.GetAttribute("title");

        if (titleAttributeValue.Length > 0)
        {
            return int.Parse(titleAttributeValue.Replace(",", ""));
        }

        return int.Parse(followingElement.GetAttribute("innerHTML").Replace(",", ""));
    }

    public override List<DellyPost> GetPosts(string owner)
    {
        return new List<DellyPost>
        {
            new DellyPost
            {
                Media = GetPostMedia(owner)
            }
        };
    }

    public override List<DellyPostMedia> GetPostMedia(string owner)
    {
        var media = new List<DellyPostMedia>();

        foreach (var domElement in _webDriver.FindElements(By.XPath(ScraperDomSelectors.DellyProfilePostPicture)).Take(20))
        {
            if (string.IsNullOrEmpty(domElement.GetProperty("src")))
            {
                continue;
            }

            media.Add(new DellyPostMedia
            {
                CdnUrl = domElement.GetProperty("src"),
                MetaData = domElement.GetAttribute("alt")
            });
        }

        return media;
    }

    public override void SetCurrentItem(string item)
    {
        _currentItem = item;
        _itemsProcessedSinceLogin++;

        if (_itemsProcessedSinceLogin > int.Parse(_scraperSettings["maximum_scrapes_before_forced_login"]))
        {
            // Lets test on another account, switch it.
            _loginNeeded = true;
        }
    }

    public override List<DellyConnection> GetFilteredConnections(List<DellyConnection> connections)
    {
        var filteredConnections = new List<DellyConnection>();

        foreach (var connection in connections)
        {
            var connectionUrl = connection.Item;
            
            if (!_scraperValidator.StringContainsMaleFirstName(connectionUrl.CleanQueueItem()) && 
                !_scraperValidator.StringContainsMaleUsername(connectionUrl.CleanQueueItem()) && 
                !_scraperValidator.StringContainsAnythingBad(connectionUrl) &&
                !_scraperValidator.StringContainsAnythingBad(connectionUrl.CleanQueueItem()))
            {
                filteredConnections.Add(connection);
            }
        }

        return filteredConnections;
    }

    public override string GetPageSource()
    {
        return _webDriver.PageSource;
    }

    public override List<ScraperQueueItem> ConvertConnectionsToQueueItems(List<DellyConnection> connections)
    {
        var queueItems = new List<ScraperQueueItem>();

        foreach (var connection in connections)
        {
            var isConfirmed = true; // TODO

            queueItems.Add(new ScraperQueueItem(0, Utilities.GetConsistentSocialUrl(connection.Item), 1, connection.IsPrivate, isConfirmed));
        }

        return queueItems;
    }

    public override bool IsProfileVisitsThrottled()
    {
        return _webDriver.PageSource.Contains("wait a few minutes before you try again");
    }

    public override bool IsProfileNotFound()
    {
        return _webDriver.PageSource.Contains("Page Not Found");
    }

    public override void SwitchAccount(bool markCurrentAsThrottled)
    {
        if (markCurrentAsThrottled)
        {
            _accountDao.MarkAccountAsThrottled(_currentAccount.Id);
        }

        Logout();
        Login();

        NavigateToProfile();
    }
}

Below you can find snippets of classes involved with the class I'm focusing on.

ScraperSettings:

public class ScraperSettings : Dictionary<string, string>
{
    private readonly ScraperSettingsDao _scraperSettingsDao;

    private readonly Timer _timer;
    
    public ScraperSettings(ScraperSettingsDao scraperSettingsDao)
    {
        _scraperSettingsDao = scraperSettingsDao;
        _timer = new Timer(Load, null, 0, 60 * 1000);
        
        Load();
    }

    private void Load(object o = null)
    {
        foreach (DataRow settingsRow in _scraperSettingsDao.GetSettingsTable().Rows)
        {
            this[(string)settingsRow["name"]] = (string)settingsRow["value"];
        }
    }
}

ScraperAccount (entity, database mapper to and from):

public class ScraperAccount
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    public int TypeId { get; set; }
}

IScraperValidator:

public interface IScraperValidator
{
    bool StringContainsMaleFirstName(string str);
    bool StringContainsFemaleName(string str);
    bool StringContainsMaleUsername(string str);
    bool IsStringForeign(string str);
    bool StringContainsSnapchatUsername(string str);
    bool TryExtractSnapchatUsernameFromString(string str, out string snapchatUsername);
    bool TryExtractAgeFromString(string str, out int age);
    bool StringContainsGenericBadPhrase(string str);
    bool StringContainsPromotion(string str);
    bool StringContainsMakeUpPhrase(string str);
    bool StringContainsDeletedProfilePhrase(string str);
    bool StringContainsAnythingBad(string str);
}

IScraperValidator Implementation (DellyValidator):

public class DellyValidator : IScraperValidator
{
    private readonly List<string> _maleFirstNamePhrases;
    private readonly List<string> _maleUsernamePhrases; 
    private readonly List<string> _femaleFirstNamePhrases; 

    public DellyValidator(
        List<string> maleFirstNamePhrases, 
        List<string> maleUsernamePhrases, 
        List<string> femaleFirstNamePhrases)
    {
        _maleFirstNamePhrases = maleFirstNamePhrases;
        _maleUsernamePhrases = maleUsernamePhrases;
        _femaleFirstNamePhrases = femaleFirstNamePhrases;
    }

    public bool StringContainsMaleFirstName(string str)
    {
        return _maleFirstNamePhrases.Any(s => str.StartsWith(s, StringComparison.InvariantCultureIgnoreCase));
    }

    public bool StringContainsFemaleName(string str)
    {
        return _femaleFirstNamePhrases.Any(s => str.Contains(s, StringComparison.InvariantCultureIgnoreCase));
    }

    public bool StringContainsMaleUsername(string str)
    {
        return _maleUsernamePhrases.Any(s => str.Contains(s, StringComparison.InvariantCultureIgnoreCase));
    }

    public bool IsStringForeign(string str)
    {
        var foreignTriggers = new List<string>
        {
            "african american", "romanian", "italian", "nigerian", "australian", "russian", "aussie", "australian", "australia", "collumbian",
            "french", "brazilian", "arabic", "polish", "swedish",

            "california", "washington", "michigan", "arizona", "massachusetts", "pennsylvania", "wisconsin",  "connecticut", "louisiana", "kentucky"
        };

        return foreignTriggers.Any(str.ContainsIgnoreCase) || HasArabicCharacters(str);
    }

    private bool HasArabicCharacters(string str)
    {
        return new Regex("[\u0600-\u06ff]|[\u0750-\u077f]|[\ufb50-\ufc3f]|[\ufe70-\ufefc]").IsMatch(str);
    }

    public bool StringContainsSnapchatUsername(string str)
    {
        var snapTriggers = new List<string>
        {
            "👻", "𝑺𝒄 ", "𝑺𝒄 :", "𝚜𝚗𝚊𝚙𝚌𝚑𝚊𝚝", "sc’", "sc - ", "sc ", "sc:", "scm ", "scm;", "sc- ", "sc.", "sc-", "sc ~", "sc;", "sc; ", "sc~", "sc/", "snapchat:",
            "snapchat - ", "snapchat-", "snap~", "snap =", "snap:", "add my sc", "add me on sc", "add me sc", "add my snap", "add my snapchat", "add me on snap",
            "scm~", "scm ~", "sc•", "sc •", "🆂🅲 "
        };

        return snapTriggers.Any(str.Contains);
    }

    public bool TryExtractSnapchatUsernameFromString(string str, out string snapchatUsername)
    {
        if (
            TryExtractSnapchatUsernameFromConvention("sc:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc-", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc -", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc ~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm ~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc;", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc👻:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc👻~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc •", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc•", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm-", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm👻:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm👻~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm •", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("scm•", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap-", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap;", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap👻:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap👻~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("👻snap", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap •", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snap•", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("sc/", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat-", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat -", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat;", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat👻:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat👻~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat •", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("snapchat•", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("👻:", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("👻-", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("👻~", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("👻;", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("👻•", str, out snapchatUsername) ||
            TryExtractSnapchatUsernameFromConvention("𝕊𝕟𝕒𝕡-", str, out snapchatUsername))
        {
            snapchatUsername = Regex.Replace(snapchatUsername, @"\p{Cs}", ""); // Lets remove any unicode such as emojis.
        }
        else if (str.Contains("sc "))
        {
            var sequences = str.Split(" ").SkipWhile(x => x != "sc");
            snapchatUsername = sequences.Count() > 0 ? sequences.Skip(1).First() : "";
        }
        else if (str.StartsWith("👻 "))
        {
            snapchatUsername = str.Split(" ").SkipWhile(x => x != "👻").Skip(1).First();
        }
        else if (str.StartsWith("👻"))
        {
            snapchatUsername = str.Split(" ").SkipWhile(x => !x.StartsWith("👻")).First().Substring("👻".Length);
        }
        else if (str.Split(" ").Any(x => x.EndsWith("👻")))
        {
            snapchatUsername = str.Split(" ").SkipWhile(x => !x.EndsWith("👻")).First();
        }

        snapchatUsername = Regex.Replace(snapchatUsername, @"\p{Cs}", ""); // Lets remove any unicode such as emojis.
        snapchatUsername = snapchatUsername.Replace("•", "");

        return !string.IsNullOrEmpty(snapchatUsername) && Utilities.IsValidSnapchatUsername(snapchatUsername);
    }

    private bool TryExtractSnapchatUsernameFromConvention(string convention, string str, out string snapchatUsername)
    {
        if (str.Contains(convention + " "))
        {
            var possibleConventions = str.Split(" ").SkipWhile(x => x != convention);
            
            if (!possibleConventions.Any())
            {
                var everythingAfterConvention = str.Substring(str.IndexOf(convention) + convention.Length + 1);

                if (!everythingAfterConvention.Contains(" "))
                {
                    snapchatUsername = everythingAfterConvention;
                }
                else
                {
                    snapchatUsername = everythingAfterConvention.Split(" ").First();
                }
            }
            else
            {
                snapchatUsername = str.Split(" ").SkipWhile(x => x != convention).Skip(1).First();
            }
        }
        else if (str.StartsWith(convention) && !str.Contains(" "))
        {
            snapchatUsername = str.Substring(convention.Length);
        }
        else if (str.Contains(convention))
        {
            if (str.Contains(" ") && str.Split(" ").Last().Contains(convention))
            {
                snapchatUsername = str.Split(" ").Last().Substring(convention.Length);
            }
            else if (str.Contains(" ") && str.Split(" ").First().Contains(convention))
            {
                snapchatUsername = str.Split(" ").First().Substring(convention.Length);
            }
            else
            {
                var firstWordWithConvention = str.Split(" ").SkipWhile(x => x != convention).FirstOrDefault();

                if (firstWordWithConvention == null)
                {
                    snapchatUsername = "";
                }
                else
                {
                    snapchatUsername = firstWordWithConvention.Substring(convention.Length);
                }
            }
        }
        else
        {
            snapchatUsername = string.Empty;
        }

        return !string.IsNullOrEmpty(snapchatUsername);
    }

    public bool TryExtractAgeFromString(string str, out int age)
    {
        var allowedAgesForSuccess = new List<int> { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };

        var twelveYearsOldFilters = new List<string>
            {"12•", "//12", "|12|", "12 x", "year 7", "12💞", "12❤️", "1️⃣2️⃣", "12yo "};
        
        var thirteenYearsOldFilters = new List<string>
            {"13•", "//13", "|13|", "13 x", "year 8", "13💞", "13❤️", "1️⃣3️⃣", "13yo "};
        
        var fourteenYearsOldFilters = new List<string>
            {"14•", "//14", "|14|", "14 x", "year 9", "14💞", "14❤️", "1️⃣4️⃣", "14yo "};
        
        var fifteenYearsOldFilters = new List<string>
            {"15•", "//15", "|15|", "15 x", "year 10", "15💞", "15❤️", "1️⃣5️⃣", "15yo"};
        
        var sixteenYearsOldFilters = new List<string>
            {"16•", "//16", "|16|", "16 x", "year 11", "6teen", "16💞", "16❤️", "16~", "16yo "};
        
        var seventeenYearsOldFilters = new List<string>
            {"17•", "//17", "|17|", "17 x", "s•e•v•e•n•t•e•e•n", "seventeen xo", "year 12", "7teen", "17💞", "17❤️", "17~", "17yo "};
        
        var eighteenYearsOldFilters = new List<string>
            {"18•", "//18", "|18|", "18 x", "year 13", "8teen", "18💞", "18❤️", "18~", "18yo "};
        
        var nineteenYearsOldFilters = new List<string> {"19•", "//19", "|19|", "19 x", "9teen", "19💞", "19❤️", "19~"};
        
        var twentyYearsOldFilters = new List<string> {"20•", "//20", "|20|", "20 x", "20💞", "20❤️", "20~"};
        
        var twentyOneYearsOldFilters = new List<string> {"21•", "//21", "|21|", "21 x", "21💞", "21❤️", "21~"};
        
        var twentyTwoYearsOldFilters = new List<string> {"22•", "//22", "|22|", "22 x", "22💞", "22❤️", "22~"};
        
        var twentyThreeYearsOldFilters = new List<string> {"23•", "//23", "|23|", "23 x", "23💞", "23❤️", "23~"};
        
        var twentyFourYearsOldFilters = new List<string> {"24•", "//24", "|24|", "24 x", "24💞", "24❤️", "24~"};

        if (twelveYearsOldFilters.Where(str.Contains).Any())
        {
            age = 12;
        }
        else if (thirteenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 13;
        }
        else if (fourteenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 14;
        }
        else if (fifteenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 15;
        }
        else if (sixteenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 16;
        }
        else if (seventeenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 17;
        }
        else if (eighteenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 18;
        }
        else if (nineteenYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 19;
        }
        else if (twentyYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 20;
        }
        else if (twentyOneYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 21;
        }
        else if (twentyTwoYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 22;
        }
        else if (twentyThreeYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 23;
        }
        else if (twentyFourYearsOldFilters.Where(str.ContainsIgnoreCase).Any())
        {
            age = 24;
        }
        else
        {
            age = 0;
        }

        return allowedAgesForSuccess.Contains(age);
    }

    public bool StringContainsPromotion(string str)
    {
        var promotions = new List<string>()
        {
            "save up to", "drinks from ", "10% off", "for sale"
        };

        return promotions.Any(x => str.ContainsIgnoreCase(x));
    }

    public bool StringContainsMakeUpPhrase(string str)
    {
        var makeupPhrases = new List<string>()
        {
            "makeupby", "hairby", "nailsby", ".nails", "nails.", "aesthetics", "lashesby", "lashes_by"
        };

        return makeupPhrases.Any(x => str.ContainsIgnoreCase(x));
    }

    public bool StringContainsDeletedProfilePhrase(string str)
    {
        var deletedPhrases = new List<string>()
        {
            "notactive", "notusing", "notbeingused", "deactivated", "deleted", "delete", "notused", "oldaccount"
        };

        return deletedPhrases.Any(x => str.ContainsIgnoreCase(x));
    }

    public bool StringContainsGenericBadPhrase(string str)
    {
        var badPhrases = new List<string>()
        {
            "ignore_delly_scraper", "#noscrape"
        };

        return badPhrases.Any(x => str.ContainsIgnoreCase(x));
    }

    /// <summary>
    /// Primary method to check all sub-methods
    /// Checks GenericBadPhrase, DeletedProfilePhrase and MakeUpPhrase
    /// </summary>
    /// <returns></returns>
    public bool StringContainsAnythingBad(string str)
    {
        return StringContainsGenericBadPhrase(str) ||
               StringContainsDeletedProfilePhrase(str) ||
               StringContainsMakeUpPhrase(str);
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Nice follow up question, but please provide the code for the abstract class AbstractSocialEventHandler. You are getting into more areas of SOLID in this question. \$\endgroup\$ – pacmaninbw Sep 30 '20 at 13:38
  • \$\begingroup\$ I know you want to improve your coding abilities and that is great, but right now no one is going to answer this question because it is off-topic. Without the definition for the base class of DellyEventHandler we don't really have enough information to provide a good review. Please read the asking guidelines in our help center, they are the rules we go by here. Read at least the first 4, but it might help to read all of them. \$\endgroup\$ – pacmaninbw Sep 30 '20 at 20:14
  • \$\begingroup\$ Will upload a github repo link shortly, thanks. \$\endgroup\$ – user231071 Oct 1 '20 at 10:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.