Example #1
0
        // send or modify embed messages listing upcoming events from the raid calendar
        public async Task SendEvents(DbDiscordServer server)
        {
            // build embed
            var embed = BuildEventsEmbed(server);

            // check if we haven't set an embed message yet
            if (server.EventEmbedMessage == null)
            {
                // try to get a pre-existing event embed
                var oldEmbedMessage = await GetPreviousEmbed(server);

                // if we found a pre-existing event embed, set it as our current event embed message
                // and edit it
                if (oldEmbedMessage != null)
                {
                    server.EventEmbedMessage = oldEmbedMessage;
                    await server.EventEmbedMessage.ModifyAsync(m => { m.Embed = embed; });
                }
                // otherwise, send a new one and set it as our current event embed message
                else
                {
                    // send embed
                    var message = await server.ReminderChannel.SendMessageAsync(null, false, embed);

                    // store message id
                    server.EventEmbedMessage = message;
                }
            } // if we have set a current event embed message, edit it
            else
            {
                await server.EventEmbedMessage.ModifyAsync(m => { m.Embed = embed; });
            }
        }
        // log in to all servers
        public async Task Login(DbDiscordServer server)
        {
            var credentialPath = $@"{_credentialPathPrefix}/{server.ServerId}";

            using (var stream = new FileStream(_filePath, FileMode.Open, FileAccess.Read))
            {
                // build code flow manager to authenticate token
                var flowManager = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                {
                    ClientSecrets = GoogleClientSecrets.Load(stream).Secrets,
                    Scopes        = _scopes,
                    DataStore     = new FileDataStore(credentialPath, true)
                });

                var fileDataStore = new FileDataStore(credentialPath, true);
                var token         = await fileDataStore.GetAsync <TokenResponse>("token");

                // load token from file
                // var token = await flowManager.LoadTokenAsync(_userId, CancellationToken.None).ConfigureAwait(false);

                // check if we need to get a new token
                if (flowManager.ShouldForceTokenRetrieval() || token == null ||
                    token.RefreshToken == null && token.IsExpired(flowManager.Clock))
                {
                    return;
                }

                // set credentials to use for syncing
                server.GoogleUserCredential = new UserCredential(flowManager, _userId, token);
            }
        }
        // check if we're authorized and if we have a calendar id, and prompt the user to set up either if needed
        // returns true if we're authorized and have a calendar id, returns false if either checks are false
        public CalendarSyncStatus CheckIfSyncPossible(DbDiscordServer server)
        {
            // check if we have credentials for google apiitem
            if (server.GoogleUserCredential == null)
            {
                return(CalendarSyncStatus.NullCredentials);
            }

            if (server.CalendarId == "")
            {
                return(CalendarSyncStatus.NullCalendarId);
            }

            if (server.CalendarId == null)
            {
                return(CalendarSyncStatus.EmptyCalendarId);
            }

            /*
             * // if server object is assigned, the bot is connected, but the bot is not connected to this server, we're probably kicked
             * if (server.DiscordServerObject != null && server.DiscordServerObject.Available &&
             *  ((SocketGuild) server.DiscordServerObject).IsConnected == false)
             * {
             *  // DEBUG
             *  Task.Run((async () =>
             *  {
             *      Logger.Log(LogLevel.Debug, $"DEBUG - Name: {server.DiscordServerObject.Name} - Available: {server.DiscordServerObject.Available} " +
             *                                 $"Connected: {((SocketGuild)server.DiscordServerObject).IsConnected} - WE SHOULD NOT SEE THIS. THIS SHOULD BE HANDLED AT THE START OF A TIMER TICK.");
             *  }));
             *  return CalendarSyncStatus.ServerUnavailable;
             * }
             */

            return(CalendarSyncStatus.OK);
        }
        // called whenever .sync command is used, and at first program launch
        public async Task <bool> ManualSync(DbDiscordServer server = null, SocketCommandContext context = null)
        {
            // if server is null, context is not null - we're calling via command, so get the right server via context
            if (server == null && context != null)
            {
                server = DbDiscordServers.ServerList.Find(x => x.DiscordServerObject == context.Guild);
            }

            // check if we're authenticated and have a calendar id to sync from
            var syncStatus = CheckIfSyncPossible(server);

            if (syncStatus != CalendarSyncStatus.OK)
            {
                if (server == null && context != null)
                {
                    await context.Channel.SendMessageAsync($"Sync failed: {SyncFailedReason(syncStatus)}");
                }
                else
                {
                    await server.ConfigChannel.SendMessageAsync($"Sync failed: {SyncFailedReason(syncStatus)}");
                }

                return(false);
            }

            // perform the actual sync
            var success = SyncFromGoogleCalendar(server);

            // handle sync success or failure
            if (success)
            {
                // send message reporting we've synced calendar events
                string resultMessage = $":calendar: Synced {server.Events.Count} calendar events.";
                if (context != null) // we only want to send a message announcing sync success if the user sent the command
                {
                    await _interactiveService.ReplyAndDeleteAsync(context, resultMessage);
                }
            }
            else
            {
                // send message reporting there were no calendar events to sync
                string resultMessage = ":calendar: No events found in calendar.";
                if (context != null)
                {
                    await _interactiveService.ReplyAndDeleteAsync(context, resultMessage);
                }
            }

            // send/modify events embed in reminders to reflect newly synced values
            await _scheduleService.SendEvents(server);

            return(true);
        }
