Skip to content

Commit

Permalink
Merge pull request #8 from nyxx-discord/feature/reminder
Browse files Browse the repository at this point in the history
[feature] Reminder
  • Loading branch information
l7ssha authored Oct 10, 2021
2 parents 82f5af3 + 8f619a7 commit d78a775
Show file tree
Hide file tree
Showing 18 changed files with 517 additions and 153 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/push_docker_image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Create and publish a Docker image

on:
push:
branches: ['main']
release:
types:
- created

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.TOKEN_PUSH_IMAGE }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
28 changes: 3 additions & 25 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
FROM google/dart:2.14
FROM google/dart:2.14.3

WORKDIR /app

RUN git clone https://github.com/l7ssha/nyxx.git

WORKDIR /app/nyxx
RUN git fetch
RUN git checkout dev

WORKDIR /app/nyxx/nyxx
RUN dartdoc

WORKDIR /app/nyxx/nyxx_commander
RUN dartdoc

WORKDIR /app/nyxx/nyxx_extensions
RUN dartdoc

WORKDIR /app/nyxx/nyxx_interactions
RUN dartdoc

WORKDIR /app/bot

ADD pubspec.* /app/bot/
ADD pubspec.* /app
RUN pub get

ADD . /app/bot/
ADD . /app
RUN pub get --offline

RUN dart run ./scripts/genDocJson.dart

RUN dart compile exe bin/running_on_dart.dart

CMD []
Expand Down
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.PHONY: help
help:
@fgrep -h "##" $(MAKEFILE_LIST) | sed -e 's/\(\:.*\#\#\)/\:\ /' | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'

.PHONY: build
build: ## build images
docker compose build

.PHONY: build-no-cache
build-no-cache: ## build images discarding cache
docker compose build --no-cache

.PHONY: stop
stop: ## stop images
docker compose down

.PHONY: start
start: build ## start images from docker compose
docker compose up

.PHONY: clean
clean: stop ## clean container created by docker compose
docker compose rm

.PHONY: clean-start
clean-start: clean build-no-cache start ## cleans cache, build discarding cache and start dockers
24 changes: 19 additions & 5 deletions bin/running_on_dart.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import "dart:async";

import "package:nyxx/nyxx.dart" show ClientOptions, Nyxx;
import "package:nyxx/nyxx.dart" show ClientOptions, Nyxx, Snowflake;
import "package:nyxx_commander/commander.dart" show CommandGroup, Commander;
import "package:nyxx_interactions/interactions.dart" show CommandOptionBuilder, CommandOptionType, Interactions, SlashCommandBuilder;

Expand All @@ -9,7 +7,7 @@ import "package:running_on_dart/running_on_dart.dart" as rod;
late Nyxx botInstance;

void main(List<String> arguments) async {
rod.openDbAndRunMigrations();
await rod.openDbAndRunMigrations();

botInstance = Nyxx(
rod.botToken,
Expand All @@ -34,7 +32,8 @@ void main(List<String> arguments) async {
..registerSubCommand("get", rod.docsGetCommand)
..registerSubCommand("search", rod.docsSearchCommand))
// Minor commands
..registerCommand("info", rod.infoCommand);
..registerCommand("info", rod.infoCommand)
..registerCommand("remainder", rod.remainderCommand);

Interactions(botInstance)
..registerSlashCommand(SlashCommandBuilder("info", "Info about bot state", [])
Expand Down Expand Up @@ -81,5 +80,20 @@ void main(List<String> arguments) async {
CommandOptionBuilder(CommandOptionType.subCommand, "list", "Lists features enabled in guild")
..registerHandler(rod.listFeaturesSlash),
]))
..registerSlashCommand(SlashCommandBuilder("reminder", "Manages reminders", [
CommandOptionBuilder(CommandOptionType.subCommand, "create", "Creates reminder in current channel", options: [
CommandOptionBuilder(CommandOptionType.string, "trigger-at", "When reminder should go on", required: true),
CommandOptionBuilder(CommandOptionType.string, "message", "Additional message", required: true),
])..registerHandler(rod.reminderAddSlash),
CommandOptionBuilder(CommandOptionType.subCommand, "list", "List your current remainders")
..registerHandler(rod.remainderGetUsers),
CommandOptionBuilder(CommandOptionType.subCommand, "clear", "Clears all your remainders")
..registerHandler(rod.remaindersClear),
CommandOptionBuilder(CommandOptionType.subCommand, "remove", "Remove single remainder", options: [
CommandOptionBuilder(CommandOptionType.integer, "id", "Id of remainder to delete")
])..registerHandler(rod.remainderRemove)
]))
..syncOnReady();

await rod.initReminderModule(botInstance);
}
Empty file removed docfiles/.gitkeep
Empty file.
3 changes: 3 additions & 0 deletions lib/running_on_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ library running_on_dart;

