├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── ping-pong.dart ├── lib ├── browser.dart ├── discord.dart ├── src │ ├── internals.dart │ └── main │ │ ├── Client.dart │ │ ├── errors │ │ ├── ClientNotReadyError.dart │ │ ├── HttpError.dart │ │ ├── InvalidShardError.dart │ │ ├── InvalidTokenError.dart │ │ └── NotSetupError.dart │ │ ├── events │ │ ├── BeforeHttpRequestSendEvent.dart │ │ ├── ChannelCreateEvent.dart │ │ ├── ChannelDeleteEvent.dart │ │ ├── ChannelUpdateEvent.dart │ │ ├── DisconnectEvent.dart │ │ ├── GuildBanAddEvent.dart │ │ ├── GuildBanRemoveEvent.dart │ │ ├── GuildCreateEvent.dart │ │ ├── GuildDeleteEvent.dart │ │ ├── GuildMemberAddEvent.dart │ │ ├── GuildMemberRemoveEvent.dart │ │ ├── GuildMemberUpdateEvent.dart │ │ ├── GuildUnavailableEvent.dart │ │ ├── GuildUpdateEvent.dart │ │ ├── HttpErrorEvent.dart │ │ ├── HttpResponseEvent.dart │ │ ├── MessageDeleteEvent.dart │ │ ├── MessageEvent.dart │ │ ├── MessageUpdateEvent.dart │ │ ├── PresenceUpdateEvent.dart │ │ ├── RatelimitEvent.dart │ │ ├── RawEvent.dart │ │ ├── ReadyEvent.dart │ │ ├── RoleCreateEvent.dart │ │ ├── RoleDeleteEvent.dart │ │ ├── RoleUpdateEvent.dart │ │ └── TypingEvent.dart │ │ ├── internal │ │ ├── Http.dart │ │ ├── Util.dart │ │ ├── _Constants.dart │ │ ├── _EventController.dart │ │ └── _WS.dart │ │ └── objects │ │ ├── Attachment.dart │ │ ├── Channel.dart │ │ ├── ClientOAuth2Application.dart │ │ ├── ClientOptions.dart │ │ ├── ClientUser.dart │ │ ├── DMChannel.dart │ │ ├── Embed.dart │ │ ├── EmbedProvider.dart │ │ ├── EmbedThumbnail.dart │ │ ├── Game.dart │ │ ├── GroupDMChannel.dart │ │ ├── Guild.dart │ │ ├── GuildChannel.dart │ │ ├── Invite.dart │ │ ├── InviteChannel.dart │ │ ├── InviteGuild.dart │ │ ├── Member.dart │ │ ├── Message.dart │ │ ├── OAuth2Application.dart │ │ ├── OAuth2Guild.dart │ │ ├── OAuth2Info.dart │ │ ├── Permissions.dart │ │ ├── Role.dart │ │ ├── Shard.dart │ │ ├── TextChannel.dart │ │ ├── User.dart │ │ ├── VoiceChannel.dart │ │ └── Webhook.dart └── vm.dart ├── pubspec.yaml └── test ├── discord.dart └── travis.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom files 2 | local/ 3 | .atom/ 4 | .vscode/ 5 | index.html 6 | 7 | # See https://github.com/matanlurey/gitignore 8 | 9 | # Files and directories created by pub 10 | .buildlog 11 | .packages 12 | .project 13 | .pub 14 | **/build 15 | **/packages 16 | 17 | # Files created by dart2js 18 | # (Most Dart developers will use pub build to compile Dart, use/modify these 19 | # rules if you intend to use dart2js directly 20 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 21 | # differentiate from explicit Javascript files) 22 | *.dart.js 23 | *.part.js 24 | *.js.deps 25 | *.js.map 26 | *.info.json 27 | 28 | # Directory created by dartdoc 29 | doc/api/ 30 | 31 | # Don't commit pubspec lock file 32 | # (Library packages only! Remove pattern if developing an application package) 33 | pubspec.lock 34 | 35 | *.iml 36 | .idea 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | script: ./test/travis.sh 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | If you want to contribute, feel free to fork and make a PR. Please lint and run `dartfmt` before opening a PR. 3 | And make sure to always make your PRs to `indev`. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Hackzzila 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nyx [![Build Status](https://travis-ci.org/Hackzzila/nyx.svg?branch=master)](https://travis-ci.org/Hackzzila/nyx) [![Our Discord Server](https://img.shields.io/badge/discord-nyx-7289DA.svg)](https://discord.gg/6JwnkNk) [![Pub](https://img.shields.io/pub/v/discord.svg)](https://pub.dartlang.org/packages/discord) 2 | 3 | ## Why nyx? 4 | 5 | ### Cross Platform 6 | nyx works on the command line, browser, mobile, and can be transpiled to JavaScript. 7 | 8 | ### Fine Control 9 | nyx allows you to control every outgoing HTTP request or websocket messages. 10 | 11 | ### Internal Sharding 12 | nyx automatically spawns shards for your bot, but you can override this and spawn a custom 13 | number of shards. Internal sharding means that all of your bots servers are managed in one script, 14 | no need for communication between shards. 15 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | linter: 4 | rules: 5 | - avoid_empty_else 6 | - comment_references 7 | - control_flow_in_finally 8 | - empty_statements 9 | - hash_and_equals 10 | - iterable_contains_unrelated_type 11 | - list_remove_unrelated_type 12 | - test_types_in_equals 13 | - throw_in_finally 14 | - unrelated_type_equality_checks 15 | - valid_regexps 16 | - always_declare_return_types 17 | - annotate_overrides 18 | - avoid_init_to_null 19 | - avoid_return_types_on_setters 20 | - await_only_futures 21 | - camel_case_types 22 | - constant_identifier_names 23 | - empty_catches 24 | - empty_constructor_bodies 25 | - library_names 26 | - library_prefixes 27 | - non_constant_identifier_names 28 | - only_throw_errors 29 | - overridden_fields 30 | - package_api_docs 31 | - package_prefixed_library_names 32 | - prefer_is_not_empty 33 | - public_member_api_docs 34 | - slash_for_doc_comments 35 | - sort_constructors_first 36 | - sort_unnamed_constructors_first 37 | - type_init_formals 38 | - unnecessary_brace_in_string_interp 39 | - unnecessary_getters_setters 40 | - package_names 41 | -------------------------------------------------------------------------------- /example/ping-pong.dart: -------------------------------------------------------------------------------- 1 | import 'package:discord/discord.dart' as discord; 2 | import 'package:discord/vm.dart' as discord; 3 | 4 | void main() { 5 | discord.configureDiscordForVM(); 6 | discord.Client bot = new discord.Client("your token"); 7 | 8 | bot.onReady.listen((discord.ReadyEvent e) { 9 | print("Ready!"); 10 | }); 11 | 12 | bot.onMessage.listen((discord.MessageEvent e) { 13 | if (e.message.content == "!ping") { 14 | e.message.channel.sendMessage(content: "Pong!"); 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/browser.dart: -------------------------------------------------------------------------------- 1 | library discord.browser; 2 | 3 | import 'dart:html'; 4 | import 'src/internals.dart' as internals; 5 | import 'package:w_transport/w_transport.dart' as w_transport; 6 | import 'package:w_transport/browser.dart' show browserTransportPlatform; 7 | 8 | /// Configures the client to run in a browser. 9 | void configureDiscordForBrowser() { 10 | w_transport.globalTransportPlatform = browserTransportPlatform; 11 | internals.operatingSystem = window.navigator.userAgent; 12 | internals.browser = true; 13 | internals.setup = true; 14 | } 15 | -------------------------------------------------------------------------------- /lib/discord.dart: -------------------------------------------------------------------------------- 1 | library discord; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert'; 5 | import 'dart:collection'; 6 | import 'src/internals.dart' as internals; 7 | import 'package:http_parser/http_parser.dart' as http_parser; 8 | import 'package:w_transport/w_transport.dart' as w_transport; 9 | 10 | part 'src/main/Client.dart'; 11 | 12 | part 'src/main/internal/_Constants.dart'; 13 | part 'src/main/internal/_EventController.dart'; 14 | part 'src/main/internal/_WS.dart'; 15 | part 'src/main/internal/Http.dart'; 16 | part 'src/main/internal/Util.dart'; 17 | 18 | part 'src/main/events/BeforeHttpRequestSendEvent.dart'; 19 | part 'src/main/events/ChannelCreateEvent.dart'; 20 | part 'src/main/events/ChannelDeleteEvent.dart'; 21 | part 'src/main/events/ChannelUpdateEvent.dart'; 22 | part 'src/main/events/DisconnectEvent.dart'; 23 | part 'src/main/events/GuildBanAddEvent.dart'; 24 | part 'src/main/events/GuildBanRemoveEvent.dart'; 25 | part 'src/main/events/GuildCreateEvent.dart'; 26 | part 'src/main/events/GuildDeleteEvent.dart'; 27 | part 'src/main/events/GuildMemberAddEvent.dart'; 28 | part 'src/main/events/GuildMemberRemoveEvent.dart'; 29 | part 'src/main/events/GuildMemberUpdateEvent.dart'; 30 | part 'src/main/events/GuildUnavailableEvent.dart'; 31 | part 'src/main/events/GuildUpdateEvent.dart'; 32 | part 'src/main/events/HttpErrorEvent.dart'; 33 | part 'src/main/events/HttpResponseEvent.dart'; 34 | part 'src/main/events/MessageDeleteEvent.dart'; 35 | part 'src/main/events/MessageEvent.dart'; 36 | part 'src/main/events/MessageUpdateEvent.dart'; 37 | part 'src/main/events/PresenceUpdateEvent.dart'; 38 | part 'src/main/events/RatelimitEvent.dart'; 39 | part 'src/main/events/RawEvent.dart'; 40 | part 'src/main/events/ReadyEvent.dart'; 41 | part 'src/main/events/RoleCreateEvent.dart'; 42 | part 'src/main/events/RoleDeleteEvent.dart'; 43 | part 'src/main/events/RoleUpdateEvent.dart'; 44 | part 'src/main/events/TypingEvent.dart'; 45 | 46 | part 'src/main/objects/Attachment.dart'; 47 | part 'src/main/objects/Channel.dart'; 48 | part 'src/main/objects/ClientOAuth2Application.dart'; 49 | part 'src/main/objects/ClientOptions.dart'; 50 | part 'src/main/objects/ClientUser.dart'; 51 | part 'src/main/objects/DMChannel.dart'; 52 | part 'src/main/objects/Embed.dart'; 53 | part 'src/main/objects/EmbedProvider.dart'; 54 | part 'src/main/objects/EmbedThumbnail.dart'; 55 | part 'src/main/objects/Game.dart'; 56 | part 'src/main/objects/GroupDMChannel.dart'; 57 | part 'src/main/objects/Guild.dart'; 58 | part 'src/main/objects/GuildChannel.dart'; 59 | part 'src/main/objects/Invite.dart'; 60 | part 'src/main/objects/InviteChannel.dart'; 61 | part 'src/main/objects/InviteGuild.dart'; 62 | part 'src/main/objects/Member.dart'; 63 | part 'src/main/objects/Message.dart'; 64 | part 'src/main/objects/OAuth2Application.dart'; 65 | part 'src/main/objects/OAuth2Guild.dart'; 66 | part 'src/main/objects/OAuth2Info.dart'; 67 | part 'src/main/objects/Permissions.dart'; 68 | part 'src/main/objects/Role.dart'; 69 | part 'src/main/objects/Shard.dart'; 70 | part 'src/main/objects/TextChannel.dart'; 71 | part 'src/main/objects/User.dart'; 72 | part 'src/main/objects/VoiceChannel.dart'; 73 | part 'src/main/objects/Webhook.dart'; 74 | 75 | part 'src/main/errors/ClientNotReadyError.dart'; 76 | part 'src/main/errors/HttpError.dart'; 77 | part 'src/main/errors/InvalidTokenError.dart'; 78 | part 'src/main/errors/InvalidShardError.dart'; 79 | part 'src/main/errors/NotSetupError.dart'; 80 | -------------------------------------------------------------------------------- /lib/src/internals.dart: -------------------------------------------------------------------------------- 1 | /// Whether or not it has been setup. 2 | bool setup = false; 3 | 4 | /// Whether or not it is running in a browser. 5 | bool browser = false; 6 | 7 | /// The OS or browser name. 8 | String operatingSystem; 9 | -------------------------------------------------------------------------------- /lib/src/main/Client.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// The base class. 4 | /// It contains all of the methods. 5 | class Client { 6 | String _token; 7 | ClientOptions _options; 8 | DateTime _startTime; 9 | _WS _ws; 10 | _EventController _events; 11 | 12 | /// The HTTP client. 13 | Http http; 14 | 15 | /// The logged in user. 16 | ClientUser user; 17 | 18 | /// The bot's OAuth2 app. 19 | ClientOAuth2Application app; 20 | 21 | /// All of the guilds the bot is in. 22 | Map guilds; 23 | 24 | /// All of the channels the bot is in. 25 | Map channels; 26 | 27 | /// All of the users the bot can see. Does not always have offline users 28 | /// without forceFetchUsers enabled. 29 | Map users; 30 | 31 | /// Whether or not the client is ready. 32 | bool ready = false; 33 | 34 | /// The current version. 35 | String version = _Constants.version; 36 | 37 | /// The client's internal shards. 38 | Map shards; 39 | 40 | /// Emitted when a raw packet is received from the websocket connection. 41 | Stream onRaw; 42 | 43 | /// Emitted when a shard is disconnected from the websocket. 44 | Stream onDisconnect; 45 | 46 | /// Emitted before all HTTP requests are sent. (You can edit them) 47 | /// 48 | /// **WARNING:** Once you listen to this stream, all requests 49 | /// will be halted until you call `request.send()` 50 | Stream beforeHttpRequestSend; 51 | 52 | /// Emitted when a successful HTTP response is received. 53 | Stream onHttpResponse; 54 | 55 | /// Emitted when a HTTP request failed. 56 | Stream onHttpError; 57 | 58 | /// Sent when the client is ratelimited, either by the ratelimit handler itself, 59 | /// or when a 429 is received. 60 | Stream onRatelimited; 61 | 62 | /// Emitted when the client is ready. 63 | Stream onReady; 64 | 65 | /// Emitted when a message is received. 66 | Stream onMessage; 67 | 68 | /// Emitted when a message is edited. 69 | Stream onMessageUpdate; 70 | 71 | /// Emitted when a message is deleted. 72 | Stream onMessageDelete; 73 | 74 | /// Emitted when a channel is created. 75 | Stream onChannelCreate; 76 | 77 | /// Emitted when a channel is updated. 78 | Stream onChannelUpdate; 79 | 80 | /// Emitted when a channel is deleted. 81 | Stream onChannelDelete; 82 | 83 | /// Emitted when a member is banned. 84 | Stream onGuildBanAdd; 85 | 86 | /// Emitted when a user is unbanned. 87 | Stream onGuildBanRemove; 88 | 89 | /// Emitted when the client joins a guild. 90 | Stream onGuildCreate; 91 | 92 | /// Emitted when a guild is updated.. 93 | Stream onGuildUpdate; 94 | 95 | /// Emitted when the client leaves a guild. 96 | Stream onGuildDelete; 97 | 98 | /// Emitted when a guild becomes unavailable. 99 | Stream onGuildUnavailable; 100 | 101 | /// Emitted when a member joins a guild. 102 | Stream onGuildMemberAdd; 103 | 104 | /// Emitted when a member is updated. 105 | Stream onGuildMemberUpdate; 106 | 107 | /// Emitted when a user leaves a guild. 108 | Stream onGuildMemberRemove; 109 | 110 | /// Emitted when a member's presence is changed. 111 | Stream onPresenceUpdate; 112 | 113 | /// Emitted when a user starts typing. 114 | Stream onTyping; 115 | 116 | /// Emitted when a role is created. 117 | Stream onRoleCreate; 118 | 119 | /// Emitted when a role is updated. 120 | Stream onRoleUpdate; 121 | 122 | /// Emitted when a role is deleted. 123 | Stream onRoleDelete; 124 | 125 | /// Creates and logs in a new client. 126 | Client(this._token, [this._options]) { 127 | if (!internals.setup) throw new NotSetupError(); 128 | 129 | if (this._options == null) { 130 | this._options = new ClientOptions(); 131 | } 132 | 133 | this.guilds = new Map(); 134 | this.channels = new Map(); 135 | this.users = new Map(); 136 | this.shards = new Map(); 137 | 138 | this.http = new Http._new(this); 139 | this._events = new _EventController(this); 140 | this._ws = new _WS(this); 141 | } 142 | 143 | /// The client's uptime. 144 | Duration get uptime => new DateTime.now().difference(_startTime); 145 | 146 | /// Destroys the websocket connection, and all streams. 147 | Future destroy() async { 148 | await this._ws.close(); 149 | await this._events.destroy(); 150 | return null; 151 | } 152 | 153 | /// Gets a [User] object. 154 | /// 155 | /// Throws an [Exception] if the HTTP request errored. 156 | /// Client.getUser("user id"); 157 | Future getUser(dynamic user) async { 158 | final String id = Util.resolve('user', user); 159 | 160 | final HttpResponse r = await this.http.send('GET', '/users/$id'); 161 | return new User._new(this, r.body.asJson() as Map); 162 | } 163 | 164 | /// Gets an [Invite] object. 165 | /// 166 | /// Throws an [Exception] if the HTTP request errored. 167 | /// Client.getInvite("invite code"); 168 | Future getInvite(String code) async { 169 | final HttpResponse r = await this.http.send('GET', '/invites/$code'); 170 | return new Invite._new(this, r.body.asJson() as Map); 171 | } 172 | 173 | /// Gets an [OAuth2Info] object. 174 | /// 175 | /// Throws an [Exception] if the HTTP request errored 176 | /// Client.getOAuth2Info("app id"); 177 | Future getOAuth2Info(dynamic app) async { 178 | final String id = Util.resolve('app', app); 179 | 180 | final HttpResponse r = await this 181 | .http 182 | .send('GET', '/oauth2/authorize?client_id=$id&scope=bot'); 183 | return new OAuth2Info._new(this, r.body.asJson() as Map); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /lib/src/main/errors/ClientNotReadyError.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An error for when a method is called before the client is ready. 4 | class ClientNotReadyError implements Exception { 5 | /// Returns a string representation of this object. 6 | @override 7 | String toString() => "ClientNotReadyError"; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/main/errors/HttpError.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An HTTP error. 4 | class HttpError implements Exception { 5 | /// The full http response. 6 | HttpResponse response; 7 | 8 | /// Discord's error code, if provided. 9 | int code; 10 | 11 | /// Discord's message, if provided. 12 | String message; 13 | 14 | /// Constructs a new [HttpError]. 15 | HttpError._new(this.response) { 16 | if (response.headers['content-type'] == "application/json") { 17 | this.code = response.body.asJson()['code']; 18 | this.message = response.body.asJson()['message']; 19 | } 20 | } 21 | 22 | /// Returns a string representation of this object. 23 | @override 24 | String toString() => response.status.toString() + ": " + response.statusText; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/main/errors/InvalidShardError.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An error for when your shard settings are invalid. 4 | class InvalidShardError implements Exception { 5 | /// Returns a string representation of this object. 6 | @override 7 | String toString() => "InvalidShardError"; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/main/errors/InvalidTokenError.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An error for when your token is invalid. 4 | class InvalidTokenError implements Exception { 5 | /// Returns a string representation of this object. 6 | @override 7 | String toString() => "InvalidTokenError"; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/main/errors/NotSetupError.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Thrown when you don't setup the client first. 4 | /// See [configureDiscordForBrowser()](https://www.dartdocs.org/documentation/discord/latest/discord.discord_browser/configureDiscordForBrowser.html) 5 | /// or [configureDiscordForVM()](https://www.dartdocs.org/documentation/discord/latest/discord.discord_vm/configureDiscordForVM.html) 6 | class NotSetupError implements Exception { 7 | /// Returns a string representation of this object. 8 | @override 9 | String toString() => "NotSetupError"; 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/main/events/BeforeHttpRequestSendEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent before all HTTP requests are sent. (You can edit them) 4 | /// 5 | /// **WARNING:** Once you listen to this stream, all requests 6 | /// will be halted until you call `request.send()` 7 | class BeforeHttpRequestSendEvent { 8 | /// The request about to be sent. 9 | HttpRequest request; 10 | 11 | BeforeHttpRequestSendEvent._new(Client client, this.request) { 12 | if (client != null) client._events.beforeHttpRequestSend.add(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/main/events/ChannelCreateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a channel is created.. 4 | class ChannelCreateEvent { 5 | /// The channel that was created, either a [GuildChannel], [DMChannel], or [GroupDMChannel]. 6 | dynamic channel; 7 | 8 | ChannelCreateEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | if (json['d']['type'] == 1) { 11 | this.channel = 12 | new DMChannel._new(client, json['d'] as Map); 13 | client._events.onChannelCreate.add(this); 14 | } else if (json['d']['type'] == 3) { 15 | this.channel = 16 | new GroupDMChannel._new(client, json['d'] as Map); 17 | client._events.onChannelCreate.add(this); 18 | } else { 19 | final Guild guild = client.guilds[json['d']['guild_id']]; 20 | if (json['d']['type'] == 0) { 21 | this.channel = new TextChannel._new( 22 | client, json['d'] as Map, guild); 23 | } else { 24 | this.channel = new VoiceChannel._new( 25 | client, json['d'] as Map, guild); 26 | } 27 | client._events.onChannelCreate.add(this); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/main/events/ChannelDeleteEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a channel is deleted. 4 | class ChannelDeleteEvent { 5 | /// The channel that was deleted, either a [GuildChannel], [DMChannel] or [GroupDMChannel]. 6 | dynamic channel; 7 | 8 | ChannelDeleteEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | if (json['d']['type'] == 1) { 11 | this.channel = 12 | new DMChannel._new(client, json['d'] as Map); 13 | client.channels.remove(channel.id); 14 | client._events.onChannelDelete.add(this); 15 | } else if (json['d']['type'] == 3) { 16 | this.channel = 17 | new GroupDMChannel._new(client, json['d'] as Map); 18 | client.channels.remove(channel.id); 19 | client._events.onChannelDelete.add(this); 20 | } else { 21 | final Guild guild = client.guilds[json['d']['guild_id']]; 22 | if (json['d']['type'] == 0) { 23 | this.channel = new TextChannel._new( 24 | client, json['d'] as Map, guild); 25 | } else { 26 | this.channel = new VoiceChannel._new( 27 | client, json['d'] as Map, guild); 28 | } 29 | guild.channels.remove(channel.id); 30 | client.channels.remove(channel.id); 31 | client._events.onChannelDelete.add(this); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/main/events/ChannelUpdateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a channel is updated. 4 | class ChannelUpdateEvent { 5 | /// The channel prior to the update. 6 | GuildChannel oldChannel; 7 | 8 | /// The channel after the update. 9 | GuildChannel newChannel; 10 | 11 | ChannelUpdateEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | final Guild guild = client.guilds[json['d']['guild_id']]; 14 | this.oldChannel = client.channels[json['d']['id']]; 15 | if (json['d']['type'] == 0) { 16 | this.newChannel = new TextChannel._new( 17 | client, json['d'] as Map, guild); 18 | } else { 19 | this.newChannel = new VoiceChannel._new( 20 | client, json['d'] as Map, guild); 21 | } 22 | client._events.onChannelUpdate.add(this); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/main/events/DisconnectEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a shard disconnects from the websocket. 4 | class DisconnectEvent { 5 | /// The shard that got disconnected. 6 | Shard shard; 7 | 8 | /// The close code. 9 | int closeCode; 10 | 11 | DisconnectEvent._new(Client client, this.shard, this.closeCode) { 12 | client._events.onDisconnect.add(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildBanAddEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a member is banned. 4 | class GuildBanAddEvent { 5 | /// The guild that the member was banned from. 6 | Guild guild; 7 | 8 | /// The user that was banned. 9 | User user; 10 | 11 | GuildBanAddEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | this.guild = client.guilds[json['d']['guild_id']]; 14 | this.user = 15 | new User._new(client, json['d']['user'] as Map); 16 | client._events.onGuildBanAdd.add(this); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildBanRemoveEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a user is unbanned from a guild. 4 | class GuildBanRemoveEvent { 5 | /// The guild that the member was unbanned from. 6 | Guild guild; 7 | 8 | /// The user that was unbanned. 9 | User user; 10 | 11 | GuildBanRemoveEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | this.guild = client.guilds[json['d']['guild_id']]; 14 | this.user = 15 | new User._new(client, json['d']['user'] as Map); 16 | client._events.onGuildBanRemove.add(this); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildCreateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when the bot joins a guild. 4 | class GuildCreateEvent { 5 | /// The guild created. 6 | Guild guild; 7 | 8 | GuildCreateEvent._new(Client client, Map json, Shard shard) { 9 | this.guild = 10 | new Guild._new(client, json['d'] as Map, true, true); 11 | 12 | if (shard._ws.client._options.forceFetchMembers) 13 | shard.send("REQUEST_GUILD_MEMBERS", 14 | {"guild_id": guild.id, "query": "", "limit": 0}); 15 | 16 | if (!client.ready) { 17 | shard._ws.testReady(); 18 | } else { 19 | client._events.onGuildCreate.add(this); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildDeleteEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when you leave a guild. 4 | class GuildDeleteEvent { 5 | /// The guild. 6 | Guild guild; 7 | 8 | GuildDeleteEvent._new(Client client, Map json, Shard shard) { 9 | if (client.ready) { 10 | this.guild = client.guilds[json['d']['id']]; 11 | client.guilds.remove(json['d']['id']); 12 | shard.guilds.remove(json['d']['id']); 13 | client._events.onGuildDelete.add(this); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildMemberAddEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a member joins a guild. 4 | class GuildMemberAddEvent { 5 | /// The member that joined. 6 | Member member; 7 | 8 | GuildMemberAddEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | final Guild guild = client.guilds[json['d']['guild_id']]; 11 | guild.memberCount++; 12 | this.member = 13 | new Member._new(client, json['d'] as Map, guild); 14 | client._events.onGuildMemberAdd.add(this); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildMemberRemoveEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a user leaves a guild, can be a leave, kick, or ban. 4 | class GuildMemberRemoveEvent { 5 | /// The guild the user left. 6 | Guild guild; 7 | 8 | ///The user that left. 9 | User user; 10 | 11 | GuildMemberRemoveEvent._new(Client client, Map json) { 12 | if (client.ready && json['d']['user']['id'] != client.user.id) { 13 | this.guild = client.guilds[json['d']['guild_id']]; 14 | guild.memberCount--; 15 | this.user = 16 | new User._new(client, json['d']['user'] as Map); 17 | guild.members.remove(user.id); 18 | client._events.onGuildMemberRemove.add(this); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildMemberUpdateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a member is updated. 4 | class GuildMemberUpdateEvent { 5 | /// The member prior to the update. 6 | Member oldMember; 7 | 8 | /// The member after the update. 9 | Member newMember; 10 | 11 | GuildMemberUpdateEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | final Guild guild = client.guilds[json['d']['guild_id']]; 14 | this.oldMember = guild.members[json['d']['user']['id']]; 15 | this.newMember = 16 | new Member._new(client, json['d'] as Map, guild); 17 | client._events.onGuildMemberUpdate.add(this); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildUnavailableEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when you leave a guild or it becomes unavailable. 4 | class GuildUnavailableEvent { 5 | /// An unavailable guild object. 6 | Guild guild; 7 | 8 | GuildUnavailableEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | this.guild = new Guild._new(client, null, false); 11 | client.guilds[json['d']['id']] = guild; 12 | client._events.onGuildUnavailable.add(this); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/main/events/GuildUpdateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a guild is updated. 4 | class GuildUpdateEvent { 5 | /// The guild prior to the update. 6 | Guild oldGuild; 7 | 8 | /// The guild after the update. 9 | Guild newGuild; 10 | 11 | GuildUpdateEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | this.newGuild = new Guild._new(client, json['d'] as Map); 14 | this.oldGuild = client.guilds[this.newGuild.id]; 15 | this.newGuild.channels = this.oldGuild.channels; 16 | this.newGuild.members = this.oldGuild.members; 17 | 18 | client._events.onGuildUpdate.add(this); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/main/events/HttpErrorEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a failed HTTP response is received. 4 | class HttpErrorEvent { 5 | /// The HTTP response. 6 | HttpResponse response; 7 | 8 | HttpErrorEvent._new(Client client, this.response) { 9 | client._events.onHttpError.add(this); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/main/events/HttpResponseEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a successful HTTP response is received. 4 | class HttpResponseEvent { 5 | /// The HTTP response. 6 | HttpResponse response; 7 | 8 | HttpResponseEvent._new(Client client, this.response) { 9 | client._events.onHttpResponse.add(this); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/main/events/MessageDeleteEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a message is deleted. 4 | class MessageDeleteEvent { 5 | /// The message, if cached. 6 | Message message; 7 | 8 | /// The ID of the message. 9 | String id; 10 | 11 | MessageDeleteEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | if (client.channels[json['d']['channel_id']].messages[json['d']['id']] != 14 | null) { 15 | this.message = 16 | client.channels[json['d']['channel_id']].messages[json['d']['id']]; 17 | this.id = message.id; 18 | this.message._onDelete.add(this); 19 | client._events.onMessageDelete.add(this); 20 | } else { 21 | this.id = json['d']['id']; 22 | if (!client._options.ignoreUncachedEvents) { 23 | client._events.onMessageDelete.add(this); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/main/events/MessageEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a new message is received. 4 | class MessageEvent { 5 | /// The new message. 6 | Message message; 7 | 8 | MessageEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | this.message = 11 | new Message._new(client, json['d'] as Map); 12 | client._events.onMessage.add(this); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/main/events/MessageUpdateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a message is updated. 4 | class MessageUpdateEvent { 5 | /// The old message, if cached. 6 | Message oldMessage; 7 | 8 | /// The updated message, if cached. 9 | Message newMessage; 10 | 11 | /// The message's ID. 12 | String id; 13 | 14 | MessageUpdateEvent._new(Client client, Map json) { 15 | if (client.ready) { 16 | if (client.channels[json['d']['channel_id']].messages[json['d']['id']] != 17 | null) { 18 | this.oldMessage = 19 | client.channels[json['d']['channel_id']].messages[json['d']['id']]; 20 | Map data = oldMessage.raw; 21 | data.addAll(json['d'] as Map); 22 | this.newMessage = new Message._new(client, data); 23 | this.id = newMessage.id; 24 | this.oldMessage._onUpdate.add(this); 25 | client._events.onMessageUpdate.add(this); 26 | } else { 27 | this.id = json['d']['id']; 28 | if (!client._options.ignoreUncachedEvents) { 29 | client._events.onMessageUpdate.add(this); 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/main/events/PresenceUpdateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a member's presence updates. 4 | class PresenceUpdateEvent { 5 | /// The old member, may be null. 6 | Member oldMember; 7 | 8 | /// The new member. 9 | Member newMember; 10 | 11 | PresenceUpdateEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | Map data = json['d'] as Map; 14 | if (data['user'].length > 1) { 15 | data['user'] = data['user'] as Map; 16 | } else { 17 | data['user'] = client.users[data['user']['id']]?.raw; 18 | } 19 | if (data['user'] == null) return; 20 | if (data['guild_id'] == null) return; 21 | 22 | this.newMember = new Member._new(client, data); 23 | this.oldMember = newMember.guild.members[newMember.id]; 24 | client._events.onPresenceUpdate.add(this); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/main/events/RatelimitEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when the client is ratelimited, either by the ratelimit handler itself, 4 | /// or when a 429 is received. 5 | class RatelimitEvent { 6 | /// true if ratelimit handler stopped the request preventatively 7 | /// 8 | /// false if the client received a 429 9 | bool handled; 10 | 11 | /// The request that was ratelimited. 12 | HttpRequest request; 13 | 14 | /// The response received if the ratelimit handler did not stop 15 | /// the request (rare) 16 | HttpResponse response; 17 | 18 | RatelimitEvent._new(Client client, this.request, this.handled, 19 | [this.response]) { 20 | client._events.onRatelimited.add(this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/main/events/RawEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when the client is ready. 4 | class RawEvent { 5 | /// The shard the packet was received on. 6 | Shard shard; 7 | 8 | /// The received packet. 9 | Map packet; 10 | 11 | RawEvent._new(Client client, this.shard, this.packet) { 12 | client._events.onRaw.add(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/main/events/ReadyEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when the client is ready. 4 | class ReadyEvent { 5 | ReadyEvent._new(Client client) { 6 | client.ready = true; 7 | client._events.onReady.add(this); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/main/events/RoleCreateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a role is created. 4 | class RoleCreateEvent { 5 | /// The role that was created. 6 | Role role; 7 | 8 | RoleCreateEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | final Guild guild = client.guilds[json['d']['guild_id']]; 11 | this.role = new Role._new( 12 | client, json['d']['role'] as Map, guild); 13 | client._events.onRoleCreate.add(this); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/main/events/RoleDeleteEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a role is deleted. 4 | class RoleDeleteEvent { 5 | /// The role that was deleted. 6 | Role role; 7 | 8 | RoleDeleteEvent._new(Client client, Map json) { 9 | if (client.ready) { 10 | final Guild guild = client.guilds[json['d']['guild_id']]; 11 | this.role = guild.roles[json['d']['role_id']]; 12 | guild.roles.remove(role.id); 13 | client._events.onRoleDelete.add(this); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/main/events/RoleUpdateEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a role is updated. 4 | class RoleUpdateEvent { 5 | /// The role prior to the update. 6 | Role oldRole; 7 | 8 | /// The role after the update. 9 | Role newRole; 10 | 11 | RoleUpdateEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | final Guild guild = client.guilds[json['d']['guild_id']]; 14 | this.oldRole = guild.roles[json['d']['role']['id']]; 15 | this.newRole = new Role._new( 16 | client, json['d']['role'] as Map, guild); 17 | client._events.onRoleUpdate.add(this); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/main/events/TypingEvent.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Sent when a user starts typing. 4 | class TypingEvent { 5 | /// The channel that the user is typing in. 6 | GuildChannel channel; 7 | 8 | /// The user that is typing. 9 | User user; 10 | 11 | TypingEvent._new(Client client, Map json) { 12 | if (client.ready) { 13 | this.channel = client.channels[json['d']['channel_id']]; 14 | this.user = client.users[json['d']['user_id']]; 15 | client._events.onTyping.add(this); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/main/internal/Http.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A HTTP request. 4 | class HttpRequest { 5 | StreamController _streamController; 6 | 7 | /// The HTTP client. 8 | Http http; 9 | 10 | /// The bucket the request will go into. 11 | HttpBucket bucket; 12 | 13 | /// The path the request is being made to. 14 | String path; 15 | 16 | /// The query params. 17 | Map queryParams; 18 | 19 | /// The final URI that the request is being made to. 20 | Uri uri; 21 | 22 | /// The HTTP method used. 23 | String method; 24 | 25 | /// Headers to be sent. 26 | Map headers; 27 | 28 | /// The request body. 29 | dynamic body; 30 | 31 | /// A stream that sends the response when received. Immediately closed after 32 | /// a value is sent. 33 | Stream stream; 34 | 35 | HttpRequest._new(this.http, this.method, this.path, this.queryParams, 36 | this.headers, this.body) { 37 | this.uri = 38 | new Uri.https(_Constants.host, _Constants.baseUri + path, queryParams); 39 | 40 | if (http.buckets[uri.toString()] == null) 41 | http.buckets[uri.toString()] = new HttpBucket._new(uri.toString()); 42 | 43 | this.bucket = http.buckets[uri.toString()]; 44 | 45 | this._streamController = new StreamController.broadcast(); 46 | this.stream = _streamController.stream; 47 | 48 | new BeforeHttpRequestSendEvent._new(this.http._client, this); 49 | 50 | if (this.http._client == null || 51 | !this.http._client._events.beforeHttpRequestSend.hasListener) 52 | this.send(); 53 | } 54 | 55 | /// Sends the request off to the bucket to be processed and sent. 56 | void send() => this.bucket._push(this); 57 | 58 | /// Destroys the request. 59 | void abort() { 60 | this._streamController.add(new HttpResponse._aborted(this)); 61 | this._streamController.close(); 62 | } 63 | 64 | Future _execute() async { 65 | try { 66 | if (this.body != null) { 67 | w_transport.JsonRequest r = new w_transport.JsonRequest() 68 | ..body = this.body; 69 | return HttpResponse._fromResponse(this, 70 | await r.send(this.method, uri: this.uri, headers: this.headers)); 71 | } else { 72 | return HttpResponse._fromResponse( 73 | this, 74 | await w_transport.Http 75 | .send(this.method, this.uri, headers: this.headers)); 76 | } 77 | } on w_transport.RequestException catch (err) { 78 | return HttpResponse._fromResponse(this, err.response); 79 | } 80 | } 81 | } 82 | 83 | /// A HTTP response. More documentation can be found at the 84 | /// [w_transport docs](https://www.dartdocs.org/documentation/w_transport/3.0.0/w_transport/Response-class.html) 85 | class HttpResponse extends w_transport.Response { 86 | /// The HTTP request. 87 | HttpRequest request; 88 | 89 | /// Whether or not the request was aborted. If true, all other fields will be null. 90 | bool aborted; 91 | 92 | HttpResponse._new(this.request, int status, String statusText, 93 | Map headers, String body, 94 | [this.aborted = false]) 95 | : super.fromString(status, statusText, headers, body); 96 | 97 | HttpResponse._aborted(this.request, [this.aborted = true]) 98 | : super.fromString(0, "ABORTED", {}, null); 99 | 100 | static HttpResponse _fromResponse( 101 | HttpRequest request, w_transport.Response r) { 102 | return new HttpResponse._new( 103 | request, r.status, r.statusText, r.headers, r.body.asString()); 104 | } 105 | } 106 | 107 | /// A bucket for managing ratelimits. 108 | class HttpBucket { 109 | /// The url that this bucket is handling requests for. 110 | String url; 111 | 112 | /// The number of requests that can be made. 113 | int limit; 114 | 115 | /// The number of remaining requests that can be made. May not always be accurate. 116 | int ratelimitRemaining = 1; 117 | 118 | /// When the ratelimits reset. 119 | DateTime ratelimitReset; 120 | 121 | /// The time difference between you and Discord. 122 | Duration timeDifference; 123 | 124 | /// A queue of requests waiting to be sent. 125 | List requests = []; 126 | 127 | /// Whether or not the bucket is waiting for a request to complete 128 | /// before continuing. 129 | bool waiting = false; 130 | 131 | HttpBucket._new(this.url); 132 | 133 | void _push(HttpRequest request) { 134 | this.requests.add(request); 135 | this._handle(); 136 | } 137 | 138 | void _handle() { 139 | if (this.waiting || this.requests.length == 0) return; 140 | this.waiting = true; 141 | 142 | this._execute(this.requests[0]); 143 | } 144 | 145 | void _execute(HttpRequest request) { 146 | if (this.ratelimitRemaining == null || this.ratelimitRemaining > 0) { 147 | request._execute().then((HttpResponse r) { 148 | this.limit = r.headers['x-ratelimit-limit'] != null 149 | ? int.parse(r.headers['x-ratelimit-limit']) 150 | : null; 151 | this.ratelimitRemaining = r.headers['x-ratelimit-remaining'] != null 152 | ? int.parse(r.headers['x-ratelimit-remaining']) 153 | : null; 154 | this.ratelimitReset = r.headers['x-ratelimit-reset'] != null 155 | ? new DateTime.fromMillisecondsSinceEpoch( 156 | int.parse(r.headers['x-ratelimit-reset']) * 1000, 157 | isUtc: true) 158 | : null; 159 | try { 160 | this.timeDifference = new DateTime.now() 161 | .toUtc() 162 | .difference(http_parser.parseHttpDate(r.headers['date']).toUtc()); 163 | } catch (err) { 164 | this.timeDifference = new Duration(); 165 | } 166 | 167 | if (r.status == 429) { 168 | new RatelimitEvent._new(request.http._client, request, false, r); 169 | new Timer( 170 | new Duration(milliseconds: r.body.asJson()['retry_after'] + 500), 171 | () => this._execute(request)); 172 | } else { 173 | this.waiting = false; 174 | this.requests.remove(request); 175 | request._streamController.add(r); 176 | request._streamController.close(); 177 | this._handle(); 178 | } 179 | }); 180 | } else { 181 | final Duration waitTime = 182 | this.ratelimitReset.difference(new DateTime.now().toUtc()) + 183 | this.timeDifference + 184 | new Duration(milliseconds: 1000); 185 | if (waitTime.isNegative) { 186 | this.ratelimitRemaining = 1; 187 | this._execute(request); 188 | } else { 189 | new RatelimitEvent._new(request.http._client, request, true); 190 | new Timer(waitTime, () { 191 | this.ratelimitRemaining = 1; 192 | this._execute(request); 193 | }); 194 | } 195 | } 196 | } 197 | } 198 | 199 | /// The client's HTTP client. 200 | class Http { 201 | Client _client; 202 | 203 | /// The buckets. 204 | Map buckets = {}; 205 | 206 | /// Headers sent on every request. 207 | Map headers; 208 | 209 | Http._new([this._client]) { 210 | this.headers = {'Content-Type': 'application/json'}; 211 | 212 | if (!internals.browser) 213 | headers['User-Agent'] = 214 | 'DiscordBot (https://github.com/Hackzzila/nyx, ${_Constants.version})'; 215 | } 216 | 217 | /// Sends a HTTP request. 218 | Future send(String method, String path, 219 | {dynamic body, 220 | Map queryParams, 221 | bool beforeReady: false, 222 | Map headers: const {}}) async { 223 | if (_client is Client && !this._client.ready && !beforeReady) 224 | throw new ClientNotReadyError(); 225 | 226 | HttpRequest request = new HttpRequest._new(this, method, path, queryParams, 227 | new Map.from(this.headers)..addAll(headers), body); 228 | 229 | await for (HttpResponse r in request.stream) { 230 | if (!r.aborted && r.status >= 200 && r.status < 300) { 231 | if (_client != null) new HttpResponseEvent._new(_client, r); 232 | return r; 233 | } else { 234 | if (_client != null) new HttpErrorEvent._new(_client, r); 235 | throw new HttpError._new(r); 236 | } 237 | } 238 | return null; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /lib/src/main/internal/Util.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// The utility functions for the client. 4 | class Util { 5 | /// Gets a DateTime from a snowflake ID. 6 | static DateTime getDate(String id) { 7 | return new DateTime.fromMillisecondsSinceEpoch( 8 | ((int.parse(id) / 4194304) + 1420070400000).toInt()); 9 | } 10 | 11 | /// Resolves an object into a target object. 12 | static String resolve(String to, dynamic object) { 13 | if (to == "channel") { 14 | if (object is Message) { 15 | return object.channel.id; 16 | } else if (object is Channel) { 17 | return object.id; 18 | } else if (object is Guild) { 19 | return object.defaultChannel.id; 20 | } else { 21 | return object.toString(); 22 | } 23 | } else if (to == "message") { 24 | if (object is Message) { 25 | return object.id; 26 | } else { 27 | return object.toString(); 28 | } 29 | } else if (to == "guild") { 30 | if (object is Message) { 31 | return object.guild.id; 32 | } else if (object is GuildChannel) { 33 | return object.guild.id; 34 | } else if (object is Guild) { 35 | return object.id; 36 | } else { 37 | return object.toString(); 38 | } 39 | } else if (to == "user") { 40 | if (object is Message) { 41 | return object.author.id; 42 | } else if (object is User) { 43 | return object.id; 44 | } else if (object is Member) { 45 | return object.id; 46 | } else { 47 | return object.toString(); 48 | } 49 | } else if (to == "member") { 50 | if (object is Message) { 51 | return object.author.id; 52 | } else if (object is User) { 53 | return object.id; 54 | } else if (object is Member) { 55 | return object.id; 56 | } else { 57 | return object.toString(); 58 | } 59 | } else if (to == "app") { 60 | if (object is User) { 61 | return object.id; 62 | } else if (object is Member) { 63 | return object.id; 64 | } else { 65 | return object.toString(); 66 | } 67 | } else { 68 | return null; 69 | } 70 | } 71 | 72 | /// Creates a text table. 73 | static String textTable(List> rows) { 74 | List> cols = []; 75 | List> newRows = []; 76 | List finalRows = []; 77 | 78 | rows.forEach((List row) { 79 | int cellCount = 0; 80 | row.forEach((String cell) { 81 | if (cols.length <= cellCount) cols.add([]); 82 | cols[cellCount].add(cell); 83 | cellCount++; 84 | }); 85 | }); 86 | 87 | int colCount = 0; 88 | cols.forEach((List col) { 89 | int maxLen = 0; 90 | col.forEach((String cell) { 91 | if (cell.length > maxLen) maxLen = cell.length; 92 | }); 93 | 94 | int cellCount = 0; 95 | col.forEach((String cell) { 96 | cols[colCount][cellCount] = cell + (" " * (maxLen - cell.length)); 97 | cellCount++; 98 | }); 99 | 100 | cols[colCount].insert(1, "-" * maxLen); 101 | colCount++; 102 | }); 103 | 104 | cols.forEach((List col) { 105 | int cellCount = 0; 106 | col.forEach((String cell) { 107 | if (newRows.length <= cellCount) newRows.add([]); 108 | newRows[cellCount].add(cell); 109 | cellCount++; 110 | }); 111 | }); 112 | 113 | newRows.forEach((List row) { 114 | finalRows.add(row.join(" ")); 115 | }); 116 | 117 | return finalRows.join("\n"); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/main/internal/_Constants.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | class _OPCodes { 4 | static const int dispatch = 0; 5 | static const int heartbeat = 1; 6 | static const int identify = 2; 7 | static const int statusUpdate = 3; 8 | static const int voiceStateUpdate = 4; 9 | static const int voiceGuildPing = 5; 10 | static const int resume = 6; 11 | static const int reconnect = 7; 12 | static const int requesGuildMembers = 8; 13 | static const int invalidSession = 9; 14 | static const int hello = 10; 15 | static const int heartbeatAck = 11; 16 | static const int guildSync = 12; 17 | } 18 | 19 | /// The client constants. 20 | class _Constants { 21 | static const String host = "discordapp.com"; 22 | static const String baseUri = "/api/v6"; 23 | static const String version = "0.15.0"; 24 | 25 | /// The gateway OP codes. 26 | static const Map opCodes = const { 27 | "DISPATCH": 0, 28 | "HEARTBEAT": 1, 29 | "IDENTIFY": 2, 30 | "STATUS_UPDATE": 3, 31 | "VOICE_STATE_UPDATE": 4, 32 | "VOICE_GUILD_PING": 5, 33 | "RESUME": 6, 34 | "RECONNECT": 7, 35 | "REQUEST_GUILD_MEMBERS": 8, 36 | "INVALID_SESSION": 9, 37 | "HELLO": 10, 38 | "HEARTBEAT_ACK": 11, 39 | "GUILD_SYNC": 12 40 | }; 41 | 42 | /// The permission bits. 43 | static const Map permissions = const { 44 | "CREATE_INSTANT_INVITE": 1 << 0, 45 | "KICK_MEMBERS": 1 << 1, 46 | "BAN_MEMBERS": 1 << 2, 47 | "ADMINISTRATOR": 1 << 3, 48 | "MANAGE_CHANNELS": 1 << 4, 49 | "MANAGE_GUILD": 1 << 5, 50 | "READ_MESSAGES": 1 << 10, 51 | "SEND_MESSAGES": 1 << 11, 52 | "SEND_TTS_MESSAGES": 1 << 12, 53 | "MANAGE_MESSAGES": 1 << 13, 54 | "EMBED_LINKS": 1 << 14, 55 | "ATTACH_FILES": 1 << 15, 56 | "READ_MESSAGE_HISTORY": 1 << 16, 57 | "MENTION_EVERYONE": 1 << 17, 58 | "EXTERNAL_EMOJIS": 1 << 18, 59 | "CONNECT": 1 << 20, 60 | "SPEAK": 1 << 21, 61 | "MUTE_MEMBERS": 1 << 22, 62 | "DEAFEN_MEMBERS": 1 << 23, 63 | "MOVE_MEMBERS": 1 << 24, 64 | "USE_VAD": 1 << 25, 65 | "CHANGE_NICKNAME": 1 << 26, 66 | "MANAGE_NICKNAMES": 1 << 27, 67 | "MANAGE_ROLES_OR_PERMISSIONS": 1 << 28, 68 | "MANAGE_WEBHOOKS": 1 << 29, 69 | "MANAGE_EMOJIS": 1 << 30 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/main/internal/_EventController.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A controller for all events. 4 | class _EventController { 5 | /// Emitted when a raw packet is received from the websocket connection. 6 | StreamController onRaw; 7 | 8 | /// Emitted when a shard is disconnected from the websocket. 9 | StreamController onDisconnect; 10 | 11 | /// Emitted before all HTTP requests are sent. (You can edit them) 12 | /// 13 | /// **WARNING:** Once you listen to this stream, all requests 14 | /// will be halted until you call `request.send()` 15 | StreamController beforeHttpRequestSend; 16 | 17 | /// Emitted when a successful HTTP response is received. 18 | StreamController onHttpResponse; 19 | 20 | /// Emitted when a HTTP request failed. 21 | StreamController onHttpError; 22 | 23 | /// Sent when the client is ratelimited, either by the ratelimit handler itself, 24 | /// or when a 429 is received. 25 | StreamController onRatelimited; 26 | 27 | /// Emitted when the client is ready. 28 | StreamController onReady; 29 | 30 | /// Emitted when a message is received. 31 | StreamController onMessage; 32 | 33 | /// Emitted when a message is edited. 34 | StreamController onMessageUpdate; 35 | 36 | /// Emitted when a message is edited. 37 | StreamController onMessageDelete; 38 | 39 | /// Emitted when a channel is created. 40 | StreamController onChannelCreate; 41 | 42 | /// Emitted when a channel is updated. 43 | StreamController onChannelUpdate; 44 | 45 | /// Emitted when a channel is deleted. 46 | StreamController onChannelDelete; 47 | 48 | /// Emitted when a member is banned. 49 | StreamController onGuildBanAdd; 50 | 51 | /// Emitted when a user is unbanned. 52 | StreamController onGuildBanRemove; 53 | 54 | /// Emitted when the client joins a guild. 55 | StreamController onGuildCreate; 56 | 57 | /// Emitted when a guild is updated.. 58 | StreamController onGuildUpdate; 59 | 60 | /// Emitted when the client leaves a guild. 61 | StreamController onGuildDelete; 62 | 63 | /// Emitted when a guild becomes unavailable. 64 | StreamController onGuildUnavailable; 65 | 66 | /// Emitted when a member joins a guild. 67 | StreamController onGuildMemberAdd; 68 | 69 | /// Emitted when a member is updated. 70 | StreamController onGuildMemberUpdate; 71 | 72 | /// Emitted when a user leaves a guild. 73 | StreamController onGuildMemberRemove; 74 | 75 | /// Emitted when a member's presence is updated. 76 | StreamController onPresenceUpdate; 77 | 78 | /// Emitted when a user starts typing. 79 | StreamController onTyping; 80 | 81 | /// Emitted when a role is updated. 82 | StreamController onRoleCreate; 83 | 84 | /// Emitted when a role is created. 85 | StreamController onRoleUpdate; 86 | 87 | /// Emitted when a role is deleted. 88 | StreamController onRoleDelete; 89 | 90 | /// Makes a new `EventController`. 91 | _EventController(Client client) { 92 | this.onRaw = new StreamController.broadcast(); 93 | client.onRaw = this.onRaw.stream; 94 | 95 | this.onDisconnect = new StreamController.broadcast(); 96 | client.onDisconnect = this.onDisconnect.stream; 97 | 98 | this.beforeHttpRequestSend = new StreamController.broadcast(); 99 | client.beforeHttpRequestSend = this.beforeHttpRequestSend.stream; 100 | 101 | this.onHttpResponse = new StreamController.broadcast(); 102 | client.onHttpResponse = this.onHttpResponse.stream; 103 | 104 | this.onHttpError = new StreamController.broadcast(); 105 | client.onHttpError = this.onHttpError.stream; 106 | 107 | this.onRatelimited = new StreamController.broadcast(); 108 | client.onRatelimited = this.onRatelimited.stream; 109 | 110 | this.onReady = new StreamController.broadcast(); 111 | client.onReady = this.onReady.stream; 112 | 113 | this.onMessage = new StreamController.broadcast(); 114 | client.onMessage = this.onMessage.stream; 115 | 116 | this.onMessageUpdate = new StreamController.broadcast(); 117 | client.onMessageUpdate = this.onMessageUpdate.stream; 118 | 119 | this.onMessageDelete = new StreamController.broadcast(); 120 | client.onMessageDelete = this.onMessageDelete.stream; 121 | 122 | this.onChannelCreate = new StreamController.broadcast(); 123 | client.onChannelCreate = this.onChannelCreate.stream; 124 | 125 | this.onChannelUpdate = new StreamController.broadcast(); 126 | client.onChannelUpdate = this.onChannelUpdate.stream; 127 | 128 | this.onChannelDelete = new StreamController.broadcast(); 129 | client.onChannelDelete = this.onChannelDelete.stream; 130 | 131 | this.onGuildBanAdd = new StreamController.broadcast(); 132 | client.onGuildBanAdd = this.onGuildBanAdd.stream; 133 | 134 | this.onGuildBanRemove = new StreamController.broadcast(); 135 | client.onGuildBanRemove = this.onGuildBanRemove.stream; 136 | 137 | this.onGuildCreate = new StreamController.broadcast(); 138 | client.onGuildCreate = this.onGuildCreate.stream; 139 | 140 | this.onGuildUpdate = new StreamController.broadcast(); 141 | client.onGuildUpdate = this.onGuildUpdate.stream; 142 | 143 | this.onGuildDelete = new StreamController.broadcast(); 144 | client.onGuildDelete = this.onGuildDelete.stream; 145 | 146 | this.onGuildUnavailable = new StreamController.broadcast(); 147 | client.onGuildUnavailable = this.onGuildUnavailable.stream; 148 | 149 | this.onGuildMemberAdd = new StreamController.broadcast(); 150 | client.onGuildMemberAdd = this.onGuildMemberAdd.stream; 151 | 152 | this.onGuildMemberUpdate = new StreamController.broadcast(); 153 | client.onGuildMemberUpdate = this.onGuildMemberUpdate.stream; 154 | 155 | this.onGuildMemberRemove = new StreamController.broadcast(); 156 | client.onGuildMemberRemove = this.onGuildMemberRemove.stream; 157 | 158 | this.onPresenceUpdate = new StreamController.broadcast(); 159 | client.onPresenceUpdate = this.onPresenceUpdate.stream; 160 | 161 | this.onTyping = new StreamController.broadcast(); 162 | client.onTyping = this.onTyping.stream; 163 | 164 | this.onRoleCreate = new StreamController.broadcast(); 165 | client.onRoleCreate = this.onRoleCreate.stream; 166 | 167 | this.onRoleUpdate = new StreamController.broadcast(); 168 | client.onRoleUpdate = this.onRoleUpdate.stream; 169 | 170 | this.onRoleDelete = new StreamController.broadcast(); 171 | client.onRoleDelete = this.onRoleDelete.stream; 172 | } 173 | 174 | /// Closes all streams. 175 | Future destroy() async { 176 | await this.onRaw.close(); 177 | await this.onDisconnect.close(); 178 | await this.beforeHttpRequestSend.close(); 179 | await this.onHttpResponse.close(); 180 | await this.onHttpError.close(); 181 | await this.onRatelimited.close(); 182 | await this.onGuildUpdate.close(); 183 | await this.onReady.close(); 184 | await this.onMessage.close(); 185 | await this.onMessageUpdate.close(); 186 | await this.onMessageDelete.close(); 187 | await this.onChannelCreate.close(); 188 | await this.onChannelUpdate.close(); 189 | await this.onChannelDelete.close(); 190 | await this.onGuildBanAdd.close(); 191 | await this.onGuildBanRemove.close(); 192 | await this.onGuildCreate.close(); 193 | await this.onGuildUpdate.close(); 194 | await this.onGuildDelete.close(); 195 | await this.onGuildUnavailable.close(); 196 | await this.onGuildMemberAdd.close(); 197 | await this.onGuildMemberUpdate.close(); 198 | await this.onGuildMemberRemove.close(); 199 | await this.onPresenceUpdate.close(); 200 | await this.onTyping.close(); 201 | await this.onRoleCreate.close(); 202 | await this.onRoleUpdate.close(); 203 | await this.onRoleDelete.close(); 204 | return null; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/src/main/internal/_WS.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// The WS manager for the client. 4 | class _WS { 5 | bool bot = false; 6 | 7 | /// The base websocket URL. 8 | String gateway; 9 | 10 | /// The client that the WS manager belongs to. 11 | Client client; 12 | 13 | /// Makes a new WS manager. 14 | _WS(this.client) { 15 | this.client.http.headers['Authorization'] = "Bot ${client._token}"; 16 | this 17 | .client 18 | .http 19 | .send("GET", "/gateway/bot", beforeReady: true) 20 | .then((HttpResponse r) { 21 | this.bot = true; 22 | this.gateway = r.body.asJson()['url']; 23 | if (this.client._options.shardCount == 1 && 24 | this.client._options.shardIds == const [0]) { 25 | this.client._options.shardIds = []; 26 | this.client._options.shardCount = r.body.asJson()['shards']; 27 | for (int i = 0; i < client._options.shardCount; i++) { 28 | this.client._options.shardIds.add(i); 29 | setupShard(i); 30 | } 31 | } else { 32 | for (int shardId in this.client._options.shardIds) { 33 | setupShard(shardId); 34 | } 35 | } 36 | this.connectShard(0); 37 | }).catchError((err) { 38 | this 39 | .client 40 | .http 41 | .send('GET', '/gateway', beforeReady: true) 42 | .then((HttpResponse r) { 43 | this.gateway = r.body.asJson()['url']; 44 | for (int shardId in this.client._options.shardIds) { 45 | setupShard(shardId); 46 | } 47 | this.connectShard(0); 48 | }); 49 | }); 50 | } 51 | 52 | void setupShard(int shardId) { 53 | Shard shard = new Shard._new(this, shardId); 54 | this.client.shards[shard.id] = shard; 55 | 56 | shard.onReady.listen((Shard s) { 57 | if (!client.ready) { 58 | testReady(); 59 | } 60 | }); 61 | } 62 | 63 | Future close() async { 64 | this.client.shards.forEach((int id, Shard shard) async { 65 | await shard._socket.close(); 66 | }); 67 | return null; 68 | } 69 | 70 | void connectShard(int index) { 71 | this.client.shards.values.toList()[index]._connect(true, true); 72 | if (index + 1 != this.client._options.shardIds.length) 73 | new Timer(new Duration(seconds: 6), () => connectShard(index + 1)); 74 | } 75 | 76 | void testReady() { 77 | bool match = true; 78 | client.guilds.forEach((String id, Guild o) { 79 | if (client._options.forceFetchMembers) { 80 | if (o == null || o.members.length != o.memberCount) match = false; 81 | } else { 82 | if (o == null) match = false; 83 | } 84 | }); 85 | 86 | bool match2 = true; 87 | this.client.shards.forEach((int id, Shard s) { 88 | if (!s.ready) match = false; 89 | }); 90 | 91 | if (match && match2) { 92 | client.ready = true; 93 | client._startTime = new DateTime.now(); 94 | if (client.user.bot) { 95 | client.http 96 | .send('GET', '/oauth2/applications/@me', beforeReady: true) 97 | .then((HttpResponse r) { 98 | client.app = new ClientOAuth2Application._new( 99 | client, r.body.asJson() as Map); 100 | new ReadyEvent._new(client); 101 | }); 102 | } else { 103 | new ReadyEvent._new(client); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/src/main/objects/Attachment.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A message attachment. 4 | class Attachment { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The attachment's ID 12 | String id; 13 | 14 | /// The attachment's filename. 15 | String filename; 16 | 17 | /// The attachment's URL. 18 | String url; 19 | 20 | /// The attachment's proxy URL. 21 | String proxyUrl; 22 | 23 | /// The attachment's file size. 24 | int size; 25 | 26 | /// The attachment's height, if an image. 27 | int height; 28 | 29 | /// The attachment's width, if an image, 30 | int width; 31 | 32 | /// A timestamp for when the message was created. 33 | DateTime createdAt; 34 | 35 | Attachment._new(this.client, this.raw) { 36 | this.id = raw['id']; 37 | this.filename = raw['filename']; 38 | this.url = raw['url']; 39 | this.proxyUrl = raw['proxyUrl']; 40 | this.size = raw['size']; 41 | this.height = raw['height']; 42 | this.width = raw['width']; 43 | this.createdAt = Util.getDate(this.id); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/main/objects/Channel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A channel. 4 | class Channel { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The channel's ID. 12 | String id; 13 | 14 | /// The channel's type. 15 | String type; 16 | 17 | /// A timestamp for when the channel was created. 18 | DateTime createdAt; 19 | 20 | Channel._new(this.client, this.raw, this.type) { 21 | this.id = raw['id']; 22 | this.createdAt = Util.getDate(this.id); 23 | 24 | client.channels[this.id] = this; 25 | } 26 | 27 | /// Deletes the channel. 28 | Future delete() async { 29 | await this.client.http.send('DELETE', "/channels/${this.id}"); 30 | return null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/main/objects/ClientOAuth2Application.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// The client's OAuth2 app, if the client is a bot. 4 | class ClientOAuth2Application extends OAuth2Application { 5 | /// The app's flags. 6 | int flags; 7 | 8 | /// The app's owner. 9 | User owner; 10 | 11 | ClientOAuth2Application._new(Client client, Map data) 12 | : super._new(client, data) { 13 | this.flags = raw['flags']; 14 | this.owner = new User._new(client, raw['owner'] as Map); 15 | } 16 | 17 | /// Creates an OAuth2 URL with the specified permissions. 18 | String makeOAuth2Url([int permissions = 0]) { 19 | return "https://discordapp.com/oauth2/authorize?client_id=$id&scope=bot&permissions=$permissions"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/main/objects/ClientOptions.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// The options for `Client`. 4 | class ClientOptions { 5 | /// Whether or not to disable @everyone and @here mentions at a global level. 6 | bool disableEveryone; 7 | 8 | /// Whether or not to automatically shard the client if the default shard 9 | /// values are untouched. 10 | bool autoShard; 11 | 12 | /// A list of shards for the client to run. 13 | List shardIds; 14 | 15 | /// The total number of shards. 16 | int shardCount; 17 | 18 | /// The number of messages to cache for each channel. 19 | int messageCacheSize; 20 | 21 | /// Whether or not to skip events if their object is not cached. 22 | /// Ex: `onMessageUpdate`. 23 | bool ignoreUncachedEvents; 24 | 25 | /// Whether or not to force fetch all of the members the client can see. 26 | /// Can slow down ready times but is recommended if you rely on `Message.member` 27 | /// or the member cache. Bots only, will break userbots. 28 | bool forceFetchMembers; 29 | 30 | /// A list of discord formatted events to be disabled. Note: some of these events 31 | /// can be dangerous to disable. Ex: `TYPING_START` 32 | List disabledEvents; 33 | 34 | /// Makes a new `ClientOptions` object. 35 | ClientOptions( 36 | {this.disableEveryone: false, 37 | this.autoShard: true, 38 | this.shardIds: const [0], 39 | this.shardCount: 1, 40 | this.disabledEvents: const [], 41 | this.messageCacheSize: 200, 42 | this.ignoreUncachedEvents: true, 43 | this.forceFetchMembers: false}); 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/main/objects/ClientUser.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// The client user. 4 | class ClientUser extends User { 5 | /// The client user's email, null if the client user is a bot. 6 | String email; 7 | 8 | /// Weather or not the client user's account is verified. 9 | bool verified; 10 | 11 | /// Weather or not the client user has MFA enabled. 12 | bool mfa; 13 | 14 | ClientUser._new(Client client, Map data) 15 | : super._new(client, data) { 16 | this.email = raw['email']; 17 | this.verified = raw['verified']; 18 | this.mfa = raw['mfa_enabled']; 19 | this.client = client; 20 | } 21 | 22 | /// Updates the client's status. 23 | /// ClientUser.setStatus(status: 'dnd'); 24 | ClientUser setStatus({String status: null}) { 25 | return this.setPresence(status: status); 26 | } 27 | 28 | /// Updates the client's game. 29 | /// ClientUser.setGame(name: '<3'); 30 | ClientUser setGame({String name: null, type: 0, url: null}) { 31 | Map game = {'name': name, 'type': type, 'url': url}; 32 | 33 | return this.setPresence(activity: game); 34 | } 35 | 36 | /// Updates the client's presence. 37 | /// ClientUser.setPresence(status: s, activity: { 'name': args.join(' ') }); 38 | ClientUser setPresence( 39 | {String status: null, bool afk: false, dynamic activity: null}) { 40 | Map game = { 41 | 'name': activity != null ? activity['name'] : null, 42 | 'type': activity != null 43 | ? activity['type'] != null ? activity['type'] : 0 44 | : 0, 45 | 'url': activity != null ? activity['url'] : null 46 | }; 47 | 48 | this.client.shards.forEach((int id, Shard shard) { 49 | shard.setPresence(status: status, afk: afk, activity: game); 50 | }); 51 | 52 | return this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/main/objects/DMChannel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A private channel. 4 | class DMChannel extends Channel { 5 | Timer _typing; 6 | 7 | /// The ID for the last message in the channel. 8 | String lastMessageID; 9 | 10 | /// A map of messages sent to this channel. 11 | LinkedHashMap messages; 12 | 13 | /// The recipient. 14 | User recipient; 15 | 16 | DMChannel._new(Client client, Map data) 17 | : super._new(client, data, "private") { 18 | this.lastMessageID = raw['last_message_id']; 19 | this.messages = new LinkedHashMap(); 20 | 21 | if (raw['recipients'] != null) { 22 | this.recipient = 23 | new User._new(client, raw['recipients'][0] as Map); 24 | } else { 25 | this.recipient = 26 | new User._new(client, raw['recipient'] as Map); 27 | } 28 | } 29 | 30 | void _cacheMessage(Message message) { 31 | if (this.client._options.messageCacheSize > 0) { 32 | if (this.messages.length >= this.client._options.messageCacheSize) { 33 | this.messages.values.toList().first._onUpdate.close(); 34 | this.messages.values.toList().first._onDelete.close(); 35 | this.messages.remove(this.messages.values.toList().first.id); 36 | } 37 | this.messages[message.id] = message; 38 | } 39 | } 40 | 41 | /// Sends a message. 42 | /// 43 | /// Throws an [Exception] if the HTTP request errored. 44 | /// Channel.send(content: "My content!"); 45 | Future send( 46 | {String content, 47 | Map embed, 48 | bool tts: false, 49 | String nonce, 50 | bool disableEveryone}) async { 51 | String newContent; 52 | if (content != null && 53 | (disableEveryone == true || 54 | (disableEveryone == null && 55 | this.client._options.disableEveryone))) { 56 | newContent = content 57 | .replaceAll("@everyone", "@\u200Beveryone") 58 | .replaceAll("@here", "@\u200Bhere"); 59 | } else { 60 | newContent = content; 61 | } 62 | 63 | final HttpResponse r = await this.client.http.send( 64 | 'POST', '/channels/${this.id}/messages', body: { 65 | "content": newContent, 66 | "tts": tts, 67 | "nonce": nonce, 68 | "embed": embed 69 | }); 70 | return new Message._new( 71 | this.client, r.body.asJson() as Map); 72 | } 73 | 74 | @deprecated 75 | 76 | /// Sends a message. 77 | /// 78 | /// Throws an [Exception] if the HTTP request errored. 79 | /// Channel.sendMessage(content: "My content!"); 80 | Future sendMessage( 81 | {String content, 82 | Map embed, 83 | bool tts: false, 84 | String nonce, 85 | bool disableEveryone}) async { 86 | return this.send( 87 | content: content, 88 | embed: embed, 89 | tts: tts, 90 | nonce: nonce, 91 | disableEveryone: disableEveryone); 92 | } 93 | 94 | /// Gets a [Message] object. Only usable by bot accounts. 95 | /// 96 | /// Throws an [Exception] if the HTTP request errored or if the client user 97 | /// is not a bot. 98 | /// Channel.getMessage("message id"); 99 | Future getMessage(dynamic message) async { 100 | if (this.client.user.bot) { 101 | final String id = Util.resolve('message', message); 102 | 103 | final HttpResponse r = await this 104 | .client 105 | .http 106 | .send('GET', '/channels/${this.id}/messages/$id'); 107 | return new Message._new( 108 | this.client, r.body.asJson() as Map); 109 | } else { 110 | throw new Exception("'getMessage' is only usable by bot accounts."); 111 | } 112 | } 113 | 114 | /// Gets several [Message] objects. 115 | /// 116 | /// Throws an [Exception] if the HTTP request errored. 117 | /// Channel.getMessages(limit: 100, after: "222078108977594368"); 118 | Future> getMessages({ 119 | int limit: 50, 120 | String after: null, 121 | String before: null, 122 | String around: null, 123 | }) async { 124 | Map query = {"limit": limit.toString()}; 125 | 126 | if (after != null) query['after'] = after; 127 | if (before != null) query['before'] = before; 128 | if (around != null) query['around'] = around; 129 | 130 | final HttpResponse r = await this 131 | .client 132 | .http 133 | .send('GET', '/channels/${this.id}/messages', queryParams: query); 134 | 135 | LinkedHashMap response = 136 | new LinkedHashMap(); 137 | 138 | for (Map val in r.body.asJson()) { 139 | response[val["id"]] = new Message._new(this.client, val); 140 | } 141 | 142 | return response; 143 | } 144 | 145 | /// Starts typing. 146 | Future startTyping() async { 147 | await this.client.http.send('POST', "/channels/$id/typing"); 148 | return null; 149 | } 150 | 151 | /// Loops `startTyping` until `stopTypingLoop` is called. 152 | void startTypingLoop() { 153 | startTyping(); 154 | this._typing = new Timer.periodic( 155 | const Duration(seconds: 7), (Timer t) => startTyping()); 156 | } 157 | 158 | /// Stops a typing loop if one is running. 159 | void stopTypingLoop() { 160 | this._typing?.cancel(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/src/main/objects/Embed.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A message embed. 4 | class Embed { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The embed's URL 12 | String url; 13 | 14 | /// The embed's type 15 | String type; 16 | 17 | /// The embed's description. 18 | String description; 19 | 20 | /// The embed's title. 21 | String title; 22 | 23 | /// The embed's thumbnail, if any. 24 | EmbedThumbnail thumbnail; 25 | 26 | /// The embed's provider, if any. 27 | EmbedProvider provider; 28 | 29 | Embed._new(this.client, this.raw) { 30 | this.url = raw['url']; 31 | this.type = raw['type']; 32 | this.description = raw['description']; 33 | 34 | if (raw.containsKey('thumbnail')) { 35 | this.thumbnail = new EmbedThumbnail._new( 36 | this.client, raw['thumbnail'] as Map); 37 | } 38 | if (raw.containsKey('provider')) { 39 | this.provider = new EmbedProvider._new( 40 | this.client, raw['provider'] as Map); 41 | } 42 | } 43 | 44 | /// Returns a string representation of this object. 45 | @override 46 | String toString() { 47 | return this.title; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/main/objects/EmbedProvider.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A message embed provider. 4 | class EmbedProvider { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The embed provider's name. 12 | String name; 13 | 14 | /// The embed provider's URL. 15 | String url; 16 | 17 | EmbedProvider._new(this.client, this.raw) { 18 | this.name = raw['name']; 19 | this.url = raw['url']; 20 | } 21 | 22 | /// Returns a string representation of this object. 23 | @override 24 | String toString() { 25 | return this.name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/main/objects/EmbedThumbnail.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A message embed thumbnail. 4 | class EmbedThumbnail { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The embed thumbnail's URL. 12 | String url; 13 | 14 | /// The embed thumbnal's proxy URL. 15 | String proxyUrl; 16 | 17 | /// The embed thumbnal's height. 18 | int height; 19 | 20 | /// The embed thumbnal's width. 21 | int width; 22 | 23 | EmbedThumbnail._new(this.client, this.raw) { 24 | this.url = raw['url']; 25 | this.proxyUrl = raw['proxy_url']; 26 | this.height = raw['height']; 27 | this.width = raw['width']; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/main/objects/Game.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A game. 4 | class Game { 5 | /// The raw object returned by the API 6 | Map raw; 7 | 8 | /// The game name. 9 | String name; 10 | 11 | /// The game type. 0 if not streamed, 1 if being streamed. 12 | int type; 13 | 14 | /// The game URL, if provided. 15 | String url; 16 | 17 | /// Makes a new game object. 18 | Game(this.name, {this.type: 0, this.url}) { 19 | this.raw = {"name": name, "type": type, "url": url}; 20 | } 21 | 22 | Game._new(Client client, this.raw) { 23 | try { 24 | this.name = raw['name'].toString(); 25 | } catch (err) { 26 | this.name = null; 27 | } 28 | 29 | try { 30 | this.url = raw['url'].toString(); 31 | } catch (err) { 32 | this.url = null; 33 | } 34 | 35 | if (raw['type'] is int) { 36 | this.type = raw['type']; 37 | } else if (raw['type'] is String) { 38 | try { 39 | this.type = int.parse(raw['type']); 40 | } catch (err) { 41 | this.type = null; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/main/objects/GroupDMChannel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A group DM channel. 4 | class GroupDMChannel extends Channel { 5 | Timer _typing; 6 | 7 | /// The ID for the last message in the channel. 8 | String lastMessageID; 9 | 10 | /// A collection of messages sent to this channel. 11 | LinkedHashMap messages; 12 | 13 | /// The recipients. 14 | Map recipients; 15 | 16 | GroupDMChannel._new(Client client, Map data) 17 | : super._new(client, data, "private") { 18 | this.lastMessageID = raw['last_message_id']; 19 | this.messages = new LinkedHashMap(); 20 | 21 | this.recipients = new Map(); 22 | raw['recipients'].forEach((Map o) { 23 | final User user = new User._new(client, o); 24 | this.recipients[user.id] = user; 25 | }); 26 | } 27 | 28 | void _cacheMessage(Message message) { 29 | if (this.client._options.messageCacheSize > 0) { 30 | if (this.messages.length >= this.client._options.messageCacheSize) { 31 | this.messages.values.toList().first._onUpdate.close(); 32 | this.messages.values.toList().first._onDelete.close(); 33 | this.messages.remove(this.messages.values.toList().first.id); 34 | } 35 | this.messages[message.id] = message; 36 | } 37 | } 38 | 39 | /// Sends a message. 40 | /// 41 | /// Throws an [Exception] if the HTTP request errored. 42 | /// Channel.send(content: "My content!"); 43 | Future send( 44 | {String content, 45 | Map embed, 46 | bool tts: false, 47 | String nonce, 48 | bool disableEveryone}) async { 49 | String newContent; 50 | if (content != null && 51 | (disableEveryone == true || 52 | (disableEveryone == null && 53 | this.client._options.disableEveryone))) { 54 | newContent = content 55 | .replaceAll("@everyone", "@\u200Beveryone") 56 | .replaceAll("@here", "@\u200Bhere"); 57 | } else { 58 | newContent = content; 59 | } 60 | 61 | final HttpResponse r = await this.client.http.send( 62 | 'POST', '/channels/${this.id}/messages', body: { 63 | "content": newContent, 64 | "tts": tts, 65 | "nonce": nonce, 66 | "embed": embed 67 | }); 68 | return new Message._new( 69 | this.client, r.body.asJson() as Map); 70 | } 71 | 72 | @deprecated 73 | 74 | /// Sends a message. 75 | /// 76 | /// Throws an [Exception] if the HTTP request errored. 77 | /// Channel.sendMessage(content: "My content!"); 78 | Future sendMessage( 79 | {String content, 80 | Map embed, 81 | bool tts: false, 82 | String nonce, 83 | bool disableEveryone}) async { 84 | return this.send( 85 | content: content, 86 | embed: embed, 87 | tts: tts, 88 | nonce: nonce, 89 | disableEveryone: disableEveryone); 90 | } 91 | 92 | /// Gets a [Message] object. Only usable by bot accounts. 93 | /// 94 | /// Throws an [Exception] if the HTTP request errored or if the client user 95 | /// is not a bot. 96 | /// Channel.getMessage("message id"); 97 | Future getMessage(dynamic message) async { 98 | if (this.client.user.bot) { 99 | final String id = Util.resolve('message', message); 100 | 101 | final HttpResponse r = await this 102 | .client 103 | .http 104 | .send('GET', '/channels/${this.id}/messages/$id'); 105 | return new Message._new( 106 | this.client, r.body.asJson() as Map); 107 | } else { 108 | throw new Exception("'getMessage' is only usable by bot accounts."); 109 | } 110 | } 111 | 112 | /// Gets several [Message] objects. 113 | /// 114 | /// Throws an [Exception] if the HTTP request errored. 115 | /// Channel.getMessages(limit: 100, after: "222078108977594368"); 116 | Future> getMessages({ 117 | int limit: 50, 118 | String after: null, 119 | String before: null, 120 | String around: null, 121 | }) async { 122 | Map query = {"limit": limit.toString()}; 123 | 124 | if (after != null) query['after'] = after; 125 | if (before != null) query['before'] = before; 126 | if (around != null) query['around'] = around; 127 | 128 | final HttpResponse r = await this 129 | .client 130 | .http 131 | .send('GET', '/channels/${this.id}/messages', queryParams: query); 132 | 133 | LinkedHashMap response = 134 | new LinkedHashMap(); 135 | 136 | for (Map val in r.body.asJson()) { 137 | response[val["id"]] = new Message._new(this.client, val); 138 | } 139 | 140 | return response; 141 | } 142 | 143 | /// Starts typing. 144 | Future startTyping() async { 145 | await this.client.http.send('POST', "/channels/$id/typing"); 146 | return null; 147 | } 148 | 149 | /// Loops `startTyping` until `stopTypingLoop` is called. 150 | void startTypingLoop() { 151 | startTyping(); 152 | this._typing = new Timer.periodic( 153 | const Duration(seconds: 7), (Timer t) => startTyping()); 154 | } 155 | 156 | /// Stops a typing loop if one is running. 157 | void stopTypingLoop() { 158 | this._typing?.cancel(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/src/main/objects/Guild.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A guild. 4 | class Guild { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The guild's name. 12 | String name; 13 | 14 | /// The guild's ID. 15 | String id; 16 | 17 | /// The guild's icon hash. 18 | String icon; 19 | 20 | /// The guild's afk channel ID, null if not set. 21 | VoiceChannel afkChannel; 22 | 23 | /// The guild's voice region. 24 | String region; 25 | 26 | /// The channel ID for the guild's widget if enabled. 27 | String embedChannelID; 28 | 29 | /// The guild's default channel. 30 | GuildChannel defaultChannel; 31 | 32 | /// The guild's AFK timeout. 33 | int afkTimeout; 34 | 35 | /// The guild's member count. 36 | int memberCount; 37 | 38 | /// The guild's verification level. 39 | int verificationLevel; 40 | 41 | /// The guild's notification level. 42 | int notificationLevel; 43 | 44 | /// The guild's MFA level. 45 | int mfaLevel; 46 | 47 | /// A timestamp for when the guild was created. 48 | DateTime createdAt; 49 | 50 | /// If the guild's widget is enabled. 51 | bool embedEnabled; 52 | 53 | /// Whether or not the guild is available. 54 | bool available; 55 | 56 | /// The guild owner's ID 57 | String ownerID; 58 | 59 | /// The guild's members. 60 | Map members; 61 | 62 | /// The guild's channels. 63 | Map channels; 64 | 65 | /// The guild's roles. 66 | Map roles; 67 | 68 | /// The shard that the guild is on. 69 | Shard shard; 70 | 71 | Guild._new(this.client, this.raw, 72 | [this.available = true, bool guildCreate = false]) { 73 | if (this.available) { 74 | this.name = raw['name']; 75 | this.id = raw['id']; 76 | this.icon = raw['icon']; 77 | this.region = raw['region']; 78 | this.embedChannelID = raw['embed_channel_id']; 79 | this.afkTimeout = raw['afk_timeout']; 80 | this.memberCount = raw['member_count']; 81 | this.verificationLevel = raw['verification_level']; 82 | this.notificationLevel = raw['default_message_notifications']; 83 | this.mfaLevel = raw['mfa_level']; 84 | this.embedEnabled = raw['embed_enabled']; 85 | this.ownerID = raw['owner_id']; 86 | this.createdAt = Util.getDate(this.id); 87 | 88 | this.roles = new Map(); 89 | raw['roles'].forEach((Map o) { 90 | new Role._new(this.client, o, this); 91 | }); 92 | 93 | this.shard = this 94 | .client 95 | .shards[(int.parse(this.id) >> 22) % this.client._options.shardCount]; 96 | 97 | if (guildCreate) { 98 | this.members = new Map(); 99 | this.channels = new Map(); 100 | 101 | raw['members'].forEach((Map o) { 102 | new Member._new(client, o, this); 103 | }); 104 | 105 | raw['channels'].forEach((Map o) { 106 | if (o['type'] == 0) { 107 | new TextChannel._new(client, o, this); 108 | } else { 109 | new VoiceChannel._new(client, o, this); 110 | } 111 | }); 112 | 113 | raw['presences'].forEach((Map o) { 114 | Member member = this.members[o['user']['id']]; 115 | if (member != null) { 116 | member.status = o['status']; 117 | if (o['game'] != null) { 118 | member.game = 119 | new Game._new(client, o['game'] as Map); 120 | } 121 | } 122 | }); 123 | 124 | this.defaultChannel = this.channels[this.id]; 125 | this.afkChannel = this.channels[raw['afk_channel_id']]; 126 | } 127 | 128 | client.guilds[this.id] = this; 129 | shard.guilds[this.id] = this; 130 | } 131 | } 132 | 133 | /// The guild's icon, represented as URL. 134 | String iconURL({String format: 'webp', int size: 128}) { 135 | return 'https://cdn.${_Constants.host}/icons/${this.id}/${this.icon}.$format?size=$size'; 136 | } 137 | 138 | /// Returns a string representation of this object. 139 | @override 140 | String toString() { 141 | return this.name; 142 | } 143 | 144 | /// Prunes the guild, returns the amount of members pruned. 145 | Future prune(int days) async { 146 | HttpResponse r = await this 147 | .client 148 | .http 149 | .send('POST', "/guilds/$id/prune", body: {"days": days}); 150 | return r.body.asJson() as int; 151 | } 152 | 153 | /// Get's the guild's bans. 154 | Future> getBans() async { 155 | HttpResponse r = await this.client.http.send('GET', "/guilds/$id/bans"); 156 | Map map = {}; 157 | r.body.asJson().forEach((Map o) { 158 | final User user = 159 | new User._new(client, o['user'] as Map); 160 | map[user.id] = user; 161 | }); 162 | return map; 163 | } 164 | 165 | /// Leaves the guild. 166 | Future leave() async { 167 | await this.client.http.send('DELETE', "/users/@me/guilds/$id"); 168 | return null; 169 | } 170 | 171 | /// Creates an empty role. 172 | Future createRole() async { 173 | HttpResponse r = await this.client.http.send('POST', "/guilds/$id/roles"); 174 | return new Role._new(client, r.body.asJson() as Map, this); 175 | } 176 | 177 | /// Creates a channel. 178 | Future createChannel(String name, String type, 179 | {int bitrate: 64000, int userLimit: 0}) async { 180 | HttpResponse r = await this.client.http.send('POST', "/guilds/$id/channels", 181 | body: { 182 | "name": name, 183 | "type": type, 184 | "bitrate": bitrate, 185 | "user_limit": userLimit 186 | }); 187 | 188 | if (r.body.asJson()['type'] == 0) { 189 | return new TextChannel._new( 190 | client, r.body.asJson() as Map, this); 191 | } else { 192 | return new VoiceChannel._new( 193 | client, r.body.asJson() as Map, this); 194 | } 195 | } 196 | 197 | /// Bans a user by ID. 198 | Future ban(String id, [int deleteMessageDays = 0]) async { 199 | await this.client.http.send('PUT', "/guilds/${this.id}/bans/$id", 200 | body: {"delete-message-days": deleteMessageDays}); 201 | return null; 202 | } 203 | 204 | /// Unbans a user by ID. 205 | Future unban(String id) async { 206 | await this.client.http.send('DELETE', "/guilds/${this.id}/bans/$id"); 207 | return null; 208 | } 209 | 210 | /// Edits the guild. 211 | Future edit( 212 | {String name: null, 213 | int verificationLevel: null, 214 | int notificationLevel: null, 215 | VoiceChannel afkChannel: null, 216 | int afkTimeout: null, 217 | String icon: null}) async { 218 | HttpResponse r = 219 | await this.client.http.send('PATCH', "/guilds/${this.id}", body: { 220 | "name": name != null ? name : this.name, 221 | "verification_level": verificationLevel != null 222 | ? verificationLevel 223 | : this.verificationLevel, 224 | "default_message_notifications": notificationLevel != null 225 | ? notificationLevel 226 | : this.notificationLevel, 227 | "afk_channel_id": afkChannel != null ? afkChannel : this.afkChannel, 228 | "afk_timeout": afkTimeout != null ? afkTimeout : this.afkTimeout, 229 | "icon": icon != null ? icon : this.icon 230 | }); 231 | return new Guild._new(this.client, r.body.asJson() as Map); 232 | } 233 | 234 | /// Gets a [Member] object. Adds it to `Guild.members` if 235 | /// not already there. 236 | /// 237 | /// Throws an [Exception] if the HTTP request errored. 238 | /// Guild.getMember("user id"); 239 | Future getMember(dynamic member) async { 240 | final String id = Util.resolve('member', member); 241 | 242 | if (this.members[member] != null) { 243 | return this.members[member]; 244 | } else { 245 | final HttpResponse r = 246 | await this.client.http.send('GET', '/guilds/${this.id}/members/$id'); 247 | 248 | final Member m = new Member._new( 249 | this.client, r.body.asJson() as Map, this); 250 | return m; 251 | } 252 | } 253 | 254 | /// Invites a bot to a guild. Only usable by user accounts. 255 | /// 256 | /// Throws an [Exception] if the HTTP request errored or if the client user 257 | /// is a bot. 258 | /// Guild.oauth2Authorize("app id"); 259 | Future oauth2Authorize(dynamic app, [int permissions = 0]) async { 260 | if (!this.client.user.bot) { 261 | final String id = Util.resolve('app', app); 262 | 263 | await this.client.http.send( 264 | 'POST', '/oauth2/authorize?client_id=$id&scope=bot', 265 | body: { 266 | "guild_id": this.id, 267 | "permissions": permissions, 268 | "authorize": true 269 | }); 270 | 271 | return null; 272 | } else { 273 | throw new Exception("'oauth2Authorize' is only usable by user accounts."); 274 | } 275 | } 276 | 277 | /// Gets all of the webhooks for this guild. 278 | Future> getWebhooks() async { 279 | HttpResponse r = await this.client.http.send('GET', "/guilds/$id/webhooks"); 280 | Map map = {}; 281 | r.body.asJson().forEach((Map o) { 282 | Webhook webhook = new Webhook._fromApi(this.client, o); 283 | map[webhook.id] = webhook; 284 | }); 285 | return map; 286 | } 287 | 288 | /// Deletes the guild. 289 | Future delete() async { 290 | await this.client.http.send('DELETE', "/guilds/${this.id}"); 291 | return null; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /lib/src/main/objects/GuildChannel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A guild channel. 4 | class GuildChannel extends Channel { 5 | /// The channel's name. 6 | String name; 7 | 8 | /// The guild that the channel is in. 9 | Guild guild; 10 | 11 | /// The channel's position in the channel list. 12 | int position; 13 | 14 | GuildChannel._new( 15 | Client client, Map data, this.guild, String type) 16 | : super._new(client, data, type) { 17 | this.name = raw['name']; 18 | this.position = raw['position']; 19 | 20 | this.guild.channels[this.id] = this; 21 | } 22 | 23 | /// Returns a string representation of this object. 24 | @override 25 | String toString() { 26 | return this.name; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/main/objects/Invite.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An invite. 4 | class Invite { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The invite's code. 12 | String code; 13 | 14 | /// A mini guild object for the invite's guild. 15 | InviteGuild guild; 16 | 17 | /// A mini channel object for the invite's channel. 18 | InviteChannel channel; 19 | 20 | Invite._new(this.client, this.raw) { 21 | this.code = raw['code']; 22 | this.guild = 23 | new InviteGuild._new(this.client, raw['guild'] as Map); 24 | this.channel = new InviteChannel._new( 25 | this.client, raw['channel'] as Map); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/main/objects/InviteChannel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A mini channel object for invites. 4 | class InviteChannel { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The channel's ID. 12 | String id; 13 | 14 | /// The channel's name. 15 | String name; 16 | 17 | /// The channel's type. 18 | String type; 19 | 20 | /// A timestamp for the channel was created. 21 | DateTime createdAt; 22 | 23 | InviteChannel._new(this.client, this.raw) { 24 | this.id = raw['id']; 25 | this.name = raw['name']; 26 | this.type = raw['type']; 27 | this.createdAt = Util.getDate(this.id); 28 | } 29 | 30 | /// Returns a string representation of this object. 31 | @override 32 | String toString() { 33 | return this.name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/main/objects/InviteGuild.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A mini guild object for invites. 4 | class InviteGuild { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The guild's ID. 12 | String id; 13 | 14 | /// The guild's name. 15 | String name; 16 | 17 | /// The guild's spash if any. 18 | String spash; 19 | 20 | /// A timestamp for when the guild was created. 21 | DateTime createdAt; 22 | 23 | InviteGuild._new(this.client, this.raw) { 24 | this.id = raw['id']; 25 | this.name = raw['name']; 26 | this.spash = raw['splash_hash']; 27 | this.createdAt = Util.getDate(this.id); 28 | } 29 | 30 | /// Returns a string representation of this object. 31 | @override 32 | String toString() { 33 | return this.name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/main/objects/Member.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A guild member. 4 | class Member extends User { 5 | User _user; 6 | 7 | /// The member's nickname, null if not set. 8 | String nickname; 9 | 10 | /// The member's status, `offline`, `online`, `idle`, or `dnd`. 11 | String status; 12 | 13 | /// When the member joined the guild. 14 | DateTime joinedAt; 15 | 16 | /// Weather or not the member is deafened. 17 | bool deaf; 18 | 19 | /// Weather or not the member is muted. 20 | bool mute; 21 | 22 | /// The member's game. 23 | Game game; 24 | 25 | /// A list of role IDs the member has. 26 | List roles; 27 | 28 | /// The guild that the member is a part of. 29 | Guild guild; 30 | 31 | Member._new(Client client, Map data, [Guild guild]) 32 | : super._new(client, data['user'] as Map) { 33 | this.nickname = data['nick']; 34 | this.deaf = data['deaf']; 35 | this.mute = data['mute']; 36 | this.status = data['status']; 37 | this.roles = data['roles'] as List; 38 | this._user = new User._new(client, data['user'] as Map); 39 | 40 | if (guild == null) { 41 | this.guild = this.client.guilds[data['guild_id']]; 42 | } else { 43 | this.guild = guild; 44 | } 45 | 46 | if (data['joined_at'] != null) { 47 | this.joinedAt = DateTime.parse(data['joined_at']); 48 | } 49 | 50 | if (data['game'] != null) { 51 | this.game = 52 | new Game._new(this.client, data['game'] as Map); 53 | } 54 | 55 | if (guild != null) this.guild.members[this.id] = this; 56 | client.users[this.toUser().id] = this.toUser(); 57 | } 58 | 59 | /// Returns a user from the member. 60 | User toUser() { 61 | return this._user; 62 | } 63 | 64 | /// Bans the member and optionally deletes [deleteMessageDays] days worth of messages. 65 | Future ban([int deleteMessageDays = 0]) async { 66 | await this.client.http.send( 67 | 'PUT', "/guilds/${this.guild.id}/bans/${this.id}", 68 | body: {"delete-message-days": deleteMessageDays}); 69 | return null; 70 | } 71 | 72 | /// Kicks the member 73 | Future kick() async { 74 | await this 75 | .client 76 | .http 77 | .send('DELETE', "/guilds/${this.guild.id}/members/${this.id}"); 78 | return null; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/main/objects/Message.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A message. 4 | class Message { 5 | StreamController _onUpdate; 6 | StreamController _onDelete; 7 | 8 | /// The [Client] object. 9 | Client client; 10 | 11 | /// The raw object returned by the API. 12 | Map raw; 13 | 14 | /// The message's content. 15 | String content; 16 | 17 | /// The message's ID. 18 | String id; 19 | 20 | /// The message's nonce, null if not set. 21 | String nonce; 22 | 23 | /// The timestamp of when the message was created. 24 | DateTime timestamp; 25 | 26 | /// The timestamp of when the message was last edited, null if not edited. 27 | DateTime editedTimestamp; 28 | 29 | /// The message's channel. 30 | dynamic channel; 31 | 32 | /// The message's guild. 33 | Guild guild; 34 | 35 | /// The message's author. 36 | User author; 37 | 38 | /// The message's author in a member form. Very rarely can be null if `forceFetchMembers` is disabled. 39 | Member member; 40 | 41 | /// The mentions in the message. 42 | Map mentions; 43 | 44 | /// A list of IDs for the role mentions in the message. 45 | Map roleMentions; 46 | 47 | /// A collection of the embeds in the message. 48 | Map embeds; 49 | 50 | /// The attachments in the message. 51 | Map attachments; 52 | 53 | /// When the message was created, redundant of `timestamp`. 54 | DateTime createdAt; 55 | 56 | /// Whether or not the message is pinned. 57 | bool pinned; 58 | 59 | /// Whether or not the message was sent with TTS enabled. 60 | bool tts; 61 | 62 | /// Whether or @everyone was mentioned in the message. 63 | bool mentionEveryone; 64 | 65 | /// Emitted when the message is edited, if it is in the channel cache. 66 | Stream onUpdate; 67 | 68 | /// Emitted when the message is deleted, if it is in the channel cache. 69 | Stream onDelete; 70 | 71 | Message._new(this.client, this.raw) { 72 | this._onUpdate = new StreamController.broadcast(); 73 | this.onUpdate = this._onUpdate.stream; 74 | 75 | this._onDelete = new StreamController.broadcast(); 76 | this.onDelete = this._onDelete.stream; 77 | 78 | this.content = raw['content']; 79 | this.id = raw['id']; 80 | this.nonce = raw['nonce']; 81 | this.timestamp = DateTime.parse(raw['timestamp']); 82 | this.author = 83 | new User._new(this.client, raw['author'] as Map); 84 | this.channel = this.client.channels[raw['channel_id']]; 85 | this.pinned = raw['pinned']; 86 | this.tts = raw['tts']; 87 | this.mentionEveryone = raw['mention_everyone']; 88 | this.createdAt = Util.getDate(this.id); 89 | 90 | this.channel._cacheMessage(this); 91 | this.channel.lastMessageID = this.id; 92 | 93 | if (this.channel is GuildChannel) { 94 | this.guild = this.channel.guild; 95 | this.member = guild.members[this.author.id]; 96 | 97 | this.roleMentions = new Map(); 98 | raw['mention_roles'].forEach((String o) { 99 | this.roleMentions[guild.roles[o].id] = guild.roles[o]; 100 | }); 101 | } 102 | 103 | if (raw['edited_timestamp'] != null) { 104 | this.editedTimestamp = DateTime.parse(raw['edited_timestamp']); 105 | } 106 | 107 | this.mentions = new Map(); 108 | raw['mentions'].forEach((Map o) { 109 | final User user = new User._new(this.client, o); 110 | this.mentions[user.id] = user; 111 | }); 112 | this.mentions; 113 | 114 | this.embeds = new Map(); 115 | raw['embeds'].forEach((Map o) { 116 | Embed embed = new Embed._new(this.client, o); 117 | this.embeds[embed.url] = embed; 118 | }); 119 | this.embeds; 120 | 121 | this.attachments = new Map(); 122 | raw['attachments'].forEach((Map o) { 123 | final Attachment attachment = new Attachment._new(this.client, o); 124 | this.attachments[attachment.id] = attachment; 125 | }); 126 | this.attachments; 127 | } 128 | 129 | /// Returns a string representation of this object. 130 | @override 131 | String toString() { 132 | return this.content; 133 | } 134 | 135 | /// Edits the message. 136 | /// 137 | /// Throws an [Exception] if the HTTP request errored. 138 | /// Message.edit("My edited content!"); 139 | Future edit( 140 | {String content, 141 | Map embed, 142 | bool tts: false, 143 | String nonce, 144 | bool disableEveryone}) async { 145 | String newContent; 146 | if (content != null && 147 | (disableEveryone == true || 148 | (disableEveryone == null && 149 | this.client._options.disableEveryone))) { 150 | newContent = content 151 | .replaceAll("@everyone", "@\u200Beveryone") 152 | .replaceAll("@here", "@\u200Bhere"); 153 | } else { 154 | newContent = content; 155 | } 156 | 157 | final HttpResponse r = await this.client.http.send( 158 | 'PATCH', '/channels/${this.channel.id}/messages/${this.id}', 159 | body: {"content": newContent, "embed": embed}); 160 | return new Message._new( 161 | this.client, r.body.asJson() as Map); 162 | } 163 | 164 | /// Deletes the message. 165 | /// 166 | /// Throws an [Exception] if the HTTP request errored. 167 | /// Message.delete(); 168 | Future delete() async { 169 | await this 170 | .client 171 | .http 172 | .send('DELETE', '/channels/${this.channel.id}/messages/${this.id}'); 173 | return null; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/src/main/objects/OAuth2Application.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An OAuth2 application. 4 | class OAuth2Application { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The app's description. 12 | String description; 13 | 14 | /// The app's icon hash. 15 | String icon; 16 | 17 | /// The app's ID. 18 | String id; 19 | 20 | /// The app's name. 21 | String name; 22 | 23 | /// The app's RPC origins. 24 | List rpcOrigins; 25 | 26 | /// A timestamp for when the app was created. 27 | DateTime createdAt; 28 | 29 | OAuth2Application._new(this.client, this.raw) { 30 | this.description = raw['description']; 31 | this.icon = raw['icon']; 32 | this.id = raw['id']; 33 | this.name = raw['name']; 34 | this.rpcOrigins = raw['rpcOrigins'] as List; 35 | this.createdAt = Util.getDate(this.id); 36 | } 37 | 38 | /// Returns a string representation of this object. 39 | @override 40 | String toString() { 41 | return this.name; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/main/objects/OAuth2Guild.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A mini guild object with permissions for [OAuth2Info]. 4 | class OAuth2Guild { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The permissions you have on that guild. 12 | Permissions permissions; 13 | 14 | /// The guild's icon hash. 15 | String icon; 16 | 17 | /// The guild's ID. 18 | String id; 19 | 20 | /// The guild's name 21 | String name; 22 | 23 | /// A timestamp for when the guild was created. 24 | DateTime createdAt; 25 | 26 | OAuth2Guild._new(this.client, this.raw) { 27 | this.permissions = new Permissions.fromInt(client, raw['permissions']); 28 | this.icon = raw['icon']; 29 | this.id = raw['id']; 30 | this.name = raw['name']; 31 | this.createdAt = Util.getDate(this.id); 32 | } 33 | 34 | /// Returns a string representation of this object. 35 | @override 36 | String toString() { 37 | return this.name; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/main/objects/OAuth2Info.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Info about a OAuth2 app, bot, user, and possible guilds that that bot can 4 | /// be invited to. 5 | class OAuth2Info { 6 | /// The [Client] object. 7 | Client client; 8 | 9 | /// The raw object returned by the API 10 | Map raw; 11 | 12 | /// The OAuth2 app. 13 | OAuth2Application app; 14 | 15 | /// The app's bot. 16 | User bot; 17 | 18 | /// You. 19 | User me; 20 | 21 | /// Mini guild objects with permissions for every guild you are on. 22 | Map guilds; 23 | 24 | OAuth2Info._new(this.client, this.raw) { 25 | this.app = new OAuth2Application._new( 26 | this.client, raw['application'] as Map); 27 | this.bot = new User._new(client, raw['bot'] as Map); 28 | this.me = new User._new(client, raw['user'] as Map); 29 | 30 | this.guilds = new Map(); 31 | raw['guilds'].forEach((Map v) { 32 | final OAuth2Guild g = new OAuth2Guild._new(this.client, v); 33 | this.guilds[g.id] = g; 34 | }); 35 | this.guilds; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/main/objects/Permissions.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// Permissions for a role or channel override. 4 | class Permissions { 5 | Client _client; 6 | 7 | /// The raw permission code. 8 | int raw; 9 | 10 | bool createInstantInvite; 11 | bool kickMembers; 12 | bool banMembers; 13 | bool administrator; 14 | bool manageChannels; 15 | bool manageGuild; 16 | bool readMessages; 17 | bool sendMessages; 18 | bool sendTtsMessages; 19 | bool manageMessages; 20 | bool embedLinks; 21 | bool attachFiles; 22 | bool readMessageHistory; 23 | bool mentionEveryone; 24 | bool useExternalEmojis; 25 | bool connect; 26 | bool speak; 27 | bool muteMembers; 28 | bool deafenMembers; 29 | bool moveMembers; 30 | bool useVad; 31 | bool changeNickname; 32 | bool manageNicknames; 33 | bool manageRoles; 34 | bool manageWebhooks; 35 | 36 | /// Makes a [Permissions] object from a raw permission code. 37 | Permissions.fromInt(this._client, int permissions) { 38 | this.raw = permissions; 39 | this.createInstantInvite = 40 | (this.raw & _Constants.permissions['CREATE_INSTANT_INVITE']) > 0; 41 | this.kickMembers = (this.raw & _Constants.permissions['KICK_MEMBERS']) > 0; 42 | this.banMembers = (this.raw & _Constants.permissions['BAN_MEMBERS']) > 0; 43 | this.administrator = 44 | (this.raw & _Constants.permissions['ADMINISTRATOR']) > 0; 45 | this.manageChannels = 46 | (this.raw & _Constants.permissions['MANAGE_CHANNELS']) > 0; 47 | this.manageGuild = (this.raw & _Constants.permissions['MANAGE_GUILD']) > 0; 48 | this.readMessages = 49 | (this.raw & _Constants.permissions['READ_MESSAGES']) > 0; 50 | this.sendMessages = 51 | (this.raw & _Constants.permissions['SEND_MESSAGES']) > 0; 52 | this.sendTtsMessages = 53 | (this.raw & _Constants.permissions['SEND_TTS_MESSAGES']) > 0; 54 | this.manageMessages = 55 | (this.raw & _Constants.permissions['MANAGE_MESSAGES']) > 0; 56 | this.embedLinks = (this.raw & _Constants.permissions['EMBED_LINKS']) > 0; 57 | this.attachFiles = (this.raw & _Constants.permissions['ATTACH_FILES']) > 0; 58 | this.readMessageHistory = 59 | (this.raw & _Constants.permissions['READ_MESSAGE_HISTORY']) > 0; 60 | this.mentionEveryone = 61 | (this.raw & _Constants.permissions['MENTION_EVERYONE']) > 0; 62 | this.useExternalEmojis = 63 | (this.raw & _Constants.permissions['EXTERNAL_EMOJIS']) > 0; 64 | this.connect = (this.raw & _Constants.permissions['CONNECT']) > 0; 65 | this.speak = (this.raw & _Constants.permissions['SPEAK']) > 0; 66 | this.muteMembers = (this.raw & _Constants.permissions['MUTE_MEMBERS']) > 0; 67 | this.deafenMembers = 68 | (this.raw & _Constants.permissions['DEAFEN_MEMBERS']) > 0; 69 | this.moveMembers = (this.raw & _Constants.permissions['MOVE_MEMBERS']) > 0; 70 | this.useVad = (this.raw & _Constants.permissions['USE_VAD']) > 0; 71 | this.changeNickname = 72 | (this.raw & _Constants.permissions['CHANGE_NICKNAME']) > 0; 73 | this.manageNicknames = 74 | (this.raw & _Constants.permissions['MANAGE_NICKNAMES']) > 0; 75 | this.manageRoles = 76 | (this.raw & _Constants.permissions['MANAGE_ROLES_OR_PERMISSIONS']) > 0; 77 | this.manageWebhooks = 78 | (this.raw & _Constants.permissions['MANAGE_WEBHOOKS']) > 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/main/objects/Role.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A role. 4 | class Role { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The role's name. 12 | String name; 13 | 14 | /// The role's ID. 15 | String id; 16 | 17 | /// The role's color, null if no color. 18 | int color; 19 | 20 | /// The role's position. 21 | int position; 22 | 23 | /// If the role is pinned in the user listing. 24 | bool hoist; 25 | 26 | /// Whether or not the role is managed by an integration. 27 | bool managed; 28 | 29 | /// Whether or not the role is mentionable. 30 | bool mentionable; 31 | 32 | /// The role's guild. 33 | Guild guild; 34 | 35 | /// The role's permissions. 36 | Permissions permissions; 37 | 38 | /// A timestamp for when the channel was created. 39 | DateTime createdAt; 40 | 41 | Role._new(this.client, this.raw, this.guild) { 42 | this.id = raw['id']; 43 | this.name = raw['name']; 44 | this.position = raw['position']; 45 | this.hoist = raw['hoist']; 46 | this.managed = raw['managed']; 47 | this.mentionable = raw['mentionable']; 48 | this.permissions = new Permissions.fromInt(this.client, raw['permissions']); 49 | this.createdAt = Util.getDate(this.id); 50 | 51 | if (raw['color'] == 0) { 52 | this.color = null; 53 | } else { 54 | this.color = raw['color']; 55 | } 56 | 57 | this.guild.roles[this.id] = this; 58 | } 59 | 60 | /// Edits the role. 61 | Future edit( 62 | {String name: null, 63 | int permissions: null, 64 | int position: null, 65 | int color: null, 66 | bool mentionable: null, 67 | bool hoist: null}) async { 68 | HttpResponse r = await this 69 | .client 70 | .http 71 | .send('PATCH', "/guilds/${this.guild.id}/roles/$id", body: { 72 | "name": name != null ? name : this.name, 73 | "permissions": permissions != null ? permissions : this.permissions.raw, 74 | "position": position != null ? position : this.position, 75 | "color": color != null ? color : this.color, 76 | "hoist": hoist != null ? hoist : this.hoist, 77 | "mentionable": mentionable != null ? mentionable : this.mentionable 78 | }); 79 | return new Role._new( 80 | this.client, r.body.asJson() as Map, this.guild); 81 | } 82 | 83 | /// Deletes the role. 84 | Future delete() async { 85 | await this.client.http.send('DELETE', "/guilds/${this.guild.id}/roles/$id"); 86 | return null; 87 | } 88 | 89 | /// Returns a string representation of this object. 90 | @override 91 | String toString() => this.name; 92 | } 93 | -------------------------------------------------------------------------------- /lib/src/main/objects/Shard.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// An internal shard. 4 | class Shard { 5 | /// The shard id. 6 | int id; 7 | 8 | /// Whether or not the shard is ready. 9 | bool ready; 10 | 11 | Timer _heartbeatTimer; 12 | _WS _ws; 13 | w_transport.WebSocket _socket; 14 | int _sequence; 15 | String _sessionId; 16 | StreamController _onReady; 17 | StreamController _onDisconnect; 18 | 19 | /// Emitted when the shard is ready. 20 | Stream onReady; 21 | 22 | /// Emitted when the shard encounters an error. 23 | Stream onDisconnect; 24 | 25 | /// A map of guilds the shard is on. 26 | Map guilds = {}; 27 | 28 | Shard._new(_WS ws, this.id) { 29 | this._ws = ws; 30 | this._onReady = new StreamController.broadcast(); 31 | this.onReady = this._onReady.stream; 32 | 33 | this._onDisconnect = new StreamController.broadcast(); 34 | this.onDisconnect = this._onDisconnect.stream; 35 | } 36 | 37 | /// Updates the presence for this shard. 38 | void setPresence({String status: null, bool afk: false, activity: null}) { 39 | if (activity == null) 40 | activity = { 41 | 'name': null, 42 | 'type': 0, 43 | 'url': null, 44 | }; 45 | 46 | Map packet = { 47 | 'afk': afk, 48 | 'since': null, 49 | 'status': status, 50 | 'game': { 51 | 'name': activity != null ? activity['name'] : null, 52 | 'type': activity != null ? activity['type'] : 0, 53 | 'url': activity != null ? activity['url'] : null 54 | } 55 | }; 56 | 57 | this.send("STATUS_UPDATE", packet); 58 | } 59 | 60 | /// Syncs all guilds; this is automatically called every 30 seconds. 61 | /// Users only. 62 | void guildSync() { 63 | this.send("GUILD_SYNC", this.guilds.keys.toList()); 64 | } 65 | 66 | void _connect([bool resume = true, bool init = false]) { 67 | this.ready = false; 68 | if (this._socket != null) this._socket.close(); 69 | if (!init) { 70 | new Timer(new Duration(seconds: 2), () => _connect(resume)); 71 | return; 72 | } 73 | w_transport.WebSocket 74 | .connect(Uri.parse('${this._ws.gateway}?v=6&encoding=json')) 75 | .then((w_transport.WebSocket socket) { 76 | this._socket = socket; 77 | this._socket.listen((String msg) => this._handleMsg(msg, resume), 78 | onDone: this._handleErr); 79 | }); 80 | } 81 | 82 | /// Sends WS data. 83 | void send(String op, dynamic d) { 84 | this._socket.add( 85 | JSON.encode({"op": _Constants.opCodes[op], "d": d})); 86 | } 87 | 88 | void _heartbeat() { 89 | if (this._socket.closeCode != null) return; 90 | this.send("HEARTBEAT", _sequence); 91 | } 92 | 93 | Future _handleMsg(String msg, bool resume) async { 94 | final json = JSON.decode(msg) as Map; 95 | 96 | new RawEvent._new(this._ws.client, this, json); 97 | 98 | if (json['s'] != null) { 99 | this._sequence = json['s']; 100 | } 101 | 102 | switch (json['op']) { 103 | case _OPCodes.hello: 104 | if (this._sessionId == null || !resume) { 105 | Map identifyMsg = { 106 | "token": this._ws.client._token, 107 | "properties": { 108 | "\$os": internals.operatingSystem, 109 | "\$browser": "nyx", 110 | "\$device": "nyx", 111 | "\$referrer": "", 112 | "\$referring_domain": "" 113 | }, 114 | "large_threshold": 100, 115 | "compress": false 116 | }; 117 | if (this._ws.bot) 118 | identifyMsg['shard'] = [ 119 | this.id, 120 | this._ws.client._options.shardCount 121 | ]; 122 | this.send("IDENTIFY", identifyMsg); 123 | } else if (resume) { 124 | this.send("RESUME", { 125 | "token": this._ws.client._token, 126 | "session_id": this._sessionId, 127 | "seq": this._sequence 128 | }); 129 | } 130 | 131 | this._heartbeatTimer = new Timer.periodic( 132 | new Duration(milliseconds: json['d']['heartbeat_interval']), 133 | (Timer t) => this._heartbeat()); 134 | break; 135 | 136 | case _OPCodes.invalidSession: 137 | this._connect(false); 138 | break; 139 | 140 | case _OPCodes.dispatch: 141 | if (this._ws.client._options.disabledEvents.contains(json['t'])) break; 142 | 143 | switch (json['t']) { 144 | case 'READY': 145 | this._sessionId = json['d']['session_id']; 146 | 147 | this._ws.client.user = new ClientUser._new( 148 | this._ws.client, json['d']['user'] as Map); 149 | 150 | if (this._ws.client.user.bot) { 151 | this._ws.client.http.headers['Authorization'] = 152 | "Bot ${this._ws.client._token}"; 153 | } else { 154 | this._ws.client.http.headers['Authorization'] = 155 | this._ws.client._token; 156 | this._ws.client._options.forceFetchMembers = false; 157 | new Timer.periodic( 158 | new Duration(seconds: 30), (Timer t) => guildSync()); 159 | } 160 | 161 | if (!internals.browser) 162 | this._ws.client.http.headers['User-Agent'] = 163 | "${this._ws.client.user.username} (https://github.com/Hackzzila/nyx, ${_Constants.version})"; 164 | 165 | json['d']['guilds'].forEach((Map o) { 166 | if (this._ws.client.user.bot) 167 | this._ws.client.guilds[o['id']] = null; 168 | else 169 | this._ws.client.guilds[o['id']] = 170 | new Guild._new(this._ws.client, o, true, true); 171 | }); 172 | 173 | json['d']['private_channels'].forEach((Map o) { 174 | if (o['type'] == 1) { 175 | new DMChannel._new(this._ws.client, o); 176 | } else { 177 | new GroupDMChannel._new(this._ws.client, o); 178 | } 179 | }); 180 | 181 | this.ready = true; 182 | this._onReady.add(this); 183 | 184 | if (!this._ws.client.user.bot) { 185 | this._ws.client.ready = true; 186 | this._ws.client._startTime = new DateTime.now(); 187 | new ReadyEvent._new(this._ws.client); 188 | } 189 | 190 | break; 191 | 192 | case 'GUILD_MEMBERS_CHUNK': 193 | json['d']['members'].forEach((Map o) { 194 | new Member._new(this._ws.client, o, 195 | this._ws.client.guilds[json['d']['guild_id']]); 196 | }); 197 | if (!_ws.client.ready) { 198 | _ws.testReady(); 199 | } 200 | break; 201 | 202 | case 'MESSAGE_CREATE': 203 | new MessageEvent._new(this._ws.client, json); 204 | break; 205 | 206 | case 'MESSAGE_DELETE': 207 | new MessageDeleteEvent._new(this._ws.client, json); 208 | break; 209 | 210 | case 'MESSAGE_UPDATE': 211 | new MessageUpdateEvent._new(this._ws.client, json); 212 | break; 213 | 214 | case 'GUILD_CREATE': 215 | new GuildCreateEvent._new(this._ws.client, json, this); 216 | break; 217 | 218 | case 'GUILD_UPDATE': 219 | new GuildUpdateEvent._new(this._ws.client, json); 220 | break; 221 | 222 | case 'GUILD_DELETE': 223 | if (json['d']['unavailable'] == true) { 224 | new GuildUnavailableEvent._new(this._ws.client, json); 225 | } else { 226 | new GuildDeleteEvent._new(this._ws.client, json, this); 227 | } 228 | break; 229 | 230 | case 'GUILD_BAN_ADD': 231 | new GuildBanAddEvent._new(this._ws.client, json); 232 | break; 233 | 234 | case 'GUILD_BAN_REMOVE': 235 | new GuildBanRemoveEvent._new(this._ws.client, json); 236 | break; 237 | 238 | case 'GUILD_MEMBER_ADD': 239 | new GuildMemberAddEvent._new(this._ws.client, json); 240 | break; 241 | 242 | case 'GUILD_MEMBER_REMOVE': 243 | new GuildMemberRemoveEvent._new(this._ws.client, json); 244 | break; 245 | 246 | case 'GUILD_MEMBER_UPDATE': 247 | new GuildMemberUpdateEvent._new(this._ws.client, json); 248 | break; 249 | 250 | case 'CHANNEL_CREATE': 251 | new ChannelCreateEvent._new(this._ws.client, json); 252 | break; 253 | 254 | case 'CHANNEL_UPDATE': 255 | new ChannelUpdateEvent._new(this._ws.client, json); 256 | break; 257 | 258 | case 'CHANNEL_DELETE': 259 | new ChannelDeleteEvent._new(this._ws.client, json); 260 | break; 261 | 262 | case 'TYPING_START': 263 | new TypingEvent._new(this._ws.client, json); 264 | break; 265 | 266 | case 'PRESENCE_UPDATE': 267 | new PresenceUpdateEvent._new(this._ws.client, json); 268 | break; 269 | 270 | case 'GUILD_ROLE_CREATE': 271 | new RoleCreateEvent._new(this._ws.client, json); 272 | break; 273 | 274 | case 'GUILD_ROLE_UPDATE': 275 | new RoleUpdateEvent._new(this._ws.client, json); 276 | break; 277 | 278 | case 'GUILD_ROLE_DELETE': 279 | new RoleDeleteEvent._new(this._ws.client, json); 280 | break; 281 | } 282 | break; 283 | } 284 | 285 | return null; 286 | } 287 | 288 | void _handleErr() { 289 | this._heartbeatTimer.cancel(); 290 | 291 | switch (this._socket.closeCode) { 292 | case 1005: 293 | return; 294 | 295 | case 4004: 296 | throw new InvalidTokenError(); 297 | 298 | case 4010: 299 | throw new InvalidShardError(); 300 | 301 | case 4007: 302 | this._connect(false); 303 | break; 304 | 305 | case 4009: 306 | this._connect(false); 307 | break; 308 | 309 | default: 310 | this._connect(); 311 | break; 312 | } 313 | new DisconnectEvent._new(this._ws.client, this, this._socket.closeCode); 314 | this._onDisconnect.add(this); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /lib/src/main/objects/TextChannel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A text channel. 4 | class TextChannel extends GuildChannel { 5 | Timer _typing; 6 | 7 | /// The channel's topic. 8 | String topic; 9 | 10 | /// A collection of messages sent to this channel. 11 | LinkedHashMap messages; 12 | 13 | /// The ID for the last message in the channel. 14 | String lastMessageID; 15 | 16 | /// The channel's mention string. 17 | String mention; 18 | 19 | TextChannel._new(Client client, Map data, Guild guild) 20 | : super._new(client, data, guild, "text") { 21 | this.topic = raw['topic']; 22 | this.lastMessageID = raw['last_message_id']; 23 | this.messages = new LinkedHashMap(); 24 | this.mention = "<#${this.id}>"; 25 | } 26 | 27 | void _cacheMessage(Message message) { 28 | if (this.client._options.messageCacheSize > 0) { 29 | if (this.messages.length >= this.client._options.messageCacheSize) { 30 | this.messages.values.toList().first._onUpdate.close(); 31 | this.messages.values.toList().first._onDelete.close(); 32 | this.messages.remove(this.messages.values.toList().first.id); 33 | } 34 | this.messages[message.id] = message; 35 | } 36 | } 37 | 38 | /// Sends a message. 39 | /// 40 | /// Throws an [Exception] if the HTTP request errored. 41 | /// Channel.send(content: "My content!"); 42 | Future send( 43 | {String content, 44 | Map embed, 45 | bool tts: false, 46 | String nonce, 47 | bool disableEveryone}) async { 48 | String newContent; 49 | if (content != null && 50 | (disableEveryone == true || 51 | (disableEveryone == null && 52 | this.client._options.disableEveryone))) { 53 | newContent = content 54 | .replaceAll("@everyone", "@\u200Beveryone") 55 | .replaceAll("@here", "@\u200Bhere"); 56 | } else { 57 | newContent = content; 58 | } 59 | 60 | final HttpResponse r = await this.client.http.send( 61 | 'POST', '/channels/${this.id}/messages', body: { 62 | "content": newContent, 63 | "tts": tts, 64 | "nonce": nonce, 65 | "embed": embed 66 | }); 67 | return new Message._new( 68 | this.client, r.body.asJson() as Map); 69 | } 70 | 71 | @deprecated 72 | 73 | /// Sends a message. 74 | /// 75 | /// Throws an [Exception] if the HTTP request errored. 76 | /// Channel.sendMessage(content: "My content!"); 77 | Future sendMessage( 78 | {String content, 79 | Map embed, 80 | bool tts: false, 81 | String nonce, 82 | bool disableEveryone}) async { 83 | return this.send( 84 | content: content, 85 | embed: embed, 86 | tts: tts, 87 | nonce: nonce, 88 | disableEveryone: disableEveryone); 89 | } 90 | 91 | /// Edits the channel. 92 | Future edit({ 93 | String name: null, 94 | String topic: null, 95 | int position: null, 96 | }) async { 97 | HttpResponse r = 98 | await this.client.http.send('PATCH', "/channels/${this.id}", body: { 99 | "name": name != null ? name : this.name, 100 | "topic": topic != null ? topic : this.topic, 101 | "position": position != null ? position : this.position 102 | }); 103 | return new TextChannel._new( 104 | this.client, r.body.asJson() as Map, this.guild); 105 | } 106 | 107 | /// Gets a [Message] object. Only usable by bot accounts. 108 | /// 109 | /// Throws an [Exception] if the HTTP request errored or if the client user 110 | /// is not a bot. 111 | /// Channel.getMessage("message id"); 112 | Future getMessage(dynamic message) async { 113 | if (this.client.user.bot) { 114 | final String id = Util.resolve('message', message); 115 | 116 | final HttpResponse r = await this 117 | .client 118 | .http 119 | .send('GET', '/channels/${this.id}/messages/$id'); 120 | return new Message._new( 121 | this.client, r.body.asJson() as Map); 122 | } else { 123 | throw new Exception("'getMessage' is only usable by bot accounts."); 124 | } 125 | } 126 | 127 | /// Gets several [Message] objects. 128 | /// 129 | /// Throws an [Exception] if the HTTP request errored. 130 | /// Channel.getMessages(limit: 100, after: "222078108977594368"); 131 | Future> getMessages({ 132 | int limit: 50, 133 | String after: null, 134 | String before: null, 135 | String around: null, 136 | }) async { 137 | Map query = {"limit": limit.toString()}; 138 | 139 | if (after != null) query['after'] = after; 140 | if (before != null) query['before'] = before; 141 | if (around != null) query['around'] = around; 142 | 143 | final HttpResponse r = await this 144 | .client 145 | .http 146 | .send('GET', '/channels/${this.id}/messages', queryParams: query); 147 | 148 | LinkedHashMap response = 149 | new LinkedHashMap(); 150 | 151 | for (Map val in r.body.asJson()) { 152 | response[val["id"]] = new Message._new(this.client, val); 153 | } 154 | 155 | return response; 156 | } 157 | 158 | /// Starts typing. 159 | Future startTyping() async { 160 | await this.client.http.send('POST', "/channels/$id/typing"); 161 | return null; 162 | } 163 | 164 | /// Loops `startTyping` until `stopTypingLoop` is called. 165 | void startTypingLoop() { 166 | startTyping(); 167 | this._typing = new Timer.periodic( 168 | const Duration(seconds: 7), (Timer t) => startTyping()); 169 | } 170 | 171 | /// Stops a typing loop if one is running. 172 | void stopTypingLoop() { 173 | this._typing?.cancel(); 174 | } 175 | 176 | /// Gets all of the webhooks for this channel. 177 | Future> getWebhooks() async { 178 | HttpResponse r = 179 | await this.client.http.send('GET', "/channels/$id/webhooks"); 180 | Map map = {}; 181 | r.body.asJson().forEach((Map o) { 182 | Webhook webhook = new Webhook._fromApi(this.client, o); 183 | map[webhook.id] = webhook; 184 | }); 185 | return map; 186 | } 187 | 188 | /// Creates a webhook. 189 | Future createWebhook(String name) async { 190 | HttpResponse r = await this 191 | .client 192 | .http 193 | .send('POST', "/channels/$id/webhooks", body: {"name": name}); 194 | return new Webhook._fromApi( 195 | this.client, r.body.asJson() as Map); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/src/main/objects/User.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A user. 4 | class User { 5 | Timer _typing; 6 | 7 | /// The [Client] object. 8 | Client client; 9 | 10 | /// The raw object returned by the API 11 | Map raw; 12 | 13 | /// The user's username. 14 | String username; 15 | 16 | /// The user's ID. 17 | String id; 18 | 19 | /// The user's discriminator. 20 | String discriminator; 21 | 22 | /// The user's avatar hash. 23 | String avatar; 24 | 25 | /// The string to mention the user. 26 | String mention; 27 | 28 | /// A timestamp of when the user was created. 29 | DateTime createdAt; 30 | 31 | /// Whether or not the user is a bot. 32 | bool bot = false; 33 | 34 | User._new(this.client, this.raw) { 35 | this.username = raw['username']; 36 | this.id = raw['id']; 37 | this.discriminator = raw['discriminator']; 38 | this.avatar = raw['avatar']; 39 | this.mention = "<@${this.id}>"; 40 | this.createdAt = Util.getDate(this.id); 41 | 42 | // This will not be set at all in some cases. 43 | if (raw['bot'] == true) { 44 | this.bot = raw['bot']; 45 | } 46 | 47 | client.users[this.id] = this; 48 | } 49 | 50 | /// The user's avatar, represented as URL. 51 | String avatarURL({String format: 'webp', int size: 128}) { 52 | return 'https://cdn.${_Constants.host}/avatars/${this.id}/${this.avatar}.$format?size=$size'; 53 | } 54 | 55 | /// Gets the [DMChannel] for the user. 56 | Future getChannel() async { 57 | try { 58 | return client.channels.values.firstWhere( 59 | (dynamic c) => c is DMChannel && c.recipient.id == this.id); 60 | } catch (err) { 61 | HttpResponse r = await client.http 62 | .send('POST', "/users/@me/channels", body: {"recipient_id": this.id}); 63 | return new DMChannel._new( 64 | client, r.body.asJson() as Map); 65 | } 66 | } 67 | 68 | /// Sends a message. 69 | /// 70 | /// Throws an [Exception] if the HTTP request errored. 71 | /// Channel.send(content: "My content!"); 72 | Future send( 73 | {String content, 74 | Map embed, 75 | bool tts: false, 76 | String nonce, 77 | bool disableEveryone}) async { 78 | String newContent; 79 | if (content != null && 80 | (disableEveryone == true || 81 | (disableEveryone == null && 82 | this.client._options.disableEveryone))) { 83 | newContent = content 84 | .replaceAll("@everyone", "@\u200Beveryone") 85 | .replaceAll("@here", "@\u200Bhere"); 86 | } else { 87 | newContent = content; 88 | } 89 | 90 | DMChannel channel = await getChannel(); 91 | String channelId = channel.id; 92 | 93 | final HttpResponse r = await this.client.http.send( 94 | 'POST', '/channels/$channelId/messages', body: { 95 | "content": newContent, 96 | "tts": tts, 97 | "nonce": nonce, 98 | "embed": embed 99 | }); 100 | return new Message._new( 101 | this.client, r.body.asJson() as Map); 102 | } 103 | 104 | @deprecated 105 | 106 | /// Sends a message. 107 | /// 108 | /// Throws an [Exception] if the HTTP request errored. 109 | /// Channel.sendMessage(content: "My content!"); 110 | Future sendMessage( 111 | {String content, 112 | Map embed, 113 | bool tts: false, 114 | String nonce, 115 | bool disableEveryone}) async { 116 | return this.send( 117 | content: content, 118 | embed: embed, 119 | tts: tts, 120 | nonce: nonce, 121 | disableEveryone: disableEveryone); 122 | } 123 | 124 | /// Gets a [Message] object. Only usable by bot accounts. 125 | /// 126 | /// Throws an [Exception] if the HTTP request errored or if the client user 127 | /// is not a bot. 128 | /// Channel.getMessage("message id"); 129 | Future getMessage(dynamic message) async { 130 | if (this.client.user.bot) { 131 | final String id = Util.resolve('message', message); 132 | DMChannel channel = await getChannel(); 133 | String channelId = channel.id; 134 | 135 | final HttpResponse r = await this 136 | .client 137 | .http 138 | .send('GET', '/channels/$channelId/messages/$id'); 139 | return new Message._new( 140 | this.client, r.body.asJson() as Map); 141 | } else { 142 | throw new Exception("'getMessage' is only usable by bot accounts."); 143 | } 144 | } 145 | 146 | /// Starts typing. 147 | Future startTyping() async { 148 | DMChannel channel = await getChannel(); 149 | String channelId = channel.id; 150 | 151 | await this.client.http.send('POST', "/channels/$channelId/typing"); 152 | return null; 153 | } 154 | 155 | /// Loops `startTyping` until `stopTypingLoop` is called. 156 | void startTypingLoop() { 157 | startTyping(); 158 | this._typing = new Timer.periodic( 159 | const Duration(seconds: 7), (Timer t) => startTyping()); 160 | } 161 | 162 | /// Stops a typing loop if one is running. 163 | void stopTypingLoop() { 164 | this._typing?.cancel(); 165 | } 166 | 167 | /// Returns a string representation of this object. 168 | @override 169 | String toString() { 170 | return this.username; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/src/main/objects/VoiceChannel.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A guild channel. 4 | class VoiceChannel extends GuildChannel { 5 | /// The channel's bitrate. 6 | int bitrate; 7 | 8 | /// The channel's user limit. 9 | int userLimit; 10 | 11 | VoiceChannel._new(Client client, Map data, Guild guild) 12 | : super._new(client, data, guild, "voice") { 13 | this.bitrate = raw['bitrate']; 14 | this.userLimit = raw['user_limit']; 15 | } 16 | 17 | /// Edits the channel. 18 | Future edit( 19 | {String name: null, 20 | int bitrate: null, 21 | int position: null, 22 | int userLimit: null}) async { 23 | HttpResponse r = 24 | await this.client.http.send('PATCH', "/channels/${this.id}", body: { 25 | "name": name != null ? name : this.name, 26 | "bitrate": bitrate != null ? bitrate : this.bitrate, 27 | "user_limit": userLimit != null ? userLimit : this.userLimit, 28 | "position": position != null ? position : this.position 29 | }); 30 | return new VoiceChannel._new( 31 | this.client, r.body.asJson() as Map, this.guild); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/main/objects/Webhook.dart: -------------------------------------------------------------------------------- 1 | part of discord; 2 | 3 | /// A webhook. 4 | class Webhook { 5 | /// The [Client] object. 6 | Client client; 7 | 8 | /// The raw object returned by the API 9 | Map raw; 10 | 11 | /// The HTTP client. 12 | Http http; 13 | 14 | /// The webhook's name. 15 | String name; 16 | 17 | /// The webhook's id. 18 | String id; 19 | 20 | /// The webhook's token. 21 | String token; 22 | 23 | /// The webhook's channel id. 24 | String channelId; 25 | 26 | /// The webhook's channel, if this is accessed using a normal client and the client has that channel in it's cache. 27 | TextChannel channel; 28 | 29 | /// The webhook's guild id. 30 | String guildId; 31 | 32 | /// The webhook's guild, if this is accessed using a normal client and the client has that guild in it's cache. 33 | Guild guild; 34 | 35 | /// The user, if this is accessed using a normal client. 36 | User user; 37 | 38 | /// When the webhook was created; 39 | DateTime createdAt; 40 | 41 | Webhook._fromApi(this.client, this.raw) { 42 | this.http = client.http; 43 | 44 | this.name = raw['name']; 45 | this.id = raw['id']; 46 | this.token = raw['token']; 47 | this.channelId = raw['channel_id']; 48 | this.guildId = raw['guild_id']; 49 | this.createdAt = Util.getDate(this.id); 50 | this.channel = this.client.channels[this.channelId]; 51 | this.guild = this.client.guilds[this.guildId]; 52 | this.user = new User._new(client, raw['user'] as Map); 53 | } 54 | 55 | Webhook._fromToken(this.http, Map raw) { 56 | this.name = raw['name']; 57 | this.id = raw['id']; 58 | this.token = raw['token']; 59 | this.channelId = raw['channel_id']; 60 | this.guildId = raw['guild_id']; 61 | this.createdAt = Util.getDate(this.id); 62 | } 63 | 64 | /// Gets a webhook by its ID and token. 65 | static Future fromToken(String id, String token) async { 66 | Http http = new Http._new(); 67 | HttpResponse r = await http.send('GET', "/webhooks/$id/$token"); 68 | return new Webhook._fromToken( 69 | http, r.body.asJson() as Map); 70 | } 71 | 72 | /// Edits the webhook. 73 | Future edit({String name}) async { 74 | HttpResponse r = await this 75 | .http 76 | .send('PATCH', "/webhooks/$id/$token", body: {"name": name}); 77 | this.name = r.body.asJson()['name']; 78 | return this; 79 | } 80 | 81 | /// Deletes the webhook. 82 | Future delete() async { 83 | await this.http.send('DELETE', "/webhooks/$id/$token"); 84 | return null; 85 | } 86 | 87 | /// Sends a message with the webhook. 88 | Future send( 89 | {String content, 90 | List> embeds, 91 | String username, 92 | String avatarUrl, 93 | bool tts}) async { 94 | Map payload = { 95 | "content": content, 96 | "username": username, 97 | "avatar_url": avatarUrl, 98 | "tts": tts, 99 | "embeds": embeds 100 | }; 101 | 102 | await this.http.send('POST', "/webhooks/$id/$token", body: payload); 103 | return null; 104 | } 105 | 106 | @deprecated 107 | 108 | /// Sends a message with the webhook. 109 | Future sendMessage( 110 | {String content, 111 | List> embeds, 112 | String username, 113 | String avatarUrl, 114 | bool tts}) async { 115 | return this.send( 116 | content: content, 117 | embeds: embeds, 118 | tts: tts, 119 | username: username, 120 | avatarUrl: avatarUrl); 121 | } 122 | 123 | /// Returns a string representation of this object. 124 | @override 125 | String toString() { 126 | return this.name; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/vm.dart: -------------------------------------------------------------------------------- 1 | library discord.vm; 2 | 3 | import 'dart:io'; 4 | import 'src/internals.dart' as internals; 5 | import 'package:w_transport/w_transport.dart' as w_transport; 6 | import 'package:w_transport/vm.dart' show vmTransportPlatform; 7 | 8 | /// Configures the client to run in Dart VM. 9 | void configureDiscordForVM() { 10 | w_transport.globalTransportPlatform = vmTransportPlatform; 11 | internals.operatingSystem = Platform.operatingSystem; 12 | internals.setup = true; 13 | } 14 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: discord 2 | version: 0.15.0 3 | description: A Discord library for Dart 4 | author: Hackzzila 5 | homepage: https://hackzzila.github.io/nyx/ 6 | dependencies: 7 | http_parser: ">=3.0.0 <4.0.0" 8 | w_transport: ">=3.0.0 <4.0.0" 9 | dev_dependencies: 10 | dart_style: 11 | -------------------------------------------------------------------------------- /test/discord.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:discord/discord.dart' as discord; 5 | import 'package:discord/vm.dart' as discord; 6 | 7 | void main() { 8 | discord.configureDiscordForVM(); 9 | 10 | var env = Platform.environment; 11 | var bot = new discord.Client(env['DISCORD_TOKEN']); 12 | 13 | new Timer(const Duration(seconds: 60), () { 14 | print('Timed out waiting for messages'); 15 | exit(1); 16 | }); 17 | 18 | bot.onReady.listen((e) async { 19 | var channel = bot.channels['228308788954791939']; 20 | channel.sendMessage( 21 | content: 22 | "Testing new Travis CI build `#${env['TRAVIS_BUILD_NUMBER']}` from commit `${env['TRAVIS_COMMIT']}` on branch `${env['TRAVIS_BRANCH']}`"); 23 | 24 | var m = await channel.sendMessage(content: "Message test."); 25 | await m.edit(content: "Edit test."); 26 | await m.delete(); 27 | await channel.sendMessage(content: "--trigger-test"); 28 | }); 29 | 30 | bot.onMessage.listen((e) async { 31 | var m = e.message; 32 | 33 | if (m.channel.id == "228308788954791939" && 34 | m.author.id == bot.user.id && 35 | m.content == "--trigger-test") { 36 | await m.delete(); 37 | await m.channel.sendMessage(content: "Tests completed successfully!"); 38 | print("Discord tests completed successfully!"); 39 | await bot.destroy(); 40 | exit(0); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /test/travis.sh: -------------------------------------------------------------------------------- 1 | # Exit on errors 2 | set -e 3 | 4 | # Make sure dartfmt is run on everything 5 | # This assumes you have dart_style as a dev_dependency 6 | echo "Checking dartfmt..." 7 | NEEDS_DARTFMT="$(find example lib test -name "*.dart" | xargs pub run dart_style:format -n)" 8 | if [[ ${NEEDS_DARTFMT} != "" ]] 9 | then 10 | echo "FAILED" 11 | echo "${NEEDS_DARTFMT}" 12 | exit 1 13 | fi 14 | echo "PASSED" 15 | 16 | # Lazy newlines 17 | echo "" 18 | 19 | # Make sure we pass the analyzer 20 | echo "Checking dartanalyzer..." 21 | FAILS_ANALYZER="$(find example lib test -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)" 22 | if [[ $FAILS_ANALYZER == *"[error]"* ]] 23 | then 24 | echo "FAILED" 25 | echo "${FAILS_ANALYZER}" 26 | exit 1 27 | fi 28 | echo "PASSED" 29 | 30 | # Lazy newlines 31 | echo "" 32 | 33 | if [ "$DISCORD_TOKEN" ]; then 34 | dart -c test/discord.dart 35 | else 36 | echo "Discord token not present, skipping Discord tests" 37 | fi 38 | --------------------------------------------------------------------------------