Example #5
0
        // searches the _reminderChannel for a message from the bot containing an embed (how else can we filter this - title?)
        // if it finds one, return that message to the calling method to be set as _eventEmbedMessage
        private async Task <IUserMessage> GetPreviousEmbed(DbDiscordServer server)
        {
            // get all messages in reminder channel
            // getmessages set to 500 to reduce the amount of times the bot has to repost the embed because ory's
            // group chats too much in their scheduling channel
            var messages = await server.ReminderChannel.GetMessagesAsync(500).FlattenAsync();

            // try to get a pre-existing embed message matching our usual event embed parameters
            // return the results
            try
            {
                var embedMsg = messages.Where(msg => msg.Author.Id == _discord.CurrentUser.Id)
                               .Where(msg => msg.Embeds.Count > 0)
                               .Where(msg => msg.Embeds.First().Title == "Schedule").ToList().First();
                return((IUserMessage)embedMsg);
            }
            catch
            {
                return(null);
            }
        }
Example #6
0
        // searches the _reminderChannel for a message from the bot containing the passed param
        // (this should be the title of an event for which we are looking for a remindermessage to edit)
        // if it finds one, return that message to the calling method to be modified
        private async Task <IUserMessage> GetPreviousReminderMessage(DbDiscordServer server, string messageContains)
        {
            // get all messages in reminder channel
            IEnumerable <IMessage> messages = null;

            while (messages == null)
            {
                try
                {
                    messages = await server.ReminderChannel.GetMessagesAsync().FlattenAsync();
                }
                catch
                {
                    Logger.Log(LogLevel.Debug, $"There was an error retrieving reminder messages, we're trying again...");
                }
                await Task.Delay(1000);
            }

            // try to get a pre-existing message matching messageContains (so {eventtitle})
            //return the results or null
            var reminderMsg = messages.Where(msg => msg.Author.Id == _discord.CurrentUser.Id).FirstOrDefault(msg => msg.Content.Contains(messageContains));

            return((IUserMessage)reminderMsg);
        }
