Docs fixes and additions. Code structure fixes, optimizations

[ci skip]
This commit is contained in:
Szymon Uglis 2020-05-28 22:08:05 +02:00
parent b7b8f6167e
commit c60864fab4
41 changed files with 469 additions and 323 deletions

View file

@ -15,7 +15,7 @@ class CommandContext {
Guild? guild;
/// Returns author as guild member
Member? get member => guild?.members[author!.id];
IMember? get member => guild?.members[author!.id];
CommandContext._new(this.channel, this.author, this.guild, this.message);

View file

@ -65,7 +65,7 @@ class Commander {
Future<void> _handleMessage(MessageReceivedEvent event) async {
/// TODO: Cache
final context = CommandContext._new(event.message.channel!, event.message.author,
final context = CommandContext._new(event.message.channel, event.message.author,
event.message is GuildMessage ? (event.message as GuildMessage).guild : null, event.message);
final prefix = await _prefixHandler(context, event.message.content);

View file

@ -10,7 +10,7 @@ documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
environment:
sdk: '>=2.9.0-2.0.dev <3.0.0'
sdk: '>=2.9.0-10.0.dev <3.0.0'
dependencies:
nyxx: "^1.0.0"

View file

@ -11,17 +11,17 @@ void main() {
bot.onMessageReceived.listen((event) async {
if (event.message.content == "Test 1") {
event.message.delete();
event.message.delete(); // ignore: unawaited_futures
}
if (event.message.content == "Test 2") {
event.message.delete();
event.message.delete(); // ignore: unawaited_futures
}
if (event.message.content == "Test 10") {
await event.message.delete();
await event.message.channel?.send(content: "Commander tests completed sucessfuly!");
await event.message.channel.send(content: "Commander tests completed sucessfuly!");
exit(0);
}
});
@ -32,13 +32,13 @@ void main() {
await channel.send(content: "Testing Commander");
final msg1 = await channel.send(content: "test>test1");
msg1.delete();
msg1.delete(); // ignore: unawaited_futures
final msg2 = await channel.send(content: "test>test2 arg1");
msg2.delete();
msg2.delete(); // ignore: unawaited_futures
final msg3 = await channel.send(content: "test>test3");
msg3.delete();
msg3.delete(); // ignore: unawaited_futures
});
Commander(bot, prefix: "test>", beforeCommandHandler: (context, message) async {

View file

@ -12,7 +12,7 @@ Future<Map<Emoji, int>> createPoll(CachelessTextChannel channel, String title, M
String? message,
bool delete = false,
dynamic Function(Map<Emoji, String> options, String message)? messageFactory}) async {
var toSend;
dynamic toSend;
if (messageFactory == null) {
final buffer = StringBuffer();

View file

@ -1,17 +1,18 @@
part of nyxx.interactivity;
/// Utils for getting and manipulating emojis
class EmojiUtils {
/// Returns [UnicodeEmoji] instance of given emoji [name].
static UnicodeEmoji? getEmoji(String name) {
if (emojisUnicode.containsKey(name)) {
return UnicodeEmoji(emojisUnicode[name]!);
if (emojis.containsKey(name)) {
return UnicodeEmoji(emojis[name]!);
}
return null;
}
/// Map of all emojis which discord uses. Key is unicode name of emoji and value actual emoji.
static Map<String, String> emojisUnicode = {
static Map<String, String> emojis = {
"grinning": "😀",
"grimacing": "😬",
"grin": "😁",

View file

@ -33,20 +33,4 @@ class _Utils {
return split(str, len);
}
/// Partition list into chunks
static Iterable<List<T>> partition<T>(List<T> lst, int len) sync* {
for (var i = 0; i < lst.length; i += len) {
yield lst.sublist(i, i + len);
}
}
/// Divides list into equal pieces
static Stream<List<T>> chunk<T>(List<T> list, int chunkSize) async* {
final len = list.length;
for (var i = 0; i < len; i += chunkSize) {
final size = i + chunkSize;
yield list.sublist(i, size > len ? len : size);
}
}
}

View file

@ -10,7 +10,7 @@ documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
environment:
sdk: '>=2.9.0-2.0.dev <3.0.0'
sdk: '>=2.9.0-10.0.dev <3.0.0'
dependencies:
nyxx: "^1.0.0"

View file

@ -5,7 +5,6 @@ library nyxx;
import "dart:async";
import "dart:collection";
import "dart:convert";
import "dart:io";
@ -18,6 +17,8 @@ import "package:w_transport/vm.dart" as transport_vm show configureWTransportFor
import "package:path/path.dart" as path_utils;
import "package:http/http.dart" as http_client;
// BASE
part "src/Nyxx.dart";
@ -111,6 +112,8 @@ part "src/core/channel/dm/GroupDMChannel.dart";
part "src/core/channel/dm/DMChannel.dart";
part "src/core/channel/Channel.dart";
part "src/core/channel/MessageChannel.dart";
part "src/core/channel/ITextChannel.dart";
part "src/core/channel/DummyTextChannel.dart";
part "src/core/embed/EmbedField.dart";
part "src/core/embed/EmbedAuthor.dart";

View file

@ -54,24 +54,57 @@ class ClientOptions {
/// If you do not specify a certain intent, you will not receive any of the gateway events that are batched into that group.
/// [Reference](https://discordapp.com/developers/docs/topics/gateway#gateway-intents)
class GatewayIntents {
/// Includes events: `GUILD_CREATE, GUILD_UPDATE, GUILD_DELETE, GUILD_ROLE_CREATE, GUILD_ROLE_UPDATE, GUILD_ROLE_DELETE, CHANNEL_DELETE, CHANNEL_CREATE, CHANNEL_UPDATE, CHANNEL_PINS_UPDATE`
bool guilds = false;
/// Includes events: `GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE`
bool guildMembers = false;
/// Includes events: `GUILD_BAN_ADD, GUILD_BAN_REMOVE`
bool guildBans = false;
/// Includes event: `GUILD_EMOJIS_UPDATE`
bool guildEmojis = false;
/// Includes events: `GUILD_INTEGRATIONS_UPDATE`
bool guildIntegrations = false;
/// Includes events: `WEBHOOKS_UPDATE`
bool guildWebhooks = false;
/// Includes events: `INVITE_CREATE, INVITE_DELETE`
bool guildInvites = false;
/// Includes events: `VOICE_STATE_UPDATE`
bool guildVoiceState = false;
/// Includes events: `PRESENCE_UPDATE`
bool guildPresences = false;
/// Include events: `MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_DELETE_BULK`
bool guildMessages = false;
/// Includes events: `MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI`
bool guildMessageReactions = false;
/// Includes events: `TYPING_START`
bool guildMessageTyping = false;
/// Includes events: `CHANNEL_CREATE, MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, CHANNEL_PINS_UPDATE`
bool directMessages = false;
/// Includes events: `MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI`
bool directMessageReactions = false;
/// Includes events: `TYPING_START`
bool directMessageTyping = false;
bool _all = false;
/// Constructs intens config object
GatewayIntents();
/// Return config with turned on all intents
GatewayIntents.all() : _all = true;
int _calculate() {
@ -88,8 +121,9 @@ class GatewayIntents {
if (guildIntegrations) value += 1 << 4;
if (guildWebhooks) value += 1 << 5;
if (guildInvites) value += 1 << 6;
if (guildVoiceState) value += 1 << 8;
if (guildPresences) value += 1 << 9;
if (guildVoiceState) value += 1 << 7;
if (guildPresences) value += 1 << 8;
if (guildMessages) value += 1 << 9;
if (guildMessageReactions) value += 1 << 10;
if (guildMessageTyping) value += 1 << 11;
if (directMessages) value += 1 << 12;

View file

@ -12,13 +12,13 @@ class Shard implements Disposable {
bool connected = false;
/// Emitted when the shard is ready.
late Stream<Shard> onConnected;
late Stream<Shard> onConnected = this._onConnect.stream;
/// Emitted when the shard encounters an error.
late Stream<Shard> onDisconnect;
late Stream<Shard> onDisconnect = this._onDisconnect.stream;
/// Emitted when shard receives member chunk.
late Stream<MemberChunkEvent> onMemberChunk;
late Stream<MemberChunkEvent> onMemberChunk = this._onMemberChunk.stream;
/// Number of events seen by shard
int get eventsSeen => _sequence;
@ -34,20 +34,11 @@ class Shard implements Disposable {
late int _sequence;
String? _sessionId;
late final StreamController<Shard> _onConnect;
late final StreamController<Shard> _onDisconnect;
late final StreamController<MemberChunkEvent> _onMemberChunk;
final StreamController<Shard> _onConnect = StreamController<Shard>.broadcast();
late final StreamController<Shard> _onDisconnect = StreamController<Shard>.broadcast();
late final StreamController<MemberChunkEvent> _onMemberChunk = StreamController.broadcast();
Shard._new(this._ws, this.id) {
this._onConnect = StreamController<Shard>.broadcast();
this.onConnected = this._onConnect.stream;
this._onDisconnect = StreamController<Shard>.broadcast();
this.onDisconnect = this._onDisconnect.stream;
this._onMemberChunk = StreamController.broadcast();
this.onMemberChunk = this._onMemberChunk.stream;
}
Shard._new(this._ws, this.id);
/// Allows to set presence for current shard.
void setPresence({UserStatus? status, bool? afk, Activity? game, DateTime? since}) {
@ -157,7 +148,7 @@ class Shard implements Disposable {
"\$device": "nyxx",
},
"large_threshold": this._ws._client._options.largeThreshold,
"compress": true
"compress": "zlib-stream"
};
if (_ws._client._options.gatewayIntents != null) {
@ -382,6 +373,7 @@ class Shard implements Disposable {
@override
Future<void> dispose() async {
this._heartbeatTimer.cancel();
await this._socketSubscription?.cancel();
await this._socket?.close(1000);
this._socket = null;

View file

@ -30,6 +30,7 @@ class Invite {
this.guild = client.guilds[Snowflake(raw["guild"]["id"])];
}
// TODO: NNBD
if (raw["channel"] != null) {
this.channel = client.channels[Snowflake(raw["channel"]["id"])];
}
@ -41,7 +42,7 @@ class Invite {
if (raw["inviter"] != null) {
this.inviter = client.users[Snowflake(raw["inviter"]["id"])];
}
if (raw["target_user"] != null) {
this.targetUser = client.users[Snowflake(raw["target_user"]["id"])];
}

View file

@ -1,19 +1,26 @@
part of nyxx;
class AppTeam extends SnowflakeEntity {
/// Hash of team icon
late final String? iconHash;
/// Id of Team owner
late final Snowflake ownerId;
/// List of members of team
late final List<AppTeamMember> members;
/// Returns instance of [AppTeamMember] of team owner
AppTeamMember get ownerMember => this.members.firstWhere((element) => element.user.id == this.ownerId);
AppTeam._new(Map<String, dynamic> raw) : super(Snowflake(raw["id"])) {
this.iconHash = raw["icon"] as String?;
this.ownerId = Snowflake(raw["owner_user_id"]);
this.members = [];
for (Map<String, dynamic> obj in raw["members"]) {
this.members.add(AppTeamMember._new(obj));
}
this.members = [
for (final rawMember in raw["members"])
AppTeamMember._new(rawMember as Map<String, dynamic>)
];
}
/// Returns url to team icon
@ -26,9 +33,8 @@ class AppTeam extends SnowflakeEntity {
}
}
/// Represent membership of user in [Team]
/// Represent membership of user in [AppTeam]
class AppTeamMember {
/// Basic information of user
late final AppTeamUser user;

View file

@ -23,54 +23,53 @@ class AuditLogEntry extends SnowflakeEntity {
String? reason;
AuditLogEntry._new(Map<String, dynamic> raw, Nyxx client) : super(Snowflake(raw["id"] as String)) {
targetId = raw["targetId"] as String;
this.targetId = raw["targetId"] as String;
changes = [
this.changes = [
if (raw["changes"] != null)
for (var o in raw["changes"]) AuditLogChange._new(o as Map<String, dynamic>)
];
user = client.users[Snowflake(raw["user_id"])];
type = AuditLogEntryType(raw["action_type"] as int);
this.user = client.users[Snowflake(raw["user_id"])];
this.type = AuditLogEntryType._create(raw["action_type"] as int);
if (raw["options"] != null) {
options = raw["options"] as String;
this.options = raw["options"] as String;
}
reason = raw["reason"] as String;
this.reason = raw["reason"] as String;
}
}
class AuditLogEntryType extends IEnum<int> {
static const AuditLogEntryType guildUpdate = AuditLogEntryType._of(1);
static const AuditLogEntryType channelCreate = AuditLogEntryType._of(10);
static const AuditLogEntryType channelUpdate = AuditLogEntryType._of(11);
static const AuditLogEntryType channelDelete = AuditLogEntryType._of(12);
static const AuditLogEntryType channelOverwriteCreate = AuditLogEntryType._of(13);
static const AuditLogEntryType channelOverwriteUpdate = AuditLogEntryType._of(14);
static const AuditLogEntryType channelOverwriteDelete = AuditLogEntryType._of(15);
static const AuditLogEntryType memberKick = AuditLogEntryType._of(20);
static const AuditLogEntryType memberPrune = AuditLogEntryType._of(21);
static const AuditLogEntryType memberBanAdd = AuditLogEntryType._of(22);
static const AuditLogEntryType memberBanRemove = AuditLogEntryType._of(23);
static const AuditLogEntryType memberUpdate = AuditLogEntryType._of(24);
static const AuditLogEntryType memberRoleUpdate = AuditLogEntryType._of(25);
static const AuditLogEntryType roleCreate = AuditLogEntryType._of(30);
static const AuditLogEntryType roleUpdate = AuditLogEntryType._of(31);
static const AuditLogEntryType roleDelete = AuditLogEntryType._of(32);
static const AuditLogEntryType inviteCreate = AuditLogEntryType._of(40);
static const AuditLogEntryType inviteUpdate = AuditLogEntryType._of(41);
static const AuditLogEntryType inviteDelete = AuditLogEntryType._of(42);
static const AuditLogEntryType webhookCreate = AuditLogEntryType._of(50);
static const AuditLogEntryType webhookUpdate = AuditLogEntryType._of(51);
static const AuditLogEntryType webhookDelete = AuditLogEntryType._of(52);
static const AuditLogEntryType emojiCreate = AuditLogEntryType._of(60);
static const AuditLogEntryType emojiUpdate = AuditLogEntryType._of(61);
static const AuditLogEntryType emojiDelete = AuditLogEntryType._of(62);
static const AuditLogEntryType messageDelete = AuditLogEntryType._of(72);
static const AuditLogEntryType guildUpdate = AuditLogEntryType._create(1);
static const AuditLogEntryType channelCreate = AuditLogEntryType._create(10);
static const AuditLogEntryType channelUpdate = AuditLogEntryType._create(11);
static const AuditLogEntryType channelDelete = AuditLogEntryType._create(12);
static const AuditLogEntryType channelOverwriteCreate = AuditLogEntryType._create(13);
static const AuditLogEntryType channelOverwriteUpdate = AuditLogEntryType._create(14);
static const AuditLogEntryType channelOverwriteDelete = AuditLogEntryType._create(15);
static const AuditLogEntryType memberKick = AuditLogEntryType._create(20);
static const AuditLogEntryType memberPrune = AuditLogEntryType._create(21);
static const AuditLogEntryType memberBanAdd = AuditLogEntryType._create(22);
static const AuditLogEntryType memberBanRemove = AuditLogEntryType._create(23);
static const AuditLogEntryType memberUpdate = AuditLogEntryType._create(24);
static const AuditLogEntryType memberRoleUpdate = AuditLogEntryType._create(25);
static const AuditLogEntryType roleCreate = AuditLogEntryType._create(30);
static const AuditLogEntryType roleUpdate = AuditLogEntryType._create(31);
static const AuditLogEntryType roleDelete = AuditLogEntryType._create(32);
static const AuditLogEntryType inviteCreate = AuditLogEntryType._create(40);
static const AuditLogEntryType inviteUpdate = AuditLogEntryType._create(41);
static const AuditLogEntryType inviteDelete = AuditLogEntryType._create(42);
static const AuditLogEntryType webhookCreate = AuditLogEntryType._create(50);
static const AuditLogEntryType webhookUpdate = AuditLogEntryType._create(51);
static const AuditLogEntryType webhookDelete = AuditLogEntryType._create(52);
static const AuditLogEntryType emojiCreate = AuditLogEntryType._create(60);
static const AuditLogEntryType emojiUpdate = AuditLogEntryType._create(61);
static const AuditLogEntryType emojiDelete = AuditLogEntryType._create(62);
static const AuditLogEntryType messageDelete = AuditLogEntryType._create(72);
const AuditLogEntryType._of(int value) : super(value);
AuditLogEntryType(int value) : super(value);
const AuditLogEntryType._create(int value) : super(value);
@override
bool operator ==(other) {

View file

@ -2,7 +2,7 @@ part of nyxx;
/// A channel.
/// Abstract base class that defines the base methods and/or properties for all Discord channel types.
abstract class Channel extends SnowflakeEntity {
class Channel extends SnowflakeEntity {
/// The channel's type.
/// https://discordapp.com/developers/docs/resources/channel#channel-object-channel-types
final ChannelType type;

View file

@ -0,0 +1,5 @@
part of nyxx;
class DummyTextChannel extends Channel with MessageChannel, ISend implements ITextChannel {
DummyTextChannel._new(Map<String, dynamic> raw, int type, Nyxx client) : super._new(raw, type, client);
}

View file

@ -0,0 +1,77 @@
part of nyxx;
/// Represents text channel. Can be either [CachelessTextChannel] or [DMChannel] or [GroupDMChannel]
abstract class ITextChannel implements Channel, MessageChannel {
/// Returns message with given [id]. Allows to force fetch message from api
/// with [ignoreCache] property. By default it checks if message is in cache and fetches from api if not.
Future<Message?> getMessage(Snowflake id, {bool ignoreCache = false});
/// Sends message to channel. Performs `toString()` on thing passed to [content]. Allows to send embeds with [embed] field.
///
/// ```
/// await chan.send(content: "Very nice message!");
/// ```
///
/// Can be used in combination with [Emoji]. Just run `toString()` on [Emoji] instance:
/// ```
/// var emoji = guild.emojis.values.firstWhere((e) => e.name.startsWith("dart"));
/// await chan.send(content: "Dart is superb! ${emoji.toString()}");
/// ```
/// Embeds can be sent very easily:
/// ```
/// var embed = new EmbedBuilder()
/// ..title = "Example Title"
/// ..addField(name: "Memory usage", value: "${ProcessInfo.currentRss / 1024 / 1024}MB");
///
/// await chan.send(embed: embed);
/// ```
///
///
/// Method also allows to send file and optional [content] with [embed].
/// Use `expandAttachment(String file)` method to expand file names in embed
///
/// ```
/// await chan.send(files: [new File("kitten.png"), new File("kitten.jpg")], content: "Kittens ^-^"]);
/// ```
/// ```
/// var embed = new nyxx.EmbedBuilder()
/// ..title = "Example Title"
/// ..thumbnailUrl = "${attach("kitten.jpg")}";
///
/// await e.message.channel
/// .send(files: [new File("kitten.jpg")], embed: embed, content: "HEJKA!");
/// ```
@override
Future<Message> send(
{dynamic content,
List<AttachmentBuilder>? files,
EmbedBuilder? embed,
bool? tts,
AllowedMentions? allowedMentions,
MessageBuilder? builder});
/// Starts typing.
Future<void> startTyping();
/// Loops `startTyping` until `stopTypingLoop` is called.
void startTypingLoop();
/// Stops a typing loop if one is running.
void stopTypingLoop();
/// Bulk removes many messages by its ids. [messagesIds] is list of messages ids to delete.
///
/// ```
/// var toDelete = chan.messages.keys.take(5);
/// await chan.bulkRemoveMessages(toDelete);
/// ```
Future<void> bulkRemoveMessages(Iterable<Message> messagesIds);
/// Gets several [Message] objects from API. Only one of [after], [before], [around] can be specified,
/// otherwise, it will throw.
///
/// ```
/// var messages = await chan.getMessages(limit: 100, after: Snowflake("222078108977594368"));
/// ```
Stream<Message> getMessages({int limit = 50, Snowflake? after, Snowflake? before, Snowflake? around});
}

View file

@ -11,17 +11,21 @@ part of nyxx;
/// print(message.author.id);
/// }
/// ```
abstract class MessageChannel implements Channel, ISend {
abstract class MessageChannel implements Channel, ISend, Disposable {
Timer? _typing;
/// Sent when a new message is received.
late final Stream<MessageReceivedEvent> onMessage;
late final Stream<MessageReceivedEvent> onMessage = client.onMessageReceived.where((event) => event.message.channel == this);
/// Emitted when user starts typing.
late final Stream<TypingEvent> onTyping;
late final Stream<TypingEvent> onTyping = client.onTyping.where((event) => event.channel == this);
/// Emitted when channel pins are updated.
late final Stream<ChannelPinsUpdateEvent> pinsUpdated = client.onChannelPinsUpdate.where((event) => event.channel == this);
/// A collection of messages sent to this channel.
late final MessageCache messages;
late final MessageCache messages = MessageCache._new(client._options.messageCacheSize);
/// File upload limit for channel
int get fileUploadLimit {
@ -32,13 +36,6 @@ abstract class MessageChannel implements Channel, ISend {
return 8 * 1024 * 1024;
}
void _initialize(Map<String, dynamic> raw) {
this.messages = MessageCache._new(client._options.messageCacheSize);
this.onTyping = client.onTyping.where((event) => event.channel == this);
this.onMessage = client.onMessageReceived.where((event) => event.message.channel == this);
}
/// Returns message with given [id]. Allows to force fetch message from api
/// with [ignoreCache] property. By default it checks if message is in cache and fetches from api if not.
Future<Message?> getMessage(Snowflake id, {bool ignoreCache = false}) async {
@ -191,7 +188,7 @@ abstract class MessageChannel implements Channel, ISend {
}
}
@override
/// Returns iterator for messages cache
Iterator<Message> get iterator => messages.values.iterator;
@override

View file

@ -1,7 +1,7 @@
part of nyxx;
/// Represents channel with another user.
class DMChannel extends Channel with MessageChannel, ISend {
class DMChannel extends Channel with MessageChannel, ISend implements ITextChannel {
/// The recipient.
late User recipient;

View file

@ -1,7 +1,7 @@
part of nyxx;
/// Represents group DM channel.
class GroupDMChannel extends Channel with MessageChannel, ISend {
class GroupDMChannel extends Channel with MessageChannel, ISend implements ITextChannel {
/// The recipients of channel.
late final List<User> recipients;

View file

@ -1,5 +1,7 @@
part of nyxx;
/// Represents channel which is part of guild.
/// Can be represented by [CachelessGuildChannel] or [CacheGuildChannel]
abstract class IGuildChannel extends Channel {
/// The channel"s name.
String get name;
@ -16,7 +18,7 @@ abstract class IGuildChannel extends Channel {
/// Indicates if channel is nsfw
bool get isNsfw;
/// Returns list of [Member] objects who can see this channel
/// Returns list of [CacheMember] objects who can see this channel
List<PermissionsOverrides> get permissionOverrides;
IGuildChannel._new(Map<String, dynamic> raw, int type, Nyxx client) : super._new(raw, type, client);
@ -44,6 +46,7 @@ abstract class IGuildChannel extends Channel {
Future<Invite> createInvite({int? maxAge, int? maxUses, bool? temporary, bool? unique, String? auditReason});
}
/// Guild channel which does not have access to cache.
abstract class CachelessGuildChannel extends IGuildChannel {
/// The channel"s name.
@override
@ -65,7 +68,7 @@ abstract class CachelessGuildChannel extends IGuildChannel {
@override
late final bool isNsfw;
/// Returns list of [Member] objects who can see this channel
/// Returns list of [CacheMember] objects who can see this channel
@override
late final List<PermissionsOverrides> permissionOverrides;
@ -152,7 +155,7 @@ abstract class CachelessGuildChannel extends IGuildChannel {
}
}
/// Represents channel which is part of guild.
/// Guild channel which does have access to cache.
abstract class CacheGuildChannel extends CachelessGuildChannel {
/// The guild that the channel is in.
late Guild guild;
@ -160,24 +163,17 @@ abstract class CacheGuildChannel extends CachelessGuildChannel {
/// Parent channel id
CategoryChannel? parentChannel;
/// Returns list of [Member] objects who can see this channel
Iterable<Member> get users => this
.guild
.members
.values
.where((member) => this.effectivePermissions(member).hasPermission(PermissionsConstants.viewChannel));
CacheGuildChannel._new(Map<String, dynamic> raw, int type, Guild guild, Nyxx client) : super._new(raw, type, guild.id, client) {
// ignore: prefer_initializing_formals
this.guild = guild;
/// Returns list of [CacheMember] objects who can see this channel
Iterable<IMember> get users => this.guild.members.values.where((member) => member is CacheMember && this.effectivePermissions(member).hasPermission(PermissionsConstants.viewChannel));
CacheGuildChannel._new(Map<String, dynamic> raw, int type, this.guild, Nyxx client) : super._new(raw, type, guild.id, client) {
if(this.parentChannelId != null) {
this.parentChannel = guild.channels[this.parentChannelId!] as CategoryChannel?;
}
}
/// Returns effective permissions for [member] to this channel including channel overrides.
Permissions effectivePermissions(Member member) {
Permissions effectivePermissions(CacheMember member) {
if (member.guild != this.guild) {
return Permissions.empty();
}
@ -202,9 +198,11 @@ abstract class CacheGuildChannel extends CachelessGuildChannel {
/// Returns effective permissions for [role] to this channel including channel overrides.
Permissions effectivePermissionForRole(Role role) {
if (role.guild != this.guild) return Permissions.empty();
if (role.guild != this.guild) {
return Permissions.empty();
}
var permissions = role.permissions.raw | guild.everyoneRole.permissions.raw;
var permissions = role.permissions.raw | (guild.everyoneRole as Role).permissions.raw;
// TODO: NNBD: try-catch in where
try {

View file

@ -2,10 +2,7 @@ part of nyxx;
/// [CachelessTextChannel] represents single text channel on [Guild].
/// Inhertits from [MessageChannel] and mixes [CacheGuildChannel].
class CachelessTextChannel extends CachelessGuildChannel with MessageChannel, ISend implements Mentionable {
/// Emitted when channel pins are updated.
late final Stream<ChannelPinsUpdateEvent> pinsUpdated;
class CachelessTextChannel extends CachelessGuildChannel with MessageChannel, ISend implements Mentionable, ITextChannel {
/// The channel's topic.
late final String? topic;
@ -22,12 +19,8 @@ class CachelessTextChannel extends CachelessGuildChannel with MessageChannel, IS
"/${this.id.toString()}";
CachelessTextChannel._new(Map<String, dynamic> raw, Snowflake guildId, Nyxx client) : super._new(raw, 0, guildId, client) {
_initialize(raw);
this.topic = raw["topic"] as String?;
this.slowModeThreshold = raw["rate_limit_per_user"] as int? ?? 0;
pinsUpdated = client.onChannelPinsUpdate.where((event) => event.channel == this);
}
/// Edits the channel.
@ -65,9 +58,9 @@ class CachelessTextChannel extends CachelessGuildChannel with MessageChannel, IS
/// Valid file types for [avatarFile] are jpeg, gif and png.
///
/// ```
/// var webhook = await chan.createWebhook("!a Send nudes kek6407");
/// var webhook = await channnel.createWebhook("!a Send nudes kek6407");
/// ```
Future<Webhook> createWebhook(String name, {File? avatarFile, String auditReason = ""}) async {
Future<Webhook> createWebhook(String name, {File? avatarFile, String? auditReason}) async {
if (name.isEmpty || name.length > 80) {
return Future.error("Webhook's name cannot be shorter than 1 character and longer than 80 characters");
}

View file

@ -13,9 +13,9 @@ class ClientUser extends User {
this.mfa = data["mfa_enabled"] as bool;
}
/// Allows to get [Member] objects for all guilds for bot user.
Map<Guild, Member> getMembership() {
final membershipCollection = <Guild, Member>{};
/// Allows to get [CacheMember] objects for all guilds for bot user.
Map<Guild, IMember> getMembership() {
final membershipCollection = <Guild, IMember>{};
for (final guild in client.guilds.values) {
final member = guild.members[this.id];

View file

@ -33,8 +33,8 @@ class Guild extends SnowflakeEntity implements Disposable {
late final List<String> features;
/// The guild's afk channel ID, null if not set.
late CacheVoiceChannel? afkChannel;
late VoiceChannel? afkChannel;
/// The guild's voice region.
late String region;
@ -69,16 +69,16 @@ class Guild extends SnowflakeEntity implements Disposable {
late final User? owner;
/// The guild's members.
late final Cache<Snowflake, Member> members;
late final Cache<Snowflake, IMember> members;
/// The guild's channels.
late final ChannelCache channels;
/// The guild's roles.
late final Cache<Snowflake, Role> roles;
late final Cache<Snowflake, IRole> roles;
/// Guild custom emojis
late final Cache<Snowflake, GuildEmoji> emojis;
late final Cache<Snowflake, IGuildEmoji> emojis;
/// Boost level of guild
late final PremiumTier premiumTier;
@ -104,10 +104,10 @@ class Guild extends SnowflakeEntity implements Disposable {
String get url => "https://discordapp.com/channels/${this.id.toString()}";
/// Getter for @everyone role
Role get everyoneRole => roles.values.firstWhere((r) => r.name == "@everyone");
IRole get everyoneRole => roles.values.firstWhere((r) => (r as Role).name == "@everyone");
/// Returns member object for bot user
Member? get selfMember => members[client.self.id];
IMember? get selfMember => members[client.self.id];
/// Upload limit for this guild in bytes
int get fileUploadLimit {
@ -200,7 +200,7 @@ class Guild extends SnowflakeEntity implements Disposable {
if (client._options.cacheMembers) {
raw["members"].forEach((o) {
final member = Member._standard(o as Map<String, dynamic>, this, client);
final member = CacheMember._standard(o as Map<String, dynamic>, this, client);
this.members[member.id] = member;
client.users[member.id] = member;
});
@ -385,7 +385,7 @@ class Guild extends SnowflakeEntity implements Disposable {
}
/// Change guild owner.
Future<Guild> changeOwner(Member member, {String? auditReason}) async {
Future<Guild> changeOwner(CacheMember member, {String? auditReason}) async {
final response = await client._http._execute(
BasicRequest._new("/guilds/$id", method: "PATCH", auditLog: auditReason, body: {"owner_id": member.id}));
@ -492,7 +492,7 @@ class Guild extends SnowflakeEntity implements Disposable {
return Future.error(response);
}
/// Adds [Role] to [Member]
/// Adds [Role] to [CacheMember]
///
/// ```
/// var role = guild.roles.values.first;
@ -500,7 +500,7 @@ class Guild extends SnowflakeEntity implements Disposable {
///
/// await guild.addRoleToMember(member, role);
/// ```
Future<void> addRoleToMember(Member user, Role role) async =>
Future<void> addRoleToMember(CacheMember user, Role role) async =>
client._http._execute(BasicRequest._new("/guilds/$id/members/${user.id}/roles/${role.id}", method: "PUT"));
/// Returns list of available [CacheVoiceChannel]s
@ -589,7 +589,7 @@ class Guild extends SnowflakeEntity implements Disposable {
///
/// await guild.ban(member);
/// ```
Future<void> ban(Member member, {int deleteMessageDays = 0, String? auditReason}) async =>
Future<void> ban(CacheMember member, {int deleteMessageDays = 0, String? auditReason}) async =>
client._http._execute(BasicRequest._new("/guilds/${this.id}/bans/${member.id.toString()}",
method: "PUT", auditLog: auditReason, body: {"delete-message-days": deleteMessageDays}));
@ -598,7 +598,7 @@ class Guild extends SnowflakeEntity implements Disposable {
/// ```
/// await guild.kick(member);
/// ```
Future<void> kick(Member member, {String? auditReason}) async =>
Future<void> kick(CacheMember member, {String? auditReason}) async =>
client._http._execute(BasicRequest._new("/guilds/${this.id.toString()}/members/${member.id.toString()}",
method: "DELTE", auditLog: auditReason));
@ -634,27 +634,27 @@ class Guild extends SnowflakeEntity implements Disposable {
return Future.error(response);
}
/// Gets a [Member] object. Caches fetched member if not cached.
/// Gets a [CacheMember] object. Caches fetched member if not cached.
///
/// ```
/// var member = guild.getMember(user);
/// ```
Future<Member> getMember(User user) async => getMemberById(user.id);
Future<CacheMember> getMember(User user) async => getMemberById(user.id);
/// Gets a [Member] object by id. Caches fetched member if not cached.
/// Gets a [CacheMember] object by id. Caches fetched member if not cached.
///
/// ```
/// var member = guild.getMember(Snowflake("302359795648954380"));
/// ```
Future<Member> getMemberById(Snowflake id) async {
Future<CacheMember> getMemberById(Snowflake id) async {
if (this.members.hasKey(id)) {
return this.members[id] as Member;
return this.members[id] as CacheMember;
}
final response = await client._http._execute(BasicRequest._new("/guilds/${this.id}/members/${id.toString()}"));
if (response is HttpResponseSuccess) {
return Member._standard(response.jsonBody as Map<String, dynamic>, this, client);
return CacheMember._standard(response.jsonBody as Map<String, dynamic>, this, client);
}
return Future.error(response);
@ -663,7 +663,7 @@ class Guild extends SnowflakeEntity implements Disposable {
/// Allows to fetch guild members. In future will be restricted with `Priviliged Intents`.
/// [after] is used to continue from specified user id.
/// By default limits to one user - use [limit] paramter to change that behavior.
Stream<Member> getMembers({int limit = 1, Snowflake? after}) async* {
Stream<CacheMember> getMembers({int limit = 1, Snowflake? after}) async* {
final request = this.client._http._execute(BasicRequest._new("/guilds/${this.id.toString()}/members",
queryParams: {"limit": limit.toString(), if (after != null) "after": after.toString()}));
@ -672,13 +672,13 @@ class Guild extends SnowflakeEntity implements Disposable {
}
for (final rawMember in (request as HttpResponseSuccess).jsonBody as List<dynamic>) {
yield Member._standard(rawMember as Map<String, dynamic>, this, client);
yield CacheMember._standard(rawMember as Map<String, dynamic>, this, client);
}
}
/// Returns a [Stream] of [Member] objects whose username or nickname starts with a provided string.
/// Returns a [Stream] of [CacheMember] objects whose username or nickname starts with a provided string.
/// By default limits to one entry - can be changed with [limit] parameter.
Stream<Member> searchMembers(String query, {int limit = 1}) async* {
Stream<CacheMember> searchMembers(String query, {int limit = 1}) async* {
final response = await client._http._execute(BasicRequest._new("/guilds/${this.id}/members/search",
queryParams: {"query": query, "limit": limit.toString()}));
@ -687,13 +687,13 @@ class Guild extends SnowflakeEntity implements Disposable {
}
for (final Map<String, dynamic> member in (response as HttpResponseSuccess).jsonBody) {
yield Member._standard(member, this, client);
yield CacheMember._standard(member, this, client);
}
}
/// Returns a [Stream] of [Member] objects whose username or nickname starts with a provided string.
/// Returns a [Stream] of [CacheMember] objects whose username or nickname starts with a provided string.
/// By default limits to one entry - can be changed with [limit] parameter.
Stream<Member> searchMembersGateway(String query, {int limit = 0}) async* {
Stream<IMember> searchMembersGateway(String query, {int limit = 0}) async* {
final nonce = "$query${id.toString()}";
this.client.shard.requestMembers(this.id, query: query, limit: limit, nonce: nonce);

View file

@ -1,7 +1,41 @@
part of nyxx;
/// Represents [Guild] role.
/// Interface allows basic operations on member but does not guarantee data to be valid or available
class IRole extends SnowflakeEntity {
/// Reference to client
final Nyxx client;
/// Id of role's [Guild]
final Snowflake guildId;
IRole._new(Snowflake id, this.guildId, this.client) : super(id);
/// Edits the role.
Future<Role> edit(RoleBuilder role, {String? auditReason}) async {
final response = await client._http._execute(BasicRequest._new("/guilds/${this.guildId}/roles/$id",
method: "PATCH", body: role._build(), auditLog: auditReason));
if (response is HttpResponseSuccess) {
return Role._new(response.jsonBody as Map<String, dynamic>, this.guildId, client);
}
return Future.error(response);
}
/// Deletes the role.
Future<void> delete({String? auditReason}) =>
client._http
._execute(BasicRequest._new("/guilds/${this.guildId}/roles/$id", method: "DELETE", auditLog: auditReason));
/// Adds role to user.
Future<void> addToUser(User user, {String? auditReason}) =>
client._http._execute(
BasicRequest._new("/guilds/${this.guildId}/members/${user.id}/roles/$id", method: "PUT", auditLog: auditReason));
}
/// Represents a Discord guild role, which is used to assign priority, permissions, and a color to guild members
class Role extends SnowflakeEntity implements Mentionable, GuildEntity {
class Role extends IRole implements Mentionable, GuildEntity {
/// The role's name.
late final String name;
@ -24,10 +58,6 @@ class Role extends SnowflakeEntity implements Mentionable, GuildEntity {
@override
late final Guild? guild;
@override
/// The role's guild id.
late final Snowflake guildId;
/// The role's permissions.
late final Permissions permissions;
@ -36,10 +66,7 @@ class Role extends SnowflakeEntity implements Mentionable, GuildEntity {
/// Mention of role. If role cannot be mentioned it returns name of role (@name)
String get mention => mentionable ? "<@&${this.id}>" : "@$name";
/// Reference to [Nyxx] instance
Nyxx client;
Role._new(Map<String, dynamic> raw, this.guildId, this.client) : super(Snowflake(raw["id"] as String)) {
Role._new(Map<String, dynamic> raw, Snowflake guildId, Nyxx client) : super._new(Snowflake(raw["id"]), guildId, client) {
this.name = raw["name"] as String;
this.position = raw["position"] as int;
this.hoist = raw["hoist"] as bool;
@ -51,28 +78,6 @@ class Role extends SnowflakeEntity implements Mentionable, GuildEntity {
this.guild = client.guilds[this.guildId];
}
/// Edits the role.
Future<Role> edit(RoleBuilder role, {String? auditReason}) async {
final response = await client._http._execute(BasicRequest._new("/guilds/${this.guildId}/roles/$id",
method: "PATCH", body: role._build(), auditLog: auditReason));
if (response is HttpResponseSuccess) {
return Role._new(response.jsonBody as Map<String, dynamic>, this.guildId, client);
}
return Future.error(response);
}
/// Deletes the role.
Future<void> delete({String? auditReason}) =>
client._http
._execute(BasicRequest._new("/guilds/${this.guildId}/roles/$id", method: "DELETE", auditLog: auditReason));
/// Adds role to user.
Future<void> addToUser(User user, {String? auditReason}) =>
client._http._execute(
BasicRequest._new("/guilds/${this.guildId}/members/${user.id}/roles/$id", method: "PUT", auditLog: auditReason));
/// Returns a mention of role. If role cannot be mentioned it returns name of role.
@override
String toString() => mention;

View file

@ -3,7 +3,10 @@ part of nyxx;
/// Type of webhook. Either [incoming] if it its normal webhook executable with token,
/// or [channelFollower] if its discord internal webhook
class WebhookType extends IEnum<int> {
/// Incoming Webhooks can post messages to channels with a generated token
static const WebhookType incoming = WebhookType._create(1);
/// Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels
static const WebhookType channelFollower = WebhookType._create(2);
const WebhookType._create(int? value) : super(value ?? 0);
@ -89,7 +92,7 @@ class Webhook extends SnowflakeEntity implements IMessageAuthor {
}
/// Edits the webhook.
Future<Webhook> edit({String? name, CachelessTextChannel? channel, File? avatar, String? encodedAvatar, String? auditReason}) async {
Future<Webhook> edit({String? name, ITextChannel? channel, File? avatar, String? encodedAvatar, String? auditReason}) async {
final body = <String, dynamic>{
if (name != null) "name": name,
if (channel != null) "channel_id": channel.id.toString()

View file

@ -4,8 +4,8 @@ part of nyxx;
abstract class Emoji {
/// Emojis name.
String name;
Emoji(this.name);
Emoji._new(this.name);
// TODO: Emojis stuff
factory Emoji._deserialize(Map<String, dynamic> raw) {

View file

@ -4,7 +4,7 @@ abstract class IGuildEmoji extends Emoji {
/// True if emoji is partial.
final bool partial;
IGuildEmoji._new(String name, this.partial) : super(name);
IGuildEmoji._new(String name, this.partial) : super._new(name);
}
/// Emoji object. Handles Unicode emojis and custom ones.
@ -26,20 +26,8 @@ class GuildEmoji extends IGuildEmoji implements SnowflakeEntity, GuildEntity {
@override
late final Snowflake id;
late final List<Snowflake> _roles;
/// Roles this emoji is whitelisted to
Iterable<Role> get roles sync* {
if(this.guild != null) {
for(final roleId in this._roles) {
final role = this.guild!.roles[roleId];
if(role != null) {
yield role;
}
}
}
}
/// Roles which can use this emote
late final Iterable<IRole> roles;
/// whether this emoji must be wrapped in colons
late final bool requireColons;
@ -50,10 +38,6 @@ class GuildEmoji extends IGuildEmoji implements SnowflakeEntity, GuildEntity {
/// whether this emoji is animated
late final bool animated;
/// True if emoji is partial.
/// Always check before accessing fields or methods, due any of field can be null or empty
late final bool partial;
/// Creates full emoji object
GuildEmoji._new(Map<String, dynamic> raw, this.guildId, this.client) : super._new(raw["name"] as String, false) {
this.id = Snowflake(raw["id"] as String);
@ -63,10 +47,10 @@ class GuildEmoji extends IGuildEmoji implements SnowflakeEntity, GuildEntity {
this.managed = raw["managed"] as bool? ?? false;
this.animated = raw["animated"] as bool? ?? false;
this._roles = [
this.roles = [
if (raw["roles"] != null)
for (final roleId in raw["roles"])
Snowflake(roleId)
IRole._new(Snowflake(roleId), this.guildId, client)
];
}

View file

@ -42,7 +42,7 @@ class GuildMessage extends Message implements GuildEntity {
String get url => "https://discordapp.com/channels/${this.guildId}"
"/${this.channelId}/${this.id}";
/// The message's author. Can be instance of [Member] or [Webhook]
/// The message's author. Can be instance of [CacheMember] or [Webhook]
@override
late final IMessageAuthor author;
@ -50,20 +50,7 @@ class GuildMessage extends Message implements GuildEntity {
/// True if message is sent by a webhook
bool get isByWebhook => author is Webhook;
late List<Snowflake> _roleMentions;
/// A list of IDs for the role mentions in the message.
Iterable<Role> get roleMentions sync* {
if(this.guild != null) {
for (final roleId in _roleMentions) {
final role = this.guild!.roles[roleId];
if(role != null) {
yield role;
}
}
}
}
late final List<IRole> roleMentions;
GuildMessage._new(Map<String, dynamic> raw, Nyxx client) : super._new(raw, client) {
if (raw["message_reference"] != null) {
@ -85,9 +72,8 @@ class GuildMessage extends Message implements GuildEntity {
final authorData = raw["author"] as Map<String, dynamic>;
final memberData = raw["member"] as Map<String, dynamic>;
final author =
Member._fromUser(authorData, memberData, client.guilds[Snowflake(raw["guild_id"])] as Guild, client);
final author = this.guild == null ? CachelessMember._fromUser(authorData, memberData, guildId, client) : CacheMember._fromUser(authorData, memberData, this.guild!, client);
client.users[author.id] = author;
guild?.members[author.id] = author;
this.author = author;
@ -97,9 +83,10 @@ class GuildMessage extends Message implements GuildEntity {
}
}
this._roleMentions = [
this.roleMentions = [
if (raw["mention_roles"] != null)
for (var r in raw["mention_roles"]) Snowflake(r)
for (var roleId in raw["mention_roles"])
this.guild == null ? IRole ._new(Snowflake(roleId), guildId, client) : guild!.roles[Snowflake(roleId)]!
];
}
@ -121,7 +108,7 @@ abstract class Message extends SnowflakeEntity implements Disposable {
late String content;
/// Channel in which message was sent
late final MessageChannel? channel;
late final ITextChannel channel;
/// Id of channel in which message was sent
late final Snowflake channelId;
@ -129,10 +116,10 @@ abstract class Message extends SnowflakeEntity implements Disposable {
/// The timestamp of when the message was last edited, null if not edited.
late final DateTime? editedTimestamp;
/// The message's author. Can be instance of [Member]
/// The message's author. Can be instance of [CacheMember]
IMessageAuthor get author;
/// The mentions in the message. [User] value of this map can be [Member]
/// The mentions in the message. [User] value of this map can be [CacheMember]
late List<User> mentions;
/// A collection of the embeds in the message.
@ -174,7 +161,14 @@ abstract class Message extends SnowflakeEntity implements Disposable {
this.content = raw["content"] as String;
this.channelId = Snowflake(raw["channel_id"]);
this.channel = client.channels[this.channelId] as MessageChannel?;
final channel = client.channels[this.channelId] as ITextChannel?;
if(channel == null) {
// TODO: channel stuff
this.channel = DummyTextChannel._new({"id" : raw["channel_id"]}, -1, client);
} else {
this.channel = channel;
}
this.pinned = raw["pinned"] as bool;
this.tts = raw["tts"] as bool;

View file

@ -2,7 +2,8 @@ part of nyxx;
/// Represents unicode emoji. Contains only emoji code.
class UnicodeEmoji extends Emoji {
UnicodeEmoji(String code) : super(code);
/// Create unicode emoji from given code
UnicodeEmoji(String code) : super._new(code);
/// Returns Emoji
String get code => this.name;

View file

@ -1,7 +1,35 @@
part of nyxx;
/// A user with [Guild] context.
class Member extends User implements GuildEntity {
/// Represents [Guild] member. Can be either [CachelessMember] or [CacheMember] depending on client config and cache state.
/// Interface allows basic operations on member but does not guarantee data to be valid or available
abstract class IMember extends User {
IMember._new(Map<String, dynamic> raw, Nyxx client) : super._new(raw, client);
/// Checks if member has specified role. Returns true if user is assigned to given role.
bool hasRole(bool Function(IRole role) func);
/// Bans the member and optionally deletes [deleteMessageDays] days worth of messages.
Future<void> ban({int? deleteMessageDays, String? reason, String? auditReason});
/// Adds role to user
///
/// ```
/// var r = guild.roles.values.first;
/// await member.addRole(r);
/// ```
Future<void> addRole(IRole role, {String? auditReason});
/// Removes [role] from user.
Future<void> removeRole(IRole role, {String? auditReason});
/// Kicks the member from guild
Future<void> kick({String? auditReason});
/// Edits members. Allows to move user in voice channel, mute or deaf, change nick, roles.
Future<void> edit({String? nick, List<IRole>? roles, bool? mute, bool? deaf, VoiceChannel? channel, String? auditReason});
}
/// Stateless [IMember] instance. Does not have reference to guild.
class CachelessMember extends IMember {
/// The members nickname, null if not set.
String? nickname;
@ -14,61 +42,32 @@ class Member extends User implements GuildEntity {
/// Weather or not the member is muted.
late final bool mute;
/// A list of [Role]s the member has.
late List<Role> roles;
/// Id of guild
final Snowflake guildId;
/// The highest displayed role that a member has, null if they dont have any
Role? hoistedRole;
/// Roles of member. It will contain instance of [IRole] if [CachelessMember] or instance of [Role] if instance of [CacheMember]
late Iterable<IRole> roles;
@override
/// Highest role of member
late IRole? hoistedRole;
/// The guild that the member is a part of.
Guild guild;
factory CachelessMember._standard(Map<String, dynamic> data, Snowflake guildId, Nyxx client) =>
CachelessMember._new(data, data["user"] as Map<String, dynamic>, guildId, client);
/// Returns highest role for member
Role get highestRole => roles.isEmpty ? guild.everyoneRole : roles.reduce((f, s) => f.position > s.position ? f : s);
factory CachelessMember._fromUser(Map<String, dynamic> dataUser, Map<String, dynamic> dataMember, Snowflake guildId, Nyxx client) =>
CachelessMember._new(dataMember, dataUser, guildId, client);
/// Color of highest role of user
DiscordColor get color => highestRole.color;
/// Voice state of member
VoiceState? get voiceState => guild.voiceStates[this.id];
/// Returns total permissions of user.
Permissions get effectivePermissions {
if (this == guild.owner) {
return Permissions.all();
}
var total = guild.everyoneRole.permissions.raw;
for (final role in roles) {
total |= role.permissions.raw;
if (PermissionsUtils.isApplied(total, PermissionsConstants.administrator)) {
return Permissions.fromInt(PermissionsConstants.allPermissions);
}
}
return Permissions.fromInt(total);
}
factory Member._standard(Map<String, dynamic> data, Guild guild, Nyxx client) =>
Member._new(data, data["user"] as Map<String, dynamic>, guild, client);
factory Member._fromUser(Map<String, dynamic> dataUser, Map<String, dynamic> dataMember, Guild guild, Nyxx client) =>
Member._new(dataMember, dataUser, guild, client);
Member._new(Map<String, dynamic> raw, Map<String, dynamic> user, this.guild, Nyxx client) : super._new(user, client) {
CachelessMember._new(Map<String, dynamic> raw, Map<String, dynamic> userRaw, this.guildId, Nyxx client) : super._new(userRaw, client) {
this.nickname = raw["nick"] as String?;
this.deaf = raw["deaf"] as bool;
this.mute = raw["mute"] as bool;
this.roles = [for (var id in raw["roles"]) guild.roles[Snowflake(id)] as Role];
this.roles = [for (var id in raw["roles"]) IRole._new(Snowflake(id), this.guildId, client)];
if (raw["hoisted_role"] != null && roles.isNotEmpty) {
// TODO: NNBD: try-catch in where
try {
this.hoistedRole = this.roles.firstWhere((element) => element.id == Snowflake(raw["hoisted_role"]));
this.hoistedRole = this.roles.firstWhere((element) => element.id == IRole._new(Snowflake(raw["hoisted_role"]), this.guildId, client));
} on Exception {
this.hoistedRole = null;
}
@ -83,7 +82,7 @@ class Member extends User implements GuildEntity {
}
}
bool _updateMember(String? nickname, List<Role> roles) {
bool _updateMember(String? nickname, List<IRole> roles) {
var changed = false;
if (this.nickname != nickname) {
@ -100,16 +99,18 @@ class Member extends User implements GuildEntity {
}
/// Checks if member has specified role. Returns true if user is assigned to given role.
bool hasRole(bool Function(Role role) func) => this.roles.any(func);
@override
bool hasRole(bool Function(IRole role) func) => this.roles.any(func);
/// Bans the member and optionally deletes [deleteMessageDays] days worth of messages.
@override
Future<void> ban({int? deleteMessageDays, String? reason, String? auditReason}) async {
final body = <String, dynamic>{
if (deleteMessageDays != null) "delete-message-days": deleteMessageDays,
if (reason != null) "reason": reason
};
return client._http._execute(BasicRequest._new("/guilds/${this.guild.id}/bans/${this.id}",
return client._http._execute(BasicRequest._new("/guilds/${this.guildId}/bans/${this.id}",
method: "PUT", auditLog: auditReason, body: body));
}
@ -119,25 +120,29 @@ class Member extends User implements GuildEntity {
/// var r = guild.roles.values.first;
/// await member.addRole(r);
/// ```
Future<void> addRole(Role role, {String? auditReason}) =>
client._http._execute(BasicRequest._new("/guilds/${guild.id}/members/${this.id}/roles/${role.id}",
method: "PUT", auditLog: auditReason));
@override
Future<void> addRole(IRole role, {String? auditReason}) =>
client._http._execute(BasicRequest._new("/guilds/$guildId/members/${this.id}/roles/${role.id}",
method: "PUT", auditLog: auditReason));
/// Removes [role] from user.
Future<void> removeRole(Role role, {String? auditReason}) =>
client._http._execute(BasicRequest._new(
"/guilds/${this.guild.id.toString()}/members/${this.id.toString()}/roles/${role.id.toString()}",
method: "DELETE",
auditLog: auditReason));
@override
Future<void> removeRole(IRole role, {String? auditReason}) =>
client._http._execute(BasicRequest._new(
"/guilds/${this.guildId.toString()}/members/${this.id.toString()}/roles/${role.id.toString()}",
method: "DELETE",
auditLog: auditReason));
/// Kicks the member from guild
@override
Future<void> kick({String? auditReason}) =>
client._http._execute(
BasicRequest._new("/guilds/${this.guild.id}/members/${this.id}", method: "DELETE", auditLog: auditReason));
client._http._execute(
BasicRequest._new("/guilds/${this.guildId}/members/${this.id}", method: "DELETE", auditLog: auditReason));
/// Edits members. Allows to move user in voice channel, mute or deaf, change nick, roles.
@override
Future<void> edit(
{String? nick, List<Role>? roles, bool? mute, bool? deaf, CacheVoiceChannel? channel, String? auditReason}) {
{String? nick, List<IRole>? roles, bool? mute, bool? deaf, VoiceChannel? channel, String? auditReason}) {
final body = <String, dynamic>{
if (nick != null) "nick": nick,
if (roles != null) "roles": roles.map((f) => f.id.toString()).toList(),
@ -146,10 +151,66 @@ class Member extends User implements GuildEntity {
if (channel != null) "channel_id": channel.id.toString()
};
return client._http._execute(BasicRequest._new("/guilds/${this.guild.id.toString()}/members/${this.id.toString()}",
return client._http._execute(BasicRequest._new("/guilds/${this.guildId.toString()}/members/${this.id.toString()}",
method: "PATCH", auditLog: auditReason, body: body));
}
@override
String toString() => super.toString();
}
/// A user with [Guild] context.
class CacheMember extends CachelessMember implements GuildEntity {
/// The guild that the member is a part of.
@override
Guild guild;
/// Returns highest role for member
IRole get highestRole => this.roles.isEmpty ? guild.everyoneRole : roles.reduce((f, s) => (f as Role).position > (s as Role).position ? f : s);
/// Color of highest role of user
DiscordColor get color => (highestRole as Role).color;
/// Voice state of member
VoiceState? get voiceState => guild.voiceStates[this.id];
/// Returns total permissions of user.
Permissions get effectivePermissions {
if (this == guild.owner) {
return Permissions.all();
}
var total = (guild.everyoneRole as Role).permissions.raw;
for (final role in roles) {
if(role is! Role) {
continue;
}
total |= role.permissions.raw;
if (PermissionsUtils.isApplied(total, PermissionsConstants.administrator)) {
return Permissions.fromInt(PermissionsConstants.allPermissions);
}
}
return Permissions.fromInt(total);
}
// TODO: Remove duplicate
@override
bool _updateMember(String? nickname, List<IRole> roles) => super._updateMember(nickname, roles);
factory CacheMember._standard(Map<String, dynamic> data, Guild guild, Nyxx client) =>
CacheMember._new(data, data["user"] as Map<String, dynamic>, guild, client);
factory CacheMember._fromUser(Map<String, dynamic> dataUser, Map<String, dynamic> dataMember, Guild guild, Nyxx client) =>
CacheMember._new(dataMember, dataUser, guild, client);
CacheMember._new(Map<String, dynamic> raw, Map<String, dynamic> user, this.guild, Nyxx client) : super._new(raw, user, guild.id, client) {
this.roles = [
for(final role in this.roles)
// TODO: NNBD
this.guild.roles[role.id]!
];
}
}

View file

@ -57,7 +57,7 @@ class ChannelPinsUpdateEvent {
this.lastPingTimestamp = DateTime.parse(raw["d"]["last_pin_timestamp"] as String);
}
this.channel = client.channels[Snowflake(raw["d"]["channel_id"])] as CachelessTextChannel;
this.channel = client.channels[Snowflake(raw["d"]["channel_id"])] as CachelessTextChannel?;
if (raw["d"]["guild_id"] != null) {
this.guildId = Snowflake(raw["d"]["guild_id"]);

View file

@ -89,7 +89,7 @@ class GuildMemberRemoveEvent {
/// Sent when a member is updated.
class GuildMemberUpdateEvent {
/// The member after the update if member is updated.
late final Member? member;
late final CacheMember? member;
/// User if user is updated. Will be null if member is not null.
late final User? user;
@ -110,7 +110,7 @@ class GuildMemberUpdateEvent {
final nickname = raw["d"]["nickname"] as String?;
final roles = (raw["d"]["roles"] as List<dynamic>).map((str) => guild.roles[Snowflake(str)]!).toList();
if (member._updateMember(nickname, roles)) {
if (this.member!._updateMember(nickname, roles)) {
return;
}
@ -122,7 +122,7 @@ class GuildMemberUpdateEvent {
/// Sent when a member joins a guild.
class GuildMemberAddEvent {
/// The member that joined.
late final Member? member;
late final CacheMember? member;
GuildMemberAddEvent._new(Map<String, dynamic> raw, Nyxx client) {
final guild = client.guilds[Snowflake(raw["d"]["guild_id"])];
@ -131,7 +131,7 @@ class GuildMemberAddEvent {
return;
}
this.member = Member._standard(raw["d"] as Map<String, dynamic>, guild, client);
this.member = CacheMember._standard(raw["d"] as Map<String, dynamic>, guild, client);
guild.members[member!.id] = member!;
if (!client.users.hasKey(member!.id)) {
@ -207,8 +207,10 @@ class GuildEmojisUpdateEvent {
/// New list of changes emojis
final Map<Snowflake, GuildEmoji> emojis = {};
/// Id of guild where event happend
late final Snowflake guildId;
/// Instance of guild if available
late final Guild? guild;
GuildEmojisUpdateEvent._new(Map<String, dynamic> json, Nyxx client) {
@ -233,8 +235,10 @@ class RoleCreateEvent {
/// The role that was created.
late final Role role;
/// Id of guild where event happend
late final Snowflake guildId;
/// Instance of [Guild] if available
late final Guild? guild;
RoleCreateEvent._new(Map<String, dynamic> json, Nyxx client) {
@ -243,20 +247,23 @@ class RoleCreateEvent {
this.role = Role._new(json["d"]["role"] as Map<String, dynamic>, guildId, client);
if (guild != null) {
guild!.roles[role!.id] = role!;
guild!.roles[role.id] = role;
}
}
}
/// Sent when a role is deleted.
class RoleDeleteEvent {
/// The role that was deleted.
Role? role;
/// The role that was deleted, if available
IRole? role;
/// Id of tole that was deleted
late final Snowflake roleId;
/// Id of guild where event happend
late final Snowflake guildId;
/// Instance of [Guild] if available
late final Guild? guild;
RoleDeleteEvent._new(Map<String, dynamic> json, Nyxx client) {
@ -275,9 +282,10 @@ class RoleDeleteEvent {
class RoleUpdateEvent {
/// The role after the update.
late final Role role;
/// Id of guild where event happend
late final Snowflake guildId;
/// Instance of [Guild] if available
late final Guild? guild;
RoleUpdateEvent._new(Map<String, dynamic> json, Nyxx client) {

View file

@ -4,7 +4,7 @@ part of nyxx;
/// You can use the `chunk_index` and `chunk_count` to calculate how many chunks are left for your request.
class MemberChunkEvent {
/// Guild members
late final Iterable<Member> members;
late final Iterable<IMember> members;
/// Id of guild
late final Snowflake guildId;
@ -44,7 +44,7 @@ class MemberChunkEvent {
this.members = [
for (var memberRaw in raw["d"]["members"])
Member._standard(memberRaw as Map<String, dynamic>, this.guild!, client)
CacheMember._standard(memberRaw as Map<String, dynamic>, this.guild!, client)
];
// TODO: Thats probably redundant

View file

@ -7,7 +7,7 @@ class MessageReceivedEvent {
MessageReceivedEvent._new(Map<String, dynamic> raw, Nyxx client) {
this.message = Message._deserialize(raw["d"] as Map<String, dynamic>, client);
message.channel?.messages.put(this.message);
message.channel.messages.put(this.message);
}
}
@ -27,7 +27,7 @@ class MessageDeleteEvent {
if (message != null) {
this.message = message;
message.channel?.messages.remove(this.messageId);
message.channel.messages.remove(this.messageId);
}
}
}

View file

@ -3,7 +3,7 @@ part of nyxx;
/// Sent when a user starts typing.
class TypingEvent {
/// The channel that the user is typing in.
MessageChannel? channel;
ITextChannel? channel;
/// Id of the channel that the user is typing in.
late final Snowflake channelId;
@ -19,7 +19,7 @@ class TypingEvent {
TypingEvent._new(Map<String, dynamic> json, Nyxx client) {
this.channelId = Snowflake(json["d"]["channel_id"]);
this.channel = client.channels[channelId] as MessageChannel?;
this.channel = client.channels[channelId] as ITextChannel?;
this.userId = Snowflake(json["d"]["user_id"]);
this.user = client.users[this.userId];

View file

@ -2,7 +2,6 @@ part of nyxx;
/// Emitted when guild's voice server changes
class VoiceServerUpdateEvent {
/// Raw websocket event payload
final Map<String, dynamic> raw;

View file

@ -1,7 +1,7 @@
part of nyxx;
//TODO: Consider what should be here and what should not
/// Could be either [User], [Member] or [Webhook].
/// Could be either [User], [CacheMember] or [Webhook].
/// [Webhook] will have most of field missing.
abstract class IMessageAuthor implements SnowflakeEntity {
/// User name

View file

@ -3,19 +3,19 @@ part of nyxx;
/// Util function for manipulating permissions
class PermissionsUtils {
/// Allows to check if [issueMember] or [issueRole] can interact with [targetMember] or [targetRole].
static bool canInteract({Member? issueMember, Role? issueRole, Member? targetMember, Role? targetRole}) {
static bool canInteract({CacheMember? issueMember, Role? issueRole, CacheMember? targetMember, Role? targetRole}) {
bool canInter(Role role1, Role role2) => role1.position > role2.position;
if (issueMember != null && targetMember != null) {
if (issueMember.guild != targetMember.guild) return false;
return canInter(issueMember.highestRole, targetMember.highestRole);
return canInter(issueMember.highestRole as Role, targetMember.highestRole as Role);
}
if (issueMember != null && targetRole != null) {
if (issueMember.guild != targetRole.guild) return false;
return canInter(issueMember.highestRole, targetRole);
return canInter(issueMember.highestRole as Role, targetRole);
}
if (issueRole != null && targetRole != null) {
@ -28,7 +28,7 @@ class PermissionsUtils {
}
/// Returns List of [channel] permissions overrides for given [member].
static List<int> getOverrides(Member member, CacheGuildChannel channel) {
static List<int> getOverrides(CacheMember member, CacheGuildChannel channel) {
var allowRaw = 0;
var denyRaw = 0;

View file

@ -10,11 +10,12 @@ documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
environment:
sdk: '>=2.9.0-2.0.dev <3.0.0'
sdk: '>=2.9.0-11.0.dev <3.0.0'
dependencies:
logging: "^0.11.4"
w_transport: "^3.2.8"
http: "^0.12.1"
dev_dependencies:
dart_style:

View file

@ -124,7 +124,7 @@ void main() {
final field = embed.fields.first;
if (field.name == "Test field" && field.content == "Test value" && !field.inline!) {
await e.message.channel?.send(content: "Tests completed successfully!");
await e.message.channel.send(content: "Tests completed successfully!");
print("Nyxx tests completed successfully!");
exit(0);
}