export "src/commands/legacy/docsLegacy.dart";
export "src/commands/legacy/infoLegacy.dart";
export "src/commands/legacy/remainderLegacy.dart";
export "src/commands/legacy/voiceLegacy.dart";

export "src/commands/slash/docsSlash.dart";
export "src/commands/slash/infoSlash.dart";
export "src/commands/slash/reminderSlash.dart";
export "src/commands/slash/settingsSlash.dart";
export "src/commands/slash/tagsSlash.dart";
export "src/commands/slash/voiceSlash.dart";
Expand All @@ -14,4 +16,5 @@ export "src/commands/voiceCommon.dart" show adminBeforehandler;
export "src/internal/db.dart" show openDbAndRunMigrations;
export "src/modules/joinLogs.dart" show joinLogJoinEvent;
export "src/modules/nicknamePoop.dart" show nicknamePoopJoinEvent, nicknamePoopUpdateEvent;
export "src/modules/reminder/reminder.dart" show initReminderModule;
export "src/modules/settings/settings.dart" show prefixHandler, botToken, setIntents, cacheOptions, getFeaturesAsChoices;
2 changes: 1 addition & 1 deletion lib/src/commands/docsCommon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Future<MessageBuilder> docsGetMessageBuilder(String phrase) async {

Future<MessageBuilder> docsSearchMessageBuilder(String phrase) async {
final query = phrase.split(" ").last;
final results = searchDocs(query);
final results = await searchDocs(query).toList();

if(results.isEmpty) {
return MessageBuilder.content("Nothing found matching: `$query`");
Expand Down
30 changes: 30 additions & 0 deletions lib/src/commands/legacy/remainderLegacy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "package:human_duration_parser/human_duration_parser.dart";
import "package:nyxx/nyxx.dart";
import "package:nyxx_commander/commander.dart";
import "package:running_on_dart/src/modules/reminder/reminder.dart";

Future<void> remainderCommand(CommandContext ctx, String content) async {
final argString = ctx.getArguments().join(" ");

if (argString.isEmpty) {
await ctx.reply(MessageBuilder.content("Provide duration when remainder should be triggered"));
return;
}

final triggerDate = DateTime.now().add(parseStringToDuration(argString));
final replyMessage = ctx.message.referencedMessage?.message?.url;

if (replyMessage == null) {
await ctx.reply(MessageBuilder.content("Reply to message to create remainder"));
return;
}

final result = await createReminder(ctx.author.id, ctx.channel.id, triggerDate, replyMessage);

if (result) {
await ctx.reply(MessageBuilder.content("All right, <t:${triggerDate.millisecondsSinceEpoch ~/ 1000}:R> will remind about: $replyMessage"));
return;
}

await ctx.reply(MessageBuilder.content("Internal server error. Report to developer"));
}
12 changes: 9 additions & 3 deletions lib/src/commands/slash/infoSlash.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import "dart:math" show Random;
import "package:http/http.dart" as http;

import "package:nyxx/nyxx.dart" show Constants, DiscordColor, EmbedBuilder, MessageBuilder;
import "package:nyxx_interactions/interactions.dart" show InteractionEvent, SlashCommandInteractionEvent;
import "package:nyxx_interactions/interactions.dart" show ComponentMessageBuilder, ComponentRowBuilder, InteractionEvent, LinkButtonBuilder, SlashCommandInteractionEvent;
import "package:running_on_dart/src/commands/infoCommon.dart" show infoGenericCommand;

Future<void> infoSlashCommand(InteractionEvent event) async {
Future<void> infoSlashCommand(SlashCommandInteractionEvent event) async {
await event.acknowledge();

await event.respond(MessageBuilder.embed(await infoGenericCommand(event.client)));
final messageBuilder = ComponentMessageBuilder()
..embeds = [await infoGenericCommand(event.client)]
..components = [[
LinkButtonBuilder("Add nyxx to your guild", event.client.app.getInviteUrl())
]];

await event.respond(messageBuilder);
}

Future<void> pingSlashHandler(SlashCommandInteractionEvent event) async {
Expand Down
74 changes: 74 additions & 0 deletions lib/src/commands/slash/reminderSlash.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import "package:human_duration_parser/human_duration_parser.dart";
import "package:nyxx/nyxx.dart";
import "package:nyxx_interactions/interactions.dart";
import "package:running_on_dart/src/internal/utils.dart";
import "package:running_on_dart/src/modules/reminder/reminder.dart";

Future<void> reminderAddSlash(SlashCommandInteractionEvent event) async {
await event.acknowledge();

final authorId = getAuthorId(event);

final messageArg = event.getArg("message").value.toString();
final triggerAt = DateTime.now().add(
parseStringToDuration(event.getArg("trigger-at").value.toString())
);

final result = await createReminder(
authorId,
event.interaction.channel.id,
triggerAt,
messageArg
);

if (result) {
return event.respond(MessageBuilder.content("All right, <t:${triggerAt.millisecondsSinceEpoch ~/ 1000}:R> will remind about: `$messageArg`"));
}

return event.respond(MessageBuilder.content("Internal server error. Report to developer"));
}

Future<void> remainderGetUsers(SlashCommandInteractionEvent event) async {
await event.acknowledge(hidden: true);

final authorId = event.interaction.guild?.id != null
? event.interaction.memberAuthor!.id
: event.interaction.userAuthor!.id;

final remainders = fetchRemaindersForUser(authorId);

if (remainders.isEmpty) {
return event.respond(MessageBuilder.content("You dont have any remainders at the moment"));
}

final stringBuffer = StringBuffer("Your current remainders:\n");
for (final remainder in remainders) {
stringBuffer.writeln("- [ID: ${remainder.id}] <t:${remainder.triggerDate.millisecondsSinceEpoch ~/ 1000}:R> - ${remainder.message}\n");
}
stringBuffer.writeln("Remainders are cached for 30s. This could not be complete list of all remainders");

await event.respond(MessageBuilder.content(stringBuffer.toString()), hidden: true);
}

Future<void> remaindersClear(SlashCommandInteractionEvent event) async {
await event.acknowledge(hidden: true);

final authorId = getAuthorId(event);

final result = await clearRemaindersForUser(authorId);
return event.respond(MessageBuilder.content("Deleted $result remainders"));
}

Future<void> remainderRemove(SlashCommandInteractionEvent event) async {
await event.acknowledge(hidden: true);

final authorId = getAuthorId(event);
final id = event.getArg("id").value as int;

final result = await removeRemainderForUser(id, authorId);
if (!result) {
return event.respond(MessageBuilder.content("There was an problem deleting your remainder"));
}

return event.respond(MessageBuilder.content("Removed remainder with id: $id"));
}
20 changes: 19 additions & 1 deletion lib/src/internal/db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ bool dbStarted = false;
/// Postgres connection
PostgreSQLConnection get connection => _connection!;

FutureOr<void> openDbAndRunMigrations() async {
Future<void> openDbAndRunMigrations() async {
await Future.delayed(const Duration(seconds: 5)); // hack for postgres

_connection = PostgreSQLConnection(_dbHost, _dbPort, _dbName, username: _dbUser, password: _dbPassword);
Expand Down Expand Up @@ -75,6 +75,24 @@ FutureOr<void> openDbAndRunMigrations() async {
..enqueueMigration("1.5", """
ALTER TABLE feature_settings ADD COLUMN additional_data VARCHAR NULL;
""")
..enqueueMigration("1.6", """
CREATE TABLE reminders (
id SERIAL PRIMARY KEY,
user_id VARCHAR NOT NULL,
channel_id VARCHAR NOT NULL,
message_id VARCHAR NULL,
add_date TIMESTAMP NOT NULL,
trigger_date TIMESTAMP NOT NULL,
message VARCHAR(50) NOT NULL
);
CREATE INDEX reminder_trigger_date_idx ON reminders USING btree(trigger_date);
""")
..enqueueMigration("1.7", """
ALTER TABLE reminders ALTER COLUMN message TYPE VARCHAR(200)
""")
..enqueueMigration("1.8", """
ALTER TABLE reminders ADD COLUMN active BOOLEAN NOT NULL;
""")
..runMigrations();
}

Expand Down
8 changes: 7 additions & 1 deletion lib/src/internal/utils.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "dart:io" show Platform, ProcessInfo;

import "package:nyxx/nyxx.dart" show Nyxx;
import "package:nyxx/nyxx.dart" show Nyxx, Snowflake;
import "package:nyxx_interactions/interactions.dart";

String? get envPrefix => Platform.environment["ROD_PREFIX"];
String? get envHotReload => Platform.environment["ROD_HOT_RELOAD"];
Expand Down Expand Up @@ -46,3 +47,8 @@ String getApproxMemberCount(Nyxx client) {

return "$_approxMemberOnline/$_approxMemberCount";
}

Snowflake getAuthorId(InteractionEvent event) =>
event.interaction.guild?.id != null
? event.interaction.memberAuthor!.id
: event.interaction.userAuthor!.id;
Loading

0 comments on commit d78a775

Please sign in to comment.