Example #7
0
        // send or modify messages alerting the user that an event will be starting soon
        public async Task HandleReminders(DbDiscordServer server)
        {
            var firstCalendarEvent = server.Events[0];

            // look for any existing reminder messages in the reminders channel that contain the first event's name
            // if one exists, set that message as the first event's alert message
            // only set the first one so repeated event names don't all get assigned this message
            var oldReminderMessage = await GetPreviousReminderMessage(server, firstCalendarEvent.Name);

            if (oldReminderMessage != null && firstCalendarEvent.AlertMessage == null)
            {
                firstCalendarEvent.AlertMessage = oldReminderMessage;
            }

            foreach (var calendarEvent in server.Events)
            {
                // get amount of time between the calendarevent start time and the current time
                // and round it to the nearest 5m interval, so usually on the 5m interval
                var timeStartDelta = RoundToNearestMinutes(calendarEvent.StartDate - GetCurrentTimeAfterOffset(), 5);

                // if it's less than an hour but more than fifteen minutes, and we haven't sent an alert message, send an alert message
                if (timeStartDelta.TotalHours < 1 && timeStartDelta.TotalMinutes > 15)
                {
                    var messageContents =
                        $"{calendarEvent.Name} is starting in {(int)timeStartDelta.TotalMinutes} minutes.";

                    // if there's an alert message already, edit it
                    if (calendarEvent.AlertMessage != null)
                    {
                        await calendarEvent.AlertMessage.ModifyAsync(m => m.Content = messageContents);

                        Logger.Log(LogLevel.Debug, $"DEBUG - {server.ServerName} - Event upcoming (15m - 1h from now) or currently underway, alert message exists, editing it.");
                    }
                    // if there wasn't an alert message, send a new message
                    else
                    {
                        var msg = await server.ReminderChannel.SendMessageAsync(messageContents);

                        calendarEvent.AlertMessage = msg;
                        Logger.Log(LogLevel.Debug, $"DEBUG - {server.ServerName} - Event upcoming (15m - 1h from now) or currently underway, alert message does not exist, sending one.");
                    }
                }

                // if it's less than an hour and less or equal to fifteen minutes, try to modify an existing alert message or send a new one
                if (timeStartDelta.TotalHours < 1 && timeStartDelta.TotalMinutes <= 15)
                {
                    var messageContents = $"{calendarEvent.Name} is starting shortly ({(int)timeStartDelta.TotalMinutes}m).";

                    // if there's an alert message already, edit it
                    if (calendarEvent.AlertMessage != null)
                    {
                        await calendarEvent.AlertMessage.ModifyAsync(m => m.Content = messageContents);
                    }
                    // if there wasn't an alert message, send a new message
                    else
                    {
                        var msg = await server.ReminderChannel.SendMessageAsync(messageContents);

                        calendarEvent.AlertMessage = msg;
                    }
                }

                // if the event is currently active (after start date but before end date)
                // update the alert message to reflect how much time is left until the event is over
                if (calendarEvent.StartDate < GetCurrentTimeAfterOffset() &&
                    calendarEvent.EndDate > GetCurrentTimeAfterOffset())
                {
                    // get amount of time between the current time and the calendarevent end time
                    var timeEndDelta = RoundToNearestMinutes(calendarEvent.EndDate - GetCurrentTimeAfterOffset(), 5);

                    var messageContents = $"{calendarEvent.Name} is underway, ending in" + GetTimeDeltaFormatting(timeEndDelta) + ".";

                    // if there's an alert message already, edit it
                    if (calendarEvent.AlertMessage != null)
                    {
                        await calendarEvent.AlertMessage.ModifyAsync(m => m.Content = messageContents);
                    }
                    // if there wasn't an alert message, send a new message
                    else
                    {
                        var msg = await server.ReminderChannel.SendMessageAsync(messageContents);

                        calendarEvent.AlertMessage = msg;
                    }
                }

                // if the event is almost past, delete the alertmessage
                if (calendarEvent.EndDate < GetCurrentTimeAfterOffset() + TimeSpan.FromMinutes(5))
                {
                    await calendarEvent.AlertMessage.DeleteAsync();

                    calendarEvent.AlertMessage = null;
                }

                // if the event is over an hour from now and an alert message exists, delete it.
                // this should not normally occur as the alertmessage should be deleted just before the event has concluded.
                // If we get here, it typically signifies a timezone error or that the bot was unable to delete the old message.
                if (calendarEvent.StartDate > GetCurrentTimeAfterOffset() + TimeSpan.FromMinutes(60) && calendarEvent.AlertMessage != null)
                {
                    // await calendarEvent.AlertMessage.DeleteAsync();

                    calendarEvent.AlertMessage = null;
                    Logger.Log(LogLevel.Error,
                               $"{server.ServerName} - {calendarEvent.Name} on {calendarEvent.StartDate.DayOfWeek} at {calendarEvent.StartDate.TimeOfDay} - " +
                               $"The event start date is over an hour away, we would have deleted the alert message. " +
                               $"This should not occur normally, and typically signifies a timezone error, or that the bot was unable" +
                               $"to delete its previous message.");
                }
            }
        }
