Skip to content

Commit

Permalink
Merge pull request #144 from nyxx-discord/main
Browse files Browse the repository at this point in the history
Update dev to main
  • Loading branch information
abitofevrything authored Nov 16, 2023
2 parents 5a9959d + 60f5900 commit 7520bd3
Show file tree
Hide file tree
Showing 66 changed files with 5,177 additions and 4,132 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ build/
doc/
.vscode/
.idea/
pubspec_overrides.yaml
153 changes: 140 additions & 13 deletions CHANGELOG.md

Large diffs are not rendered by default.

54 changes: 51 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,59 @@
# Nyxx commands
# nyxx_commands

Nyxx commands is a framework for easily creating slash commands and text commands for Discord using the [nyxx](https://pub.dev/packages/nyxx) library.
nyxx_commands is a framework for easily creating slash commands and text commands for Discord using the [nyxx](https://pub.dev/packages/nyxx) library.

Insipred by [discord.py](https://discordpy.readthedocs.io/en/stable/)'s [commands](https://discordpy.readthedocs.io/en/stable/ext/commands/index.html) extension.
Inspired by [discord.py](https://discordpy.readthedocs.io/en/stable/)'s [commands](https://discordpy.readthedocs.io/en/stable/ext/commands/index.html) extension.

Need help with nyxx_commands? Join our [Discord server](https://discord.gg/nyxx) and ask in the `#nyxx_commands` channel.

## Features
- Easy command creation
- Automatic compatibility with Discord slash commands
- Compatibility with the [nyxx](https://pub.dev/packages/nyxx) library
- Argument parsing

## Compiling nyxx_commands

If you compile a bot using nyxx_commands with `dart compile exe`, you might get an error you hadn't seen during development:
```
Error: Function data was not correctly loaded. Did you compile the wrong file?
Stack trace:
#0 loadFunctionData (package:nyxx_commands/src/mirror_utils/compiled.dart:10)
#1 ChatCommand._loadArguments (package:nyxx_commands/src/commands/chat_command.dart:354)
...
```

This is because nyxx_commands uses `dart:mirrors` to load the arguments for your commands. During development this is fine but this functionality breaks when compiled because [`dart:mirrors` cannot be used in compiled Dart programs](https://api.dart.dev/dart-mirrors/dart-mirrors-library.html#status-unstable).

To mitigate this, nyxx_commands provides a script that compiles your code for you and loads this information for nyxx_commands to use. To use it, you must first wrap all your command callbacks in [`id`](https://pub.dev/documentation/nyxx_commands/latest/nyxx_commands/id.html) and give each function a unique id.


For example, this chat command:
```dart
final ping = ChatCommand(
'ping',
'Ping the bot',
(IChatContext context) => context.respond(MessageBuilder.content('Pong!')),
);
```
must have its callback wrapped with `id` like so:
```dart
final ping = ChatCommand(
'ping',
'Ping the bot',
id('ping', (IChatContext context) => context.respond(MessageBuilder.content('Pong!'))),
);
```

If you forget to add the `id` to a function, you'll get an error similar to this one:
```
Error: Command Exception: Couldn't load function data for function Closure: (IChatContext) => Null
Stack trace:
#0 loadFunctionData (package:nyxx_commands/src/mirror_utils/compiled.dart:18)
#1 ChatCommand._loadArguments (package:nyxx_commands/src/commands/chat_command.dart:354)
...
```

Once you've added `id` to all your commands, use the `nyxx_commands:compile` script to compile your program. See `dart run nyxx_commands:compile --help` for a list of options.

If you use the `--no-compile` flag, make sure that you run/compile the generated file and not your own main file.
3 changes: 1 addition & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ analyzer:
exclude: [build/**, "*.g.dart"]
language:
strict-raw-types: true
strong-mode:
implicit-casts: false
strict-casts: false
7 changes: 6 additions & 1 deletion bin/compile/element_tree_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,12 @@ class EntireAstVisitor extends RecursiveAstVisitor<void> {
void visitPartDirective(PartDirective directive) {
super.visitPartDirective(directive);

DirectiveUri uri = directive.element!.uri;
if (uri is! DirectiveUriWithSource) {
throw CommandsError('Unknown part target $directive');
}

// Visit "part-ed" files of interesting sources
_interestingSources.add((directive.element!.uri as DirectiveUriWithSource).source.fullName);
_interestingSources.add(uri.source.fullName);
}
}
62 changes: 31 additions & 31 deletions bin/compile/function_metadata/metadata_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:nyxx_commands/nyxx_commands.dart';
import 'package:nyxx_commands/nyxx_commands.dart' show CommandsError;

import '../generator.dart';
import '../type_tree/tree_builder.dart';
import 'compile_time_function_data.dart';

/// Convert [idCreations] into function metadata.
Expand Down Expand Up @@ -66,36 +65,37 @@ Iterable<CompileTimeFunctionData> getFunctionData(
}

/// Extracts all the annotations on a parameter that have a type with the type id [type].
Iterable<Annotation> annotationsWithType(int type) {
Iterable<Annotation> constructorAnnotations = parameter.metadata
.where((node) => node.elementAnnotation?.element is ConstructorElement)
.where((node) =>
getId((node.elementAnnotation!.element as ConstructorElement)
.enclosingElement
.thisType) ==
type);

Iterable<Annotation> constVariableAnnotations = parameter.metadata
.where((node) => (node.elementAnnotation?.element is ConstVariableElement))
.where((node) =>
getId((node.elementAnnotation!.element as ConstVariableElement)
.evaluationResult!
.value!
.type) ==
type);

return constructorAnnotations.followedBy(constVariableAnnotations);
Iterable<Annotation> annotationsWithType(String source) {
return parameter.metadata.where((element) {
ElementAnnotation? annotation = element.elementAnnotation;
if (annotation == null) {
return false;
}

DartObject? result = annotation.computeConstantValue();
if (annotation.constantEvaluationErrors?.isEmpty != true || result == null) {
return false;
}

return result.type?.element?.location?.encoding == source;
});
}

Iterable<Annotation> nameAnnotations = annotationsWithType(nameId);

Iterable<Annotation> descriptionAnnotations = annotationsWithType(descriptionId);

Iterable<Annotation> choicesAnnotations = annotationsWithType(choicesId);

Iterable<Annotation> useConverterAnnotations = annotationsWithType(useConverterId);

Iterable<Annotation> autocompleteAnnotations = annotationsWithType(autocompleteId);
Iterable<Annotation> nameAnnotations = annotationsWithType(
'package:nyxx_commands/src/util/util.dart;package:nyxx_commands/src/util/util.dart;Name',
);
Iterable<Annotation> descriptionAnnotations = annotationsWithType(
'package:nyxx_commands/src/util/util.dart;package:nyxx_commands/src/util/util.dart;Description',
);
Iterable<Annotation> choicesAnnotations = annotationsWithType(
'package:nyxx_commands/src/util/util.dart;package:nyxx_commands/src/util/util.dart;Choices',
);
Iterable<Annotation> useConverterAnnotations = annotationsWithType(
'package:nyxx_commands/src/util/util.dart;package:nyxx_commands/src/util/util.dart;UseConverter',
);
Iterable<Annotation> autocompleteAnnotations = annotationsWithType(
'package:nyxx_commands/src/util/util.dart;package:nyxx_commands/src/util/util.dart;Autocomplete',
);

if ([
nameAnnotations,
Expand Down Expand Up @@ -179,7 +179,7 @@ Iterable<CompileTimeFunctionData> getFunctionData(
return result;
}

/// Extract the object referenced or creatted by an annotation.
/// Extract the object referenced or created by an annotation.
DartObject getAnnotationData(ElementAnnotation annotation) {
DartObject? result;
if (annotation.element is ConstructorElement) {
Expand Down
3 changes: 1 addition & 2 deletions bin/compile/function_metadata/metadata_builder_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/ast/ast.dart';

import '../element_tree_visitor.dart';
Expand All @@ -21,7 +20,7 @@ import '../element_tree_visitor.dart';
class FunctionBuilderVisitor extends EntireAstVisitor {
final List<InvocationExpression> ids = [];

FunctionBuilderVisitor(AnalysisContext context, bool slow) : super(context, slow);
FunctionBuilderVisitor(super.context, super.slow);

@override
void visitMethodInvocation(MethodInvocation node) {
Expand Down
Loading

0 comments on commit 7520bd3

Please sign in to comment.