Example #8
0
        // put together the events embed & return it to calling method
        private Embed BuildEventsEmbed(DbDiscordServer server)
        {
            EmbedBuilder embedBuilder = new EmbedBuilder();

            // if there are no items in CalendarEvents, build a field stating so
            if (server.Events.Count == 0)
            {
                embedBuilder.AddField("No raids scheduled.", "Go play Maplestory or something.");
            }

            // iterate through each calendar event and build strings from them
            // if there are no events, the foreach loop is skipped, so no need to check
            foreach (var calendarEvent in server.Events)
            {
                // don't add items from the past
                if (calendarEvent.EndDate < GetCurrentTimeAfterOffset())
                {
                    continue;
                }

                // get the time difference between the event and now
                // roundtonearestminutes wrapper will round it to closest 5m interval
                TimeSpan timeDelta = default;

                // holy f*****g formatting batman
                StringBuilder stringBuilder = new StringBuilder();

                // if event hasn't started yet
                if (calendarEvent.StartDate > GetCurrentTimeAfterOffset())
                {
                    stringBuilder.AppendLine($"Starts on {calendarEvent.StartDate,0:M/dd} at {calendarEvent.StartDate,0: h:mm tt} {calendarEvent.Timezone} and ends at {calendarEvent.EndDate,0: h:mm tt} {calendarEvent.Timezone}");
                    stringBuilder.Append(":watch: Starts in");
                    timeDelta = RoundToNearestMinutes(calendarEvent.StartDate - GetCurrentTimeAfterOffset(), 5);
                }

                // if event has started but hasn't finished
                else if (calendarEvent.StartDate < GetCurrentTimeAfterOffset() &&
                         calendarEvent.EndDate > GetCurrentTimeAfterOffset())
                {
                    stringBuilder.AppendLine($"Currently underway, ending at {calendarEvent.EndDate,0: h:mm tt} {calendarEvent.Timezone}");
                    stringBuilder.Append(":watch: Ends in");
                    timeDelta = RoundToNearestMinutes(calendarEvent.EndDate - GetCurrentTimeAfterOffset(), 5);
                }


                // get formatting for timedelta
                stringBuilder.Append(GetTimeDeltaFormatting(timeDelta));

                stringBuilder.Append(".");

                // bundle it all together into a line for the embed
                embedBuilder.AddField($"{calendarEvent.Name}", stringBuilder.ToString());
            }

            // add the extra little embed bits
            embedBuilder.WithTitle("Schedule")
            .WithColor(Color.Blue)
            .WithFooter("Synced: ")
            // set the actual datetime value since discord timestamps
            // are timezone-aware (?)
            .WithTimestamp(DateTime.Now);

            // roll it all up and send it to the channel
            var embed = embedBuilder.Build();

            return(embed);
        }
Example #9
0
 // converts stored IDs from database into ulongs (mongo can't store ulong ha ha) and use them to
 // assign our discord objects
 public void SetServerDiscordObjects(DbDiscordServer server)
 {
     server.DiscordServerObject = _discord.GetGuild(Convert.ToUInt64(server.ServerId));
     server.ConfigChannel       = _discord.GetChannel(Convert.ToUInt64(server.ConfigChannelId)) as ITextChannel;
     server.ReminderChannel     = _discord.GetChannel(Convert.ToUInt64(server.ReminderChannelId)) as ITextChannel;
 }
        // logic for pulling data from api and adding it to CalendarEvents list, returns bool representing
        // if calendar had events or not
        public bool SyncFromGoogleCalendar(DbDiscordServer server)
        {
            // Set the timespan of events to sync
            var min = _scheduleService.GetCurrentTimeAfterOffset();
            var max = _scheduleService.GetCurrentTimeAfterOffset().AddMonths(1);

            // pull events from the specified google calendar
            // string is the calendar id of the calendar to sync with
            var events = GetCalendarEvents(server.GoogleUserCredential, server.CalendarId, min, max);

            // declare events to use for list comparisons
            List <CalendarEvent> oldEventsList = new List <CalendarEvent>();
            List <CalendarEvent> newEventsList = new List <CalendarEvent>();

            oldEventsList.AddRange(server.Events);
            server.Events.Clear();

            // if there are events, iterate through and add them to our calendarevents list
            if (events.Any())
            {
                // build a list of the events we pulled from gcal
                foreach (var eventItem in events)
                {
                    // we should detect the timezone here in some fashion and then correct the below start/end datetimes to the corresponding offsets
                    var test = eventItem.Start.TimeZone;

                    // api wrapper will always pull times in local time aka eastern because it sucks
                    eventItem.Start.DateTime = eventItem.Start.DateTime - TimeSpan.FromHours(_scheduleService.GetTimezoneOffset());
                    eventItem.End.DateTime   = eventItem.End.DateTime - TimeSpan.FromHours(_scheduleService.GetTimezoneOffset());

                    // don't add items from the past
                    if (eventItem.End.DateTime < _scheduleService.GetCurrentTimeAfterOffset())
                    {
                        continue;
                    }

                    DateTime startDate;
                    DateTime endDate;

                    if (eventItem.Start.DateTime.HasValue == false || eventItem.End.DateTime.HasValue == false)
                    {
                        startDate = DateTime.Parse(eventItem.Start.Date);
                        endDate   = DateTime.Parse(eventItem.End.Date);
                    }
                    else
                    {
                        startDate = eventItem.Start.DateTime.Value;
                        endDate   = eventItem.End.DateTime.Value;
                    }

                    // build calendar event to be added to our list
                    var calendarEvent = new CalendarEvent()
                    {
                        Name      = eventItem.Summary,
                        StartDate = startDate,
                        EndDate   = endDate,
                        Timezone  = "PST",
                        UniqueId  = eventItem.Id
                    };

                    newEventsList.Add(calendarEvent);
                }

                // build our working list of calendarevents, mixing old event items (if any) and new ones
                if (oldEventsList.Count == 0)
                {
                    // if calendarevents list (and thus oldeventslist) is empty, we're running for the first time
                    // so just add newEventsList to calendarevents and be done
                    server.Events.AddRange(newEventsList);
                }
                else
                {
                    // match events we just pulled from google to events we have stored already, by start date
                    // store new name (this doesn't matter), start and endgames from new list into CalendarEvents
                    // keep existing alert flags
                    var oldEventsDict = oldEventsList.ToDictionary(n => n.UniqueId);
                    foreach (var n in newEventsList)
                    {
                        CalendarEvent o;
                        if (oldEventsDict.TryGetValue(n.UniqueId, out o))
                        {
                            var calendarEvent = new CalendarEvent();
                            calendarEvent.Name         = n.Name;
                            calendarEvent.Timezone     = o.Timezone;
                            calendarEvent.AlertMessage = o.AlertMessage;
                            calendarEvent.UniqueId     = o.UniqueId;

                            // if this event's been manually adjusted, keep the old values
                            if (o.ManuallyAdjusted)
                            {
                                calendarEvent.StartDate = o.StartDate;
                                calendarEvent.EndDate   = o.EndDate;
                            }
                            else // else accept the new values
                            {
                                calendarEvent.StartDate = n.StartDate;
                                calendarEvent.EndDate   = n.EndDate;
                            }

                            server.Events.Add(calendarEvent);
                        }

                        else
                        {
                            server.Events.Add(n);
                        }
                    }
                }
                return(true); // calendar had events, and we added them
            }
            return(false);    // calendar did not have events
        }