├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── PSR.md ├── README.md ├── composer.json ├── examples ├── docs-examples.php ├── greetNewMembers.php ├── replyToMentionHi.php ├── richEmbed.php ├── sendMessageToChannelOnReady.php └── webhook.php └── src ├── Client.php ├── ClientEvents.php ├── DiscordException.php ├── HTTP ├── APIEndpoints.php ├── APIManager.php ├── APIRequest.php ├── AthenaRatelimitBucket.php ├── DiscordAPIException.php ├── Endpoints │ ├── Channel.php │ ├── Emoji.php │ ├── Guild.php │ ├── Invite.php │ ├── User.php │ ├── Voice.php │ └── Webhook.php └── RatelimitBucket.php ├── Interfaces ├── CategoryChannelInterface.php ├── ChannelInterface.php ├── ChannelStorageInterface.php ├── DMChannelInterface.php ├── EmojiStorageInterface.php ├── GroupDMChannelInterface.php ├── GuildChannelInterface.php ├── GuildMemberStorageInterface.php ├── GuildNewsChannelInterface.php ├── GuildStorageInterface.php ├── GuildStoreChannelInterface.php ├── GuildTextChannelInterface.php ├── GuildVoiceChannelInterface.php ├── MessageStorageInterface.php ├── PresenceStorageInterface.php ├── RatelimitBucketInterface.php ├── RoleStorageInterface.php ├── StorageInterface.php ├── TextChannelInterface.php ├── UserStorageInterface.php ├── VoiceChannelInterface.php ├── WSCompressionInterface.php ├── WSEncodingInterface.php ├── WSEventInterface.php └── WSHandlerInterface.php ├── Models ├── Activity.php ├── AuditLog.php ├── AuditLogEntry.php ├── Base.php ├── CategoryChannel.php ├── ChannelStorage.php ├── ClientBase.php ├── ClientStatus.php ├── ClientUser.php ├── DMChannel.php ├── Emoji.php ├── EmojiStorage.php ├── GroupDMChannel.php ├── Guild.php ├── GuildBan.php ├── GuildMember.php ├── GuildMemberStorage.php ├── GuildStorage.php ├── GuildStoreChannel.php ├── Invite.php ├── Message.php ├── MessageActivity.php ├── MessageApplication.php ├── MessageAttachment.php ├── MessageEmbed.php ├── MessageMentions.php ├── MessageReaction.php ├── MessageStorage.php ├── OAuthApplication.php ├── PartialChannel.php ├── PartialGuild.php ├── PermissionOverwrite.php ├── Permissions.php ├── Presence.php ├── PresenceStorage.php ├── RichPresenceAssets.php ├── Role.php ├── RoleStorage.php ├── Shard.php ├── Storage.php ├── TextChannel.php ├── User.php ├── UserConnection.php ├── UserStorage.php ├── VoiceChannel.php ├── VoiceRegion.php └── Webhook.php ├── Traits ├── GuildChannelTrait.php └── TextChannelTrait.php ├── Utils ├── Collector.php ├── DataHelpers.php ├── EventHelpers.php ├── FileHelpers.php ├── ImageHelpers.php ├── MessageHelpers.php ├── Snowflake.php └── URLHelpers.php ├── WebSocket ├── Compression │ └── ZlibStream.php ├── DiscordGatewayException.php ├── Encoding │ ├── Etf.php │ └── Json.php ├── Events │ ├── ChannelCreate.php │ ├── ChannelDelete.php │ ├── ChannelPinsUpdate.php │ ├── ChannelUpdate.php │ ├── GuildBanAdd.php │ ├── GuildBanRemove.php │ ├── GuildCreate.php │ ├── GuildDelete.php │ ├── GuildEmojisUpdate.php │ ├── GuildIntegrationsUpdate.php │ ├── GuildMemberAdd.php │ ├── GuildMemberRemove.php │ ├── GuildMemberUpdate.php │ ├── GuildMembersChunk.php │ ├── GuildRoleCreate.php │ ├── GuildRoleDelete.php │ ├── GuildRoleUpdate.php │ ├── GuildUpdate.php │ ├── MessageCreate.php │ ├── MessageDelete.php │ ├── MessageDeleteBulk.php │ ├── MessageReactionAdd.php │ ├── MessageReactionRemove.php │ ├── MessageReactionRemoveAll.php │ ├── MessageUpdate.php │ ├── PresenceUpdate.php │ ├── Ready.php │ ├── Resumed.php │ ├── TypingStart.php │ ├── UserUpdate.php │ ├── VoiceServerUpdate.php │ └── VoiceStateUpdate.php ├── Handlers │ ├── Dispatch.php │ ├── Heartbeat.php │ ├── HeartbeatAck.php │ ├── Hello.php │ ├── InvalidSession.php │ └── Reconnect.php ├── WSConnection.php ├── WSHandler.php └── WSManager.php └── WebhookClient.php /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at github@charuru.moe. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Please describe the issue you are having:** 8 | 9 | 10 | **If you are encountering any errors, post the full stack trace here:** 11 | ``` 12 | 13 | ``` 14 | 15 | **Please include a Minimal, Complete and Verifiable Example:** 16 | ```php 17 | 18 | ``` 19 | 20 | **Please include the expected output of the MCVE:** 21 | ```php 22 | 23 | ``` 24 | 25 | **Please include the actual output of the MCVE:** 26 | ```php 27 | 28 | ``` 29 | 30 | **Further details:** 31 | 32 | - Yasmin version: 33 | - PHP version: 34 | - Operating System: 35 | 36 | - [ ] I have also tested the issue on latest master with commit hash: 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Pull Request description:** 2 | 3 | 4 | **Why should this Pull Request be merged:** 5 | 6 | 7 | **Semantic Versioning Classification:** 8 | - [ ] This PR includes major breaking changes (such as method changes) 9 | - [ ] This PR includes **only** documentational changes (such as docblocks, README, etc.) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/* 3 | cache 4 | cache/* 5 | sami/* 6 | sami.* 7 | vendor 8 | vendor/* 9 | *.token 10 | composer.lock 11 | index.php 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Wanted Changes 4 | 5 | 1. Bugfixes and Feature Additions. 6 | 2. (Partial) rework of methods and/or functions to make the implementation easier. 7 | 3. Rewording or extending documentation to clarify unclear or complicated wording. 8 | 4. Fixing of spelling and grammatical errors. 9 | 10 | ## Unwanted Changes 11 | 12 | 1. Whitespace or formatting changes. 13 | 2. Modifications to the overall structure and format. 14 | 3. Modifications of .gitignore to add unwanted folders or files of your favourite IDE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yasmin [![Build Status](https://scrutinizer-ci.com/g/CharlotteDunois/Yasmin/badges/build.png?b=master)](https://scrutinizer-ci.com/g/CharlotteDunois/Yasmin/build-status/master) 2 | 3 | Yasmin is a Discord API library for PHP. Yasmin connects to the Gateway and interacts with the REST API. 4 | 5 | This library is **only** for PHP 7.1 (and later) and use in CLI. Only bot accounts are supported by Yasmin. 6 | 7 | # Before you start 8 | Before you start using this Library, you **need** to know how PHP works, you need to know the language and you need to know how Event Loops and Promises work. This is a fundamental requirement before you start. Without this knowledge, you will only suffer. 9 | 10 | See https://github.com/elazar/asynchronous-php for resources. 11 | 12 | # Getting Started 13 | Getting started with Yasmin is pretty straight forward. All you need to do is to use [composer](https://packagist.org/packages/charlottedunois/yasmin) to install Yasmin and its dependencies. After that, you can include composer's autoloader into your file and start interacting with Discord and Yasmin! 14 | 15 | ``` 16 | composer require charlottedunois/yasmin 17 | ``` 18 | 19 |
20 | 21 | It is important to listen to `error` events. If you don't attach an `error` listener, the event emitter will throw an exception. 22 | 23 | Make sure you also have a rejection handler for all promises, as unhandled promise rejections get swallowed and you will never know what happened to them. 24 | 25 | **Important Information**: All properties on class instances, which are implemented using a magic method (which means pretty much all properties), are **throwing** if the property doesn't exist. 26 | 27 | There is a WIP Gitbook with a few protips in it, feel free to read it: https://charlottedunois.gitbooks.io/yasmin-guide/content/ 28 | 29 | # Example 30 | This is a fairly trivial example of using Yasmin. You should put all your listener code into try-catch blocks and handle exceptions accordingly. 31 | 32 | ```php 33 | // Include composer autoloader 34 | 35 | $loop = \React\EventLoop\Factory::create(); 36 | $client = new \CharlotteDunois\Yasmin\Client(array(), $loop); 37 | 38 | $client->on('error', function ($error) { 39 | echo $error.PHP_EOL; 40 | }); 41 | 42 | $client->on('ready', function () use ($client) { 43 | echo 'Logged in as '.$client->user->tag.' created on '.$client->user->createdAt->format('d.m.Y H:i:s').PHP_EOL; 44 | }); 45 | 46 | $client->on('message', function ($message) { 47 | echo 'Received Message from '.$message->author->tag.' in '.($message->channel instanceof \CharlotteDunois\Yasmin\Interfaces\DMChannelInterface ? 'DM' : 'channel #'.$message->channel->name ).' with '.$message->attachments->count().' attachment(s) and '.\count($message->embeds).' embed(s)'.PHP_EOL; 48 | }); 49 | 50 | $client->login('YOUR_TOKEN')->done(); 51 | $loop->run(); 52 | ``` 53 | 54 | # Voice Support 55 | There is no voice support. 56 | 57 | # Documentation 58 | https://charlottedunois.github.io/Yasmin/ 59 | 60 | # Windows and SSL 61 | Unfortunately PHP on Windows does not have access to the Windows Certificate Store. This is an issue because TLS gets used and as such certificate verification gets applied (turning this off is **not** an option). 62 | 63 | You will notice this issue by your script exiting immediately after one loop turn without any errors. Unfortunately there is for some reason no error or exception. 64 | 65 | As such users of this library need to download a [Certificate Authority extract](https://curl.haxx.se/docs/caextract.html) from the cURL website.
66 | The path to the caextract must be set in the [`php.ini`](https://secure.php.net/manual/en/openssl.configuration.php) for `openssl.cafile`. 67 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "charlottedunois/yasmin", 3 | "description": "Yasmin is a Discord API library for PHP.", 4 | "type": "library", 5 | "license": "Apache-2.0", 6 | "authors": [ 7 | { 8 | "name": "CharlotteDunois", 9 | "email": "github@charuru.moe" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "require": { 14 | "php": ">=7.1", 15 | "ext-bcmath": "*", 16 | "ext-date": "*", 17 | "ext-filter": "*", 18 | "ext-json": "*", 19 | "ext-mbstring": "*", 20 | "ext-openssl": "*", 21 | "ext-pcre": "*", 22 | "ext-zlib": "*", 23 | "charlottedunois/collection": "^0.2.0", 24 | "charlottedunois/eventemitter": "^0.1.5", 25 | "charlottedunois/validator": "^0.3.0", 26 | "clue/buzz-react": "^2.6.0", 27 | "psr/http-message": "^1.0", 28 | "ratchet/pawl": "^0.3.1", 29 | "react/event-loop": "^1.0|^0.5|^0.4.3", 30 | "react/filesystem": "^0.1.1", 31 | "react/promise": "^2.7.0", 32 | "ringcentral/psr7": "^1.2" 33 | }, 34 | "require-dev": { 35 | "charlottedunois/athena": "^0.1.1|dev-master", 36 | "charlottedunois/kimberly": "^0.1.2|dev-master" 37 | }, 38 | "suggest": { 39 | "charlottedunois/athena": "For Redis-backed ratelimit bucket", 40 | "charlottedunois/kimberly": "For WS encoding etf 🚀" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "CharlotteDunois\\Yasmin\\": "src" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/docs-examples.php: -------------------------------------------------------------------------------- 1 | user->setAvatar(__DIR__.'/resources/avatar.png'); 17 | $client->user->setAvatar('https://my.resources.online/resources/avatar.png'); 18 | $client->user->setAvatar(file_get_contents(__DIR__.'/resources/avatar.png')); 19 | 20 | // ClientUser::setGame 21 | // In/After ready event 22 | $client->user->setGame('Yasmin'); 23 | 24 | // ClientUser::setStatus 25 | // In/After ready event 26 | $client->user->setStatus('online'); 27 | 28 | // ClientUser::setPresence 29 | // In/After ready event 30 | $client->user->setPresence( 31 | array( 32 | 'status' => 'idle', 33 | 'game' => array( 34 | 'name' => 'Yasmin', 35 | 'type' => 0 36 | ) 37 | ) 38 | ); 39 | 40 | // ClientUser::setUsername 41 | // In/After ready event 42 | $client->user->setUsername('My Super New Username'); 43 | -------------------------------------------------------------------------------- /examples/greetNewMembers.php: -------------------------------------------------------------------------------- 1 | on('guildMemberAdd', function ($member) { 21 | try { 22 | // Find the first channel matching the name member-log in the guild 23 | $channel = $member->guild->channels->first(function ($channel) { 24 | return ($channel->name === 'member-log'); 25 | }); 26 | 27 | // Making sure the channel exists 28 | if($channel) { 29 | // Send the message, welcoming & mentioning the member 30 | 31 | // We do not need another promise here, so 32 | // we call done, because we want to consume the promise 33 | $channel->send('Welcome to the guild '.$member->guild->name.', '.$member.'!') 34 | ->done(null, function ($error) { 35 | // We will just echo any errors for this example 36 | echo $error.PHP_EOL; 37 | }); 38 | } 39 | } catch(\Exception $error) { 40 | // Handle exception 41 | } 42 | }); 43 | 44 | $client->login('YOUR_TOKEN'); 45 | $loop->run(); 46 | -------------------------------------------------------------------------------- /examples/replyToMentionHi.php: -------------------------------------------------------------------------------- 1 | array( 20 | /* We disable the TYPING_START event to save CPU cycles, we don't need it here in this example. */ 21 | 'TYPING_START' 22 | ) 23 | ), $loop); 24 | 25 | // Precompute mention format 26 | $mentions = array(); 27 | 28 | $client->once('ready', function () use ($client, &$mentions) { 29 | $format1 = '<@'.$client->user->id.'>'; 30 | $format2 = '<@!'.$client->user->id.'>'; 31 | 32 | $mentions = array( 33 | $format1, 34 | strlen($format1), 35 | $format2, 36 | strlen($format2) 37 | ); 38 | }); 39 | 40 | $client->on('message', function ($message) use ($client, &$mentions) { 41 | try { 42 | // Get the start of message content to compare with our mention formats 43 | // We use the longest mention format 44 | // We also trim it from any trailing whitespaces 45 | $start = \trim(\substr($message->content, $mentions[3])); 46 | 47 | // Now we compare it, we only want the bot to respond 48 | // when the mention is at the start of the content 49 | if($start === $mentions[0] || $start === $mentions[1]) { 50 | // We do not need another promise here, so 51 | // we call done, because we want to consume the promise 52 | $message->reply('Hi!')->done(null, function ($error) { 53 | // We will just echo any errors for this example 54 | echo $error.PHP_EOL; 55 | }); 56 | } 57 | } catch(\Exception $error) { 58 | // Handle exception 59 | } 60 | }); 61 | 62 | $client->login('YOUR_TOKEN'); 63 | $loop->run(); 64 | -------------------------------------------------------------------------------- /examples/richEmbed.php: -------------------------------------------------------------------------------- 1 | once('ready', function () use ($client) { 20 | try { 21 | $channel = $client->channels->get('CHANNEL_ID'); 22 | 23 | // Making sure the channel exists 24 | if($channel) { 25 | $embed = new \CharlotteDunois\Yasmin\Models\MessageEmbed(); 26 | 27 | // Build the embed 28 | $embed 29 | ->setTitle('A new Rich Embed') // Set a title 30 | ->setColor(random_int(0, 16777215)) // Set a color (the thing on the left side) 31 | ->setDescription(':)') // Set a description (below title, above fields) 32 | ->addField('Test', 'Value') // Add one field 33 | ->addField('Test 2', 'Value 2', true) // Add one inline field 34 | ->addField('Test 3', 'Value 3', true) // Add another inline field 35 | ->setThumbnail('https://avatars1.githubusercontent.com/u/4529744?s=460&v=4') // Set a thumbnail (the image in the top right corner) 36 | ->setImage('https://avatars1.githubusercontent.com/u/4529744?s=460&v=4') // Set an image (below everything except footer) 37 | ->setTimestamp() // Set a timestamp (gets shown next to footer) 38 | ->setAuthor('Yasmin', 'https://avatars1.githubusercontent.com/u/4529744?s=460&v=4') // Set an author with icon 39 | ->setFooter('Generated with the Rich Embed Builder (Y)') // Set a footer without icon 40 | ->setURL('https://github.com/CharlotteDunois/Yasmin'); // Set the URL 41 | 42 | // Send the message 43 | 44 | // We do not need another promise here, so 45 | // we call done, because we want to consume the promise 46 | $channel->send('', array('embed' => $embed)) 47 | ->done(null, function ($error) { 48 | // We will just echo any errors for this example 49 | echo $error.PHP_EOL; 50 | }); 51 | } 52 | } catch(\Exception $error) { 53 | // Handle exception 54 | } 55 | }); 56 | 57 | $client->login('YOUR_TOKEN'); 58 | $loop->run(); 59 | -------------------------------------------------------------------------------- /examples/sendMessageToChannelOnReady.php: -------------------------------------------------------------------------------- 1 | once('ready', function () use ($client) { 21 | try { 22 | $channel = $client->channels->get('CHANNEL_ID'); 23 | /* 24 | or (not recommended if the bot is in more than 1 guild): 25 | $channel = $client->channels->first(function ($channel) { 26 | return ($channel->name === 'general'); 27 | }); 28 | */ 29 | 30 | // Making sure the channel exists 31 | if($channel) { 32 | // Send the message 33 | 34 | // We do not need another promise here, so 35 | // we call done, because we want to consume the promise 36 | $channel->send('Hello, I am a Discord Bot written in PHP using Yasmin.') 37 | ->done(null, function ($error) { 38 | // We will just echo any errors for this example 39 | echo $error.PHP_EOL; 40 | }); 41 | } 42 | } catch(\Exception $error) { 43 | // Handle exception 44 | } 45 | }); 46 | 47 | $client->login('YOUR_TOKEN'); 48 | $loop->run(); 49 | -------------------------------------------------------------------------------- /examples/webhook.php: -------------------------------------------------------------------------------- 1 | send('Hallo')->done(function () use ($loop) { 24 | echo 'Message sent!'.PHP_EOL; 25 | $loop->stop(); 26 | }, function ($error) { 27 | // We will just echo any errors for this example 28 | echo $error.PHP_EOL; 29 | }); 30 | 31 | $loop->run(); 32 | -------------------------------------------------------------------------------- /src/DiscordException.php: -------------------------------------------------------------------------------- 1 | 'https://cdn.discordapp.com/', 24 | 'emojis' => 'emojis/%s.%s', 25 | 'icons' => 'icons/%s/%s.%s', 26 | 'splashes' => 'splashes/%s/%s.%s', 27 | 'defaultavatars' => 'embed/avatars/%s.%s', 28 | 'avatars' => 'avatars/%s/%s.%s', 29 | 'appicons' => 'app-icons/%s/%s.png', 30 | 'appassets' => 'app-assets/%s/%s.png', 31 | 'channelicons' => 'channel-icons/%s/%s.png', 32 | 'guildbanners' => 'banners/%s/%s.%s' 33 | ); 34 | 35 | /** 36 | * HTTP constants. 37 | * @var array 38 | * @internal 39 | */ 40 | const HTTP = array( 41 | 'url' => 'https://discordapp.com/api/', 42 | 'version' => 7, 43 | 'invite' => 'https://discord.gg/' 44 | ); 45 | 46 | /** 47 | * Endpoints General. 48 | * @var array 49 | * @internal 50 | */ 51 | const ENDPOINTS = array( 52 | 'currentOAuthApplication' => 'oauth2/applications/@me' 53 | ); 54 | 55 | /** 56 | * The API manager. 57 | * @var \CharlotteDunois\Yasmin\HTTP\APIManager 58 | */ 59 | protected $api; 60 | 61 | /** 62 | * The channel endpoints. 63 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\Channel 64 | */ 65 | public $channel; 66 | 67 | /** 68 | * The emoji endpoints. 69 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\Emoji 70 | */ 71 | public $emoji; 72 | 73 | /** 74 | * The guild endpoints. 75 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\Guild 76 | */ 77 | public $guild; 78 | 79 | /** 80 | * The invite endpoints. 81 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\Invite 82 | */ 83 | public $invite; 84 | 85 | /** 86 | * The user endpoints. 87 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\User 88 | */ 89 | public $user; 90 | 91 | /** 92 | * The voice endpoints. 93 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\Voice 94 | */ 95 | public $voice; 96 | 97 | /** 98 | * The webhook endpoints. 99 | * @var \CharlotteDunois\Yasmin\HTTP\Endpoints\Webhook 100 | */ 101 | public $webhook; 102 | 103 | 104 | /** 105 | * DO NOT initialize this class yourself. 106 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 107 | */ 108 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api) { 109 | $this->api = $api; 110 | 111 | $this->channel = new \CharlotteDunois\Yasmin\HTTP\Endpoints\Channel($api); 112 | $this->emoji = new \CharlotteDunois\Yasmin\HTTP\Endpoints\Emoji($api); 113 | $this->guild = new \CharlotteDunois\Yasmin\HTTP\Endpoints\Guild($api); 114 | $this->invite = new \CharlotteDunois\Yasmin\HTTP\Endpoints\Invite($api); 115 | $this->user = new \CharlotteDunois\Yasmin\HTTP\Endpoints\User($api); 116 | $this->voice = new \CharlotteDunois\Yasmin\HTTP\Endpoints\Voice($api); 117 | $this->webhook = new \CharlotteDunois\Yasmin\HTTP\Endpoints\Webhook($api); 118 | } 119 | 120 | /** 121 | * Gets the current OAuth application. 122 | * @return \React\Promise\ExtendedPromiseInterface 123 | */ 124 | function getCurrentApplication() { 125 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::ENDPOINTS['currentOAuthApplication']; 126 | return $this->api->makeRequest('GET', $url, array()); 127 | } 128 | 129 | /** 130 | * Formats Endpoints strings. 131 | * @param string $endpoint 132 | * @param string ...$args 133 | * @return string 134 | */ 135 | static function format(string $endpoint, ...$args) { 136 | return \sprintf($endpoint, ...$args); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/HTTP/DiscordAPIException.php: -------------------------------------------------------------------------------- 1 | path = $path; 29 | $flattened = \implode('\n', self::flattenErrors(($error['errors'] ?? $error))); 30 | 31 | parent::__construct((!empty($error['message']) && !empty($flattened) ? $error['message'].\PHP_EOL.$flattened : ($error['message'] ?? $flattened)), (int) ($error['code'] ?? 0)); 32 | } 33 | 34 | /** 35 | * Flattens an errors object returned from the API into an array. 36 | * @param array $obj Discord error object 37 | * @param string $key Used internally to determine key names of nested fields 38 | * @return string[] 39 | * @internal 40 | */ 41 | static function flattenErrors($obj, $key = '') { 42 | $messages = array(); 43 | 44 | foreach($obj as $k => $val) { 45 | if($k === 'message') { 46 | continue; 47 | } 48 | 49 | $newKey = $k; 50 | if($key) { 51 | if(\is_numeric($k)) { 52 | $newKey = $key.'.'.$k; 53 | } else { 54 | $newKey = $key.'['.$k.']'; 55 | } 56 | } 57 | 58 | if(isset($val['_errors'])) { 59 | $messages[] = $newKey.': '.\implode(' ', \array_map(function ($element) { 60 | return $element['message']; 61 | }, $val['_errors'])); 62 | } else if(isset($val['code']) || isset($val['message'])) { 63 | $messages[] = \trim(($val['code'] ?? '').': '.($val['message'] ?? '')); 64 | } else if(\is_array($val)) { 65 | $messages = \array_merge($messages, self::flattenErrors($val, $newKey)); 66 | } else { 67 | $messages[] = $val; 68 | } 69 | } 70 | 71 | return $messages; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/HTTP/Endpoints/Emoji.php: -------------------------------------------------------------------------------- 1 | 'guilds/%s/emojis', 23 | 'get' => 'guilds/%s/emojis/%s', 24 | 'create' => 'guilds/%s/emojis', 25 | 'modify' => 'guilds/%s/emojis/%s', 26 | 'delete' => 'guilds/%s/emojis/%s' 27 | ); 28 | 29 | /** 30 | * @var \CharlotteDunois\Yasmin\HTTP\APIManager 31 | */ 32 | protected $api; 33 | 34 | /** 35 | * Constructor. 36 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 37 | */ 38 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api) { 39 | $this->api = $api; 40 | } 41 | 42 | function listGuildEmojis(string $guildid) { 43 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['list'], $guildid); 44 | return $this->api->makeRequest('GET', $url, array()); 45 | } 46 | 47 | function getGuildEmoji(string $guildid, string $emojiid) { 48 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['get'], $guildid, $emojiid); 49 | return $this->api->makeRequest('GET', $url, array()); 50 | } 51 | 52 | function createGuildEmoji(string $guildid, array $options, string $reason = '') { 53 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['create'], $guildid); 54 | return $this->api->makeRequest('POST', $url, array('auditLogReason' => $reason, 'data' => $options)); 55 | } 56 | 57 | function modifyGuildEmoji(string $guildid, string $emojiid, array $options, string $reason = '') { 58 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['modify'], $guildid, $emojiid); 59 | return $this->api->makeRequest('PATCH', $url, array('auditLogReason' => $reason, 'data' => $options)); 60 | } 61 | 62 | function deleteGuildEmoji(string $guildid, string $emojiid, string $reason = '') { 63 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['delete'], $guildid, $emojiid); 64 | return $this->api->makeRequest('DELETE', $url, array('auditLogReason' => $reason)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/HTTP/Endpoints/Invite.php: -------------------------------------------------------------------------------- 1 | 'invites/%s', 23 | 'delete' => 'invites/%s', 24 | 'accept' => 'invites/%s' 25 | ); 26 | 27 | /** 28 | * @var \CharlotteDunois\Yasmin\HTTP\APIManager 29 | */ 30 | protected $api; 31 | 32 | /** 33 | * Constructor. 34 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 35 | */ 36 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api) { 37 | $this->api = $api; 38 | } 39 | 40 | function getInvite(string $code, bool $withCounts = false) { 41 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['get'], $code); 42 | 43 | $opts = array(); 44 | if($withCounts) { 45 | $opts['querystring'] = array('with_counts' => 'true'); 46 | } 47 | 48 | return $this->api->makeRequest('GET', $url, $opts); 49 | } 50 | 51 | function deleteInvite(string $code, string $reason = '') { 52 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['delete'], $code); 53 | return $this->api->makeRequest('DELETE', $url, array('auditLogReason' => $reason)); 54 | } 55 | 56 | function acceptInvite(string $code) { 57 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['accept'], $code); 58 | return $this->api->makeRequest('POST', $url, array()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/HTTP/Endpoints/User.php: -------------------------------------------------------------------------------- 1 | 'users/%s', 23 | 'current' => array( 24 | 'get' => 'users/@me', 25 | 'modify' => 'users/@me', 26 | 'guilds' => 'users/@me/guilds', 27 | 'leaveGuild' => 'users/@me/guilds/%s', 28 | 'dms' => 'users/@me/channels', 29 | 'createDM' => 'users/@me/channels', 30 | 'createGroupDM' => 'users/@me/channels', 31 | 'connections' => 'users/@me/connections' 32 | ) 33 | ); 34 | 35 | /** 36 | * @var \CharlotteDunois\Yasmin\HTTP\APIManager 37 | */ 38 | protected $api; 39 | 40 | /** 41 | * Constructor. 42 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 43 | */ 44 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api) { 45 | $this->api = $api; 46 | } 47 | 48 | function getCurrentUser() { 49 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['get']); 50 | return $this->api->makeRequest('GET', $url, array()); 51 | } 52 | 53 | function getUser(string $userid) { 54 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['get'], $userid); 55 | return $this->api->makeRequest('GET', $url, array()); 56 | } 57 | 58 | function modifyCurrentUser(array $options) { 59 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['modify']); 60 | return $this->api->makeRequest('PATCH', $url, array('data' => $options)); 61 | } 62 | 63 | function getCurrentUserGuilds() { 64 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['guilds']); 65 | return $this->api->makeRequest('GET', $url, array()); 66 | } 67 | 68 | function leaveUserGuild(string $guildid) { 69 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['leaveGuild'], $guildid); 70 | return $this->api->makeRequest('DELETE', $url, array()); 71 | } 72 | 73 | function getUserDMs() { 74 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['dms']); 75 | return $this->api->makeRequest('GET', $url, array()); 76 | } 77 | 78 | function createUserDM(string $recipientid) { 79 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['createDM']); 80 | return $this->api->makeRequest('POST', $url, array('data' => array('recipient_id' => $recipientid))); 81 | } 82 | 83 | function createGroupDM(array $accessTokens, array $nicks) { 84 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['createGroupDM']); 85 | return $this->api->makeRequest('POST', $url, array('data' => array('access_tokens' => $accessTokens, 'nicks' => $nicks))); 86 | } 87 | 88 | function getUserConnections(string $accessToken) { 89 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['current']['connections']); 90 | return $this->api->makeRequest('GET', $url, array('auth' => 'Bearer '.$accessToken)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/HTTP/Endpoints/Voice.php: -------------------------------------------------------------------------------- 1 | 'voice/regions' 23 | ); 24 | 25 | /** 26 | * @var \CharlotteDunois\Yasmin\HTTP\APIManager 27 | */ 28 | protected $api; 29 | 30 | /** 31 | * Constructor. 32 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 33 | */ 34 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api) { 35 | $this->api = $api; 36 | } 37 | 38 | function listVoiceRegions() { 39 | $url = self::ENDPOINTS['regions']; 40 | return $this->api->makeRequest('GET', $url, array()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/HTTP/Endpoints/Webhook.php: -------------------------------------------------------------------------------- 1 | 'channels/%s/webhooks', 23 | 'channels' => 'channels/%s/webhooks', 24 | 'guilds' => 'guilds/%s/webhooks', 25 | 'get' => 'webhooks/%s', 26 | 'getToken' => 'webhooks/%s/%s', 27 | 'modify' => 'webhooks/%s', 28 | 'modifyToken' => 'webhooks/%s/%s', 29 | 'delete' => 'webhooks/%s', 30 | 'deleteToken' => 'webhooks/%s/%s', 31 | 'execute' => 'webhooks/%s/%s' 32 | ); 33 | 34 | /** 35 | * Constructor. 36 | * @var \CharlotteDunois\Yasmin\HTTP\APIManager 37 | */ 38 | protected $api; 39 | 40 | /** 41 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 42 | */ 43 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api) { 44 | $this->api = $api; 45 | } 46 | 47 | function createWebhook(string $channelid, string $name, ?string $avatarBase64 = null, string $reason = '') { 48 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['create'], $channelid); 49 | return $this->api->makeRequest('POST', $url, array('auditLogReason' => $reason, 'data' => array('name' => $name, 'avatar' => $avatarBase64))); 50 | } 51 | 52 | function getChannelWebhooks(string $channelid) { 53 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['channels'], $channelid); 54 | return $this->api->makeRequest('GET', $url, array()); 55 | } 56 | 57 | function getGuildsWebhooks(string $guildid) { 58 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['guilds'], $guildid); 59 | return $this->api->makeRequest('GET', $url, array()); 60 | } 61 | 62 | function getWebhook(string $webhookid) { 63 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['get'], $webhookid); 64 | return $this->api->makeRequest('GET', $url, array()); 65 | } 66 | 67 | function getWebhookToken(string $webhookid, string $token) { 68 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['getToken'], $webhookid, $token); 69 | return $this->api->makeRequest('GET', $url, array()); 70 | } 71 | 72 | function modifyWebhook(string $webhookid, array $options, string $reason = '') { 73 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['modify'], $webhookid); 74 | return $this->api->makeRequest('PATCH', $url, array('auditLogReason' => $reason, 'data' => $options)); 75 | } 76 | 77 | function modifyWebhookToken(string $webhookid, string $token, array $options, string $reason = '') { 78 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['modifyToken'], $webhookid, $token); 79 | return $this->api->makeRequest('PATCH', $url, array('auditLogReason' => $reason, 'data' => $options, 'noAuth' => true)); 80 | } 81 | 82 | function deleteWebhook(string $webhookid, string $reason = '') { 83 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['delete'], $webhookid); 84 | return $this->api->makeRequest('DELETE', $url, array('auditLogReason' => $reason)); 85 | } 86 | 87 | function deleteWebhookToken(string $webhookid, string $token, string $reason = '') { 88 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['deleteToken'], $webhookid, $token); 89 | return $this->api->makeRequest('DELETE', $url, array('auditLogReason' => $reason, 'noAuth' => true)); 90 | } 91 | 92 | function executeWebhook(string $webhookid, string $token, array $options, array $files = array(), array $querystring = array()) { 93 | $url = \CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(self::ENDPOINTS['execute'], $webhookid, $token); 94 | return $this->api->makeRequest('POST', $url, array('data' => $options, 'files' => $files, 'noAuth' => true, 'querystring' => $querystring)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Interfaces/CategoryChannelInterface.php: -------------------------------------------------------------------------------- 1 | int, 24 | * 'maxUses' => int, (0 = unlimited) 25 | * 'temporary' => bool, 26 | * 'unique' => bool 27 | * ) 28 | * ``` 29 | * 30 | * @param array $options 31 | * @return \React\Promise\ExtendedPromiseInterface 32 | */ 33 | function createInvite(array $options = array()); 34 | 35 | /** 36 | * Fetches all invites of this channel. Resolves with a Collection of Invite instances, mapped by their code. 37 | * @return \React\Promise\ExtendedPromiseInterface 38 | * @see \CharlotteDunois\Yasmin\Models\Invite 39 | */ 40 | function fetchInvites(); 41 | 42 | /** 43 | * Sets the topic of the channel. Resolves with $this. 44 | * @param string $topic 45 | * @param string $reason 46 | * @return \React\Promise\ExtendedPromiseInterface 47 | * @throws \InvalidArgumentException 48 | */ 49 | function setTopic(string $topic, string $reason = ''); 50 | } 51 | -------------------------------------------------------------------------------- /src/Interfaces/GuildStorageInterface.php: -------------------------------------------------------------------------------- 1 | int, 33 | * 'maxUses' => int, (0 = unlimited) 34 | * 'temporary' => bool, 35 | * 'unique' => bool 36 | * ) 37 | * ``` 38 | * 39 | * @param array $options 40 | * @return \React\Promise\ExtendedPromiseInterface 41 | */ 42 | function createInvite(array $options = array()); 43 | 44 | /** 45 | * Fetches all invites of this channel. Resolves with a Collection of Invite instances, mapped by their code. 46 | * @return \React\Promise\ExtendedPromiseInterface 47 | * @see \CharlotteDunois\Yasmin\Models\Invite 48 | */ 49 | function fetchInvites(); 50 | 51 | /** 52 | * Sets the slowmode in seconds for this channel. 53 | * @param int $slowmode 54 | * @param string $reason 55 | * @return \React\Promise\ExtendedPromiseInterface 56 | */ 57 | function setSlowmode(int $slowmode, string $reason = ''); 58 | 59 | /** 60 | * Sets the topic of the channel. Resolves with $this. 61 | * @param string $topic 62 | * @param string $reason 63 | * @return \React\Promise\ExtendedPromiseInterface 64 | * @throws \InvalidArgumentException 65 | */ 66 | function setTopic(string $topic, string $reason = ''); 67 | } 68 | -------------------------------------------------------------------------------- /src/Interfaces/GuildVoiceChannelInterface.php: -------------------------------------------------------------------------------- 1 | 14 | * The ratelimit bucket queue is always managed in memory (as in belongs to that process), however the ratelimits are distributed to the used system. 15 | * 16 | * Included are two ratelimit bucket systems:
17 | * * In memory ratelimit bucket, using arrays - Class: `\CharlotteDunois\Yasmin\HTTP\RatelimitBucket` (default)
18 | * * Redis ratelimit bucket, using Athena to interface with Redis - Class: `\CharlotteDunois\Yasmin\HTTP\AthenaRatelimitBucket` 19 | * 20 | * To use a different one than the default, you have to pass the full qualified class name to the client constructor as client option `http.ratelimitbucket.name`. 21 | * 22 | * The Redis ratelimit bucket system uses Athena, an asynchronous redis cache for PHP. The package is called `charlottedunois/athena` (which is suggested on composer).
23 | * To be able to use the Redis ratelimit bucket, you need to pass an instance of `AthenaCache` as client option `http.ratelimitbucket.athena` to the client. 24 | */ 25 | interface RatelimitBucketInterface { 26 | /** 27 | * Initializes the bucket. 28 | * @param \CharlotteDunois\Yasmin\HTTP\APIManager $api 29 | * @param string $endpoint 30 | * @throws \RuntimeException 31 | */ 32 | function __construct(\CharlotteDunois\Yasmin\HTTP\APIManager $api, string $endpoint); 33 | 34 | /** 35 | * Destroys the bucket. 36 | */ 37 | function __destruct(); 38 | 39 | /** 40 | * Whether we are busy. 41 | * @return bool 42 | */ 43 | function isBusy(): bool; 44 | 45 | /** 46 | * Sets the busy flag (marking as running). 47 | * @param bool $busy 48 | * @return void 49 | */ 50 | function setBusy(bool $busy): void; 51 | 52 | /** 53 | * Sets the ratelimits from the response. 54 | * @param int|null $limit 55 | * @param int|null $remaining 56 | * @param float|null $resetTime Reset time in seconds with milliseconds. 57 | * @return \React\Promise\ExtendedPromiseInterface|void 58 | */ 59 | function handleRatelimit(?int $limit, ?int $remaining, ?float $resetTime); 60 | 61 | /** 62 | * Returns the endpoint this bucket is for. 63 | * @return string 64 | */ 65 | function getEndpoint(): string; 66 | 67 | /** 68 | * Returns the size of the queue. 69 | * @return int 70 | */ 71 | function size(): int; 72 | 73 | /** 74 | * Pushes a new request into the queue. 75 | * @param \CharlotteDunois\Yasmin\HTTP\APIRequest $request 76 | * @return $this 77 | */ 78 | function push(\CharlotteDunois\Yasmin\HTTP\APIRequest $request); 79 | 80 | /** 81 | * Unshifts a new request into the queue. Modifies remaining ratelimit. 82 | * @param \CharlotteDunois\Yasmin\HTTP\APIRequest $request 83 | * @return $this 84 | */ 85 | function unshift(\CharlotteDunois\Yasmin\HTTP\APIRequest $request); 86 | 87 | /** 88 | * Retrieves ratelimit meta data. 89 | * 90 | * The resolved value must be: 91 | * ``` 92 | * array( 93 | * 'limited' => bool, 94 | * 'resetTime' => float|null 95 | * ) 96 | * ``` 97 | * 98 | * @return \React\Promise\ExtendedPromiseInterface|array 99 | */ 100 | function getMeta(); 101 | 102 | /** 103 | * Returns the first queue item or false. Modifies remaining ratelimit. 104 | * @return \CharlotteDunois\Yasmin\HTTP\APIRequest|false 105 | */ 106 | function shift(); 107 | 108 | /** 109 | * Unsets all queue items. 110 | * @return void 111 | */ 112 | function clear(): void; 113 | } 114 | -------------------------------------------------------------------------------- /src/Interfaces/RoleStorageInterface.php: -------------------------------------------------------------------------------- 1 | guild = $guild; 51 | 52 | $this->entries = new \CharlotteDunois\Collect\Collection(); 53 | $this->users = new \CharlotteDunois\Collect\Collection(); 54 | $this->webhooks = new \CharlotteDunois\Collect\Collection(); 55 | 56 | foreach($audit['users'] as $user) { 57 | $usr = $this->client->users->patch($user); 58 | $this->users->set($usr->id, $usr); 59 | } 60 | 61 | foreach($audit['webhooks'] as $webhook) { 62 | $hook = new \CharlotteDunois\Yasmin\Models\Webhook($this->client, $webhook); 63 | $this->webhooks->set($hook->id, $hook); 64 | } 65 | 66 | foreach($audit['audit_log_entries'] as $entry) { 67 | $log = new \CharlotteDunois\Yasmin\Models\AuditLogEntry($this->client, $this, $entry); 68 | $this->entries->set($log->id, $log); 69 | } 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | * @return mixed 75 | * @internal 76 | */ 77 | function __get($name) { 78 | if(\property_exists($this, $name)) { 79 | return $this->$name; 80 | } 81 | 82 | return parent::__get($name); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Models/ClientBase.php: -------------------------------------------------------------------------------- 1 | client = $client; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | * @return mixed 40 | * @internal 41 | */ 42 | function __get($name) { 43 | switch($name) { 44 | case 'client': 45 | return $this->client; 46 | break; 47 | } 48 | 49 | return parent::__get($name); 50 | } 51 | 52 | /** 53 | * @internal 54 | * @return mixed 55 | */ 56 | function __debugInfo() { 57 | $vars = \get_object_vars($this); 58 | unset($vars['client']); 59 | 60 | return $vars; 61 | } 62 | 63 | /** 64 | * @return mixed 65 | * @internal 66 | */ 67 | function jsonSerialize() { 68 | $vars = parent::jsonSerialize(); 69 | unset($vars['client']); 70 | 71 | return $vars; 72 | } 73 | 74 | /** 75 | * @return string 76 | * @internal 77 | */ 78 | function serialize() { 79 | $vars = \get_object_vars($this); 80 | unset($vars['client']); 81 | 82 | return \serialize($vars); 83 | } 84 | 85 | /** 86 | * @return void 87 | * @internal 88 | */ 89 | function unserialize($data) { 90 | if(self::$serializeClient === null) { 91 | throw new \Exception('Unable to unserialize a class without ClientBase::$serializeClient being set'); 92 | } 93 | 94 | parent::unserialize($data); 95 | 96 | $this->client = self::$serializeClient; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Models/ClientStatus.php: -------------------------------------------------------------------------------- 1 | desktop = $clientStatus['desktop'] ?? null; 73 | $this->mobile = $clientStatus['mobile'] ?? null; 74 | $this->web = $clientStatus['web'] ?? null; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | * @return mixed 80 | * @throws \RuntimeException 81 | * @internal 82 | */ 83 | function __get($name) { 84 | if(\property_exists($this, $name)) { 85 | return $this->$name; 86 | } 87 | 88 | return parent::__get($name); 89 | } 90 | 91 | /** 92 | * @return mixed 93 | * @internal 94 | */ 95 | function jsonSerialize() { 96 | return array( 97 | 'desktop' => $this->desktop, 98 | 'mobile' => $this->mobile, 99 | 'web' => $this->web, 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Models/EmojiStorage.php: -------------------------------------------------------------------------------- 1 | guild = $guild; 28 | 29 | $this->baseStorageArgs[] = $this->guild; 30 | } 31 | 32 | /** 33 | * Resolves given data to an emoji. 34 | * @param \CharlotteDunois\Yasmin\Models\Emoji|\CharlotteDunois\Yasmin\Models\MessageReaction|string|int $emoji string/int = emoji ID 35 | * @return \CharlotteDunois\Yasmin\Models\Emoji 36 | * @throws \InvalidArgumentException 37 | */ 38 | function resolve($emoji) { 39 | if($emoji instanceof \CharlotteDunois\Yasmin\Models\Emoji) { 40 | return $emoji; 41 | } 42 | 43 | if($emoji instanceof \CharlotteDunois\Yasmin\Models\MessageReaction) { 44 | return $emoji->emoji; 45 | } 46 | 47 | if(\is_int($emoji)) { 48 | $emoji = (string) $emoji; 49 | } 50 | 51 | if(\is_string($emoji) && parent::has($emoji)) { 52 | return parent::get($emoji); 53 | } 54 | 55 | throw new \InvalidArgumentException('Unable to resolve unknown emoji'); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | * @param string $key 61 | * @return bool 62 | */ 63 | function has($key) { 64 | return parent::has($key); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | * @param string $key 70 | * @return \CharlotteDunois\Yasmin\Models\Emoji|null 71 | */ 72 | function get($key) { 73 | return parent::get($key); 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | * @param string $key 79 | * @param \CharlotteDunois\Yasmin\Models\Emoji $value 80 | * @return $this 81 | */ 82 | function set($key, $value) { 83 | parent::set($key, $value); 84 | if($this !== $this->client->emojis) { 85 | $this->client->emojis->set($key, $value); 86 | } 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | * @param string $key 94 | * @return $this 95 | */ 96 | function delete($key) { 97 | parent::delete($key); 98 | if($this !== $this->client->emojis) { 99 | $this->client->emojis->delete($key); 100 | } 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Factory to create (or retrieve existing) emojis. 107 | * @param array $data 108 | * @return \CharlotteDunois\Yasmin\Models\Emoji 109 | * @internal 110 | */ 111 | function factory(array $data) { 112 | if(parent::has($data['id'])) { 113 | $emoji = parent::get($data['id']); 114 | $emoji->_patch($data); 115 | return $emoji; 116 | } 117 | 118 | $emoji = new \CharlotteDunois\Yasmin\Models\Emoji($this->client, $this->guild, $data); 119 | 120 | if($emoji->id !== null) { 121 | $this->set($emoji->id, $emoji); 122 | $this->client->emojis->set($emoji->id, $emoji); 123 | } 124 | 125 | return $emoji; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Models/GuildBan.php: -------------------------------------------------------------------------------- 1 | guild = $guild; 45 | $this->user = $user; 46 | $this->reason = $reason; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | * @return mixed 52 | * @throws \RuntimeException 53 | * @internal 54 | */ 55 | function __get($name) { 56 | if(\property_exists($this, $name)) { 57 | return $this->$name; 58 | } 59 | 60 | return parent::__get($name); 61 | } 62 | 63 | /** 64 | * Unbans the user. 65 | * @param string $reason 66 | * @return \React\Promise\ExtendedPromiseInterface 67 | */ 68 | function unban(string $reason = '') { 69 | return $this->guild->unban($this->user, $reason); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Models/GuildMemberStorage.php: -------------------------------------------------------------------------------- 1 | guild = $guild; 28 | 29 | $this->baseStorageArgs[] = $this->guild; 30 | } 31 | 32 | /** 33 | * Resolves given data to a guildmember. 34 | * @param \CharlotteDunois\Yasmin\Models\GuildMember|\CharlotteDunois\Yasmin\Models\User|string|int $guildmember string/int = user ID 35 | * @return \CharlotteDunois\Yasmin\Models\GuildMember 36 | * @throws \InvalidArgumentException 37 | */ 38 | function resolve($guildmember) { 39 | if($guildmember instanceof \CharlotteDunois\Yasmin\Models\GuildMember) { 40 | return $guildmember; 41 | } 42 | 43 | if($guildmember instanceof \CharlotteDunois\Yasmin\Models\User) { 44 | $guildmember = $guildmember->id; 45 | } 46 | 47 | if(\is_int($guildmember)) { 48 | $guildmember = (string) $guildmember; 49 | } 50 | 51 | if(\is_string($guildmember) && parent::has($guildmember)) { 52 | return parent::get($guildmember); 53 | } 54 | 55 | throw new \InvalidArgumentException('Unable to resolve unknown guild member'); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | * @param string $key 61 | * @return bool 62 | */ 63 | function has($key) { 64 | return parent::has($key); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | * @param string $key 70 | * @return \CharlotteDunois\Yasmin\Models\GuildMember|null 71 | */ 72 | function get($key) { 73 | return parent::get($key); 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | * @param string $key 79 | * @param \CharlotteDunois\Yasmin\Models\GuildMember $value 80 | * @return $this 81 | */ 82 | function set($key, $value) { 83 | parent::set($key, $value); 84 | return $this; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | * @param string $key 90 | * @return $this 91 | */ 92 | function delete($key) { 93 | parent::delete($key); 94 | return $this; 95 | } 96 | 97 | /** 98 | * Factory to create (or retrieve existing) guild members. 99 | * @param array $data 100 | * @return \CharlotteDunois\Yasmin\Models\GuildMember 101 | * @internal 102 | */ 103 | function factory(array $data) { 104 | if(parent::has($data['user']['id'])) { 105 | $member = parent::get($data['user']['id']); 106 | $member->_patch($data); 107 | return $member; 108 | } 109 | 110 | $member = new \CharlotteDunois\Yasmin\Models\GuildMember($this->client, $this->guild, $data); 111 | $this->set($member->id, $member); 112 | return $member; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Models/GuildStorage.php: -------------------------------------------------------------------------------- 1 | client->guilds) { 75 | $this->client->guilds->delete($key); 76 | } 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Factory to create (or retrieve existing) guilds. 83 | * @param array $data 84 | * @param int|null $shardID 85 | * @return \CharlotteDunois\Yasmin\Models\Guild 86 | * @internal 87 | */ 88 | function factory(array $data, ?int $shardID = null) { 89 | if(parent::has($data['id'])) { 90 | $guild = parent::get($data['id']); 91 | $guild->_patch($data); 92 | return $guild; 93 | } 94 | 95 | $guild = new \CharlotteDunois\Yasmin\Models\Guild($this->client, $data, $shardID); 96 | $this->set($guild->id, $guild); 97 | return $guild; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Models/MessageActivity.php: -------------------------------------------------------------------------------- 1 | 1, 29 | 'SPECTATE' => 2, 30 | 'LISTEN' => 3, 31 | 'JOIN_REQUEST' => 5 32 | ); 33 | 34 | /** 35 | * The party ID associated with this message activity, or null. 36 | * @var string|null 37 | */ 38 | protected $partyID; 39 | 40 | /** 41 | * The message activity type. 42 | * @var int 43 | */ 44 | protected $type; 45 | 46 | /** 47 | * The user this message activity is for. 48 | * @var \CharlotteDunois\Yasmin\Models\User|null 49 | */ 50 | protected $user; 51 | 52 | /** 53 | * @internal 54 | */ 55 | function __construct(\CharlotteDunois\Yasmin\Client $client, array $activity) { 56 | parent::__construct($client); 57 | 58 | $this->partyID = \CharlotteDunois\Yasmin\Utils\DataHelpers::typecastVariable(($activity['party_id'] ?? null), 'string'); 59 | $this->type = (int) $activity['type']; 60 | 61 | if($activity['party_id'] !== null) { 62 | $name = \explode(':', $activity['party_id']); 63 | $uid = (string) ($name[1] ?? $name[0]); 64 | $this->user = $this->client->users->get($uid); 65 | } 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | * @return mixed 71 | * @throws \RuntimeException 72 | * @internal 73 | */ 74 | function __get($name) { 75 | if(\property_exists($this, $name)) { 76 | return $this->$name; 77 | } 78 | 79 | switch($name) { 80 | case 'activity': 81 | if($this->user) { 82 | $presence = $this->user->getPresence(); 83 | if($presence !== null && $presence->activity !== null) { 84 | return $presence->activity; 85 | } 86 | } 87 | 88 | return null; 89 | break; 90 | } 91 | 92 | return parent::__get($name); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Models/MessageApplication.php: -------------------------------------------------------------------------------- 1 | id = (string) $application['id']; 59 | $this->name = (string) $application['name']; 60 | $this->icon = $application['icon'] ?? null; 61 | $this->coverImage = $application['cover_image'] ?? null; 62 | $this->description = (string) $application['description']; 63 | 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | * @return mixed 69 | * @throws \RuntimeException 70 | * @internal 71 | */ 72 | function __get($name) { 73 | if(\property_exists($this, $name)) { 74 | return $this->$name; 75 | } 76 | 77 | return parent::__get($name); 78 | } 79 | 80 | /** 81 | * Returns the URL of the cover image. 82 | * @param int|null $size Any powers of 2 (16-2048). 83 | * @return string|null 84 | */ 85 | function getCoverImageURL(?int $size = null) { 86 | if($size & ($size - 1)) { 87 | throw new \InvalidArgumentException('Invalid size "'.$size.'", expected any powers of 2'); 88 | } 89 | 90 | if($this->coverImage !== null) { 91 | return \CharlotteDunois\Yasmin\HTTP\APIEndpoints::CDN['url'].\CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(\CharlotteDunois\Yasmin\HTTP\APIEndpoints::CDN['appicons'], $this->id, $this->coverImage).(!empty($size) ? '?size='.$size : ''); 92 | } 93 | 94 | return null; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Models/MessageStorage.php: -------------------------------------------------------------------------------- 1 | channel = $channel; 40 | 41 | $this->baseStorageArgs[] = $this->channel; 42 | 43 | $this->enabled = (bool) $this->client->getOption('messageCache', true); 44 | if($this->enabled) { 45 | $time = (int) $this->client->getOption('messageCacheLifetime', 0); 46 | $inv = (int) $this->client->getOption('messageSweepInterval', $time); 47 | 48 | if($inv > 0) { 49 | $this->timer = $this->client->addPeriodicTimer($inv, function () use ($time) { 50 | $this->sweep($time); 51 | }); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * @internal 58 | */ 59 | function __destruct() { 60 | if($this->timer) { 61 | $this->client->cancelTimer($this->timer); 62 | } 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | * @param string $key 68 | * @return bool 69 | */ 70 | function has($key) { 71 | return parent::has($key); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | * @param string $key 77 | * @return \CharlotteDunois\Yasmin\Models\Message|null 78 | */ 79 | function get($key) { 80 | return parent::get($key); 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | * @param string $key 86 | * @param \CharlotteDunois\Yasmin\Models\Message $value 87 | * @return $this 88 | */ 89 | function set($key, $value) { 90 | if(!$this->enabled) { 91 | return $this; 92 | } 93 | 94 | parent::set($key, $value); 95 | return $this; 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | * @param string $key 101 | * @return $this 102 | */ 103 | function delete($key) { 104 | parent::delete($key); 105 | return $this; 106 | } 107 | 108 | /** 109 | * Sweeps messages, deletes messages older than the parameter (timestamp - $time). Returns the amount of sweeped messages. 110 | * @param int $time 0 = clear all 111 | * @return int 112 | */ 113 | function sweep(int $time) { 114 | if($time <= 0) { 115 | $this->clear(); 116 | return; 117 | } 118 | 119 | $amount = 0; 120 | foreach($this->data as $key => $msg) { 121 | if($msg->createdTimestamp > (\time() - $time)) { 122 | $this->delete($msg->id); 123 | unset($msg); 124 | 125 | $amount++; 126 | } 127 | } 128 | 129 | return $amount; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Models/PartialChannel.php: -------------------------------------------------------------------------------- 1 | id = (string) $channel['id']; 61 | $this->name = $channel['name'] ?? null; 62 | $this->type = \CharlotteDunois\Yasmin\Models\ChannelStorage::CHANNEL_TYPES[$channel['type']]; 63 | $this->icon = $channel['icon'] ?? null; 64 | 65 | $this->createdTimestamp = (int) \CharlotteDunois\Yasmin\Utils\Snowflake::deconstruct($this->id)->timestamp; 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | * @return mixed 71 | * @throws \RuntimeException 72 | * @internal 73 | */ 74 | function __get($name) { 75 | if(\property_exists($this, $name)) { 76 | return $this->$name; 77 | } 78 | 79 | switch($name) { 80 | case 'createdAt': 81 | return \CharlotteDunois\Yasmin\Utils\DataHelpers::makeDateTime($this->createdTimestamp); 82 | break; 83 | } 84 | 85 | return parent::__get($name); 86 | } 87 | 88 | /** 89 | * Returns the group DM's icon URL, or null. 90 | * @param int|null $size One of 128, 256, 512, 1024 or 2048. 91 | * @param string $format One of png, jpg or webp. 92 | * @return string|null 93 | */ 94 | function getIconURL(?int $size = null, string $format = 'png') { 95 | if($size & ($size - 1)) { 96 | throw new \InvalidArgumentException('Invalid size "'.$size.'", expected any powers of 2'); 97 | } 98 | 99 | if($this->icon !== null) { 100 | return \CharlotteDunois\Yasmin\HTTP\APIEndpoints::CDN['url'].\CharlotteDunois\Yasmin\HTTP\APIEndpoints::format(\CharlotteDunois\Yasmin\HTTP\APIEndpoints::CDN['channelicons'], $this->id, $this->icon, $format).(!empty($size) ? '?size='.$size : ''); 101 | } 102 | 103 | return null; 104 | } 105 | 106 | /** 107 | * Automatically converts to the channel name. 108 | * @return string 109 | */ 110 | function __toString() { 111 | return $this->name; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Models/Presence.php: -------------------------------------------------------------------------------- 1 | userID = $this->client->users->patch($presence['user'])->id; 64 | 65 | $this->_patch($presence); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | * @return mixed 71 | * @throws \RuntimeException 72 | * @internal 73 | */ 74 | function __get($name) { 75 | if(\property_exists($this, $name)) { 76 | return $this->$name; 77 | } 78 | 79 | switch($name) { 80 | case 'user': 81 | return $this->client->users->get($this->userID); 82 | break; 83 | } 84 | 85 | return parent::__get($name); 86 | } 87 | 88 | /** 89 | * @return mixed 90 | * @internal 91 | */ 92 | function jsonSerialize() { 93 | return array( 94 | 'status' => $this->status, 95 | 'clientStatus' => $this->clientStatus, 96 | 'game' => $this->activity 97 | ); 98 | } 99 | 100 | /** 101 | * @return void 102 | * @internal 103 | */ 104 | function _patch(array $presence) { 105 | $this->activity = (!empty($presence['game']) ? (new \CharlotteDunois\Yasmin\Models\Activity($this->client, $presence['game'])) : null); 106 | $this->status = $presence['status']; 107 | $this->clientStatus = (!empty($presence['client_status']) ? (new \CharlotteDunois\Yasmin\Models\ClientStatus($presence['client_status'])) : null); 108 | $this->activities = (!empty($presence['activities']) ? \array_map(function (array $activitiy) { 109 | return (new \CharlotteDunois\Yasmin\Models\Activity($this->client, $activitiy)); 110 | }, $presence['activities']) : array()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Models/PresenceStorage.php: -------------------------------------------------------------------------------- 1 | enabled = (bool) $this->client->getOption('presenceCache', true); 28 | } 29 | 30 | /** 31 | * Resolves given data to a presence. 32 | * @param \CharlotteDunois\Yasmin\Models\Presence|\CharlotteDunois\Yasmin\Models\User|string|int $presence string/int = user ID 33 | * @return \CharlotteDunois\Yasmin\Models\Presence 34 | * @throws \InvalidArgumentException 35 | */ 36 | function resolve($presence) { 37 | if($presence instanceof \CharlotteDunois\Yasmin\Models\Presence) { 38 | return $presence; 39 | } 40 | 41 | if($presence instanceof \CharlotteDunois\Yasmin\Models\User) { 42 | $presence = $presence->id; 43 | } 44 | 45 | if(\is_int($presence)) { 46 | $presence = (string) $presence; 47 | } 48 | 49 | if(\is_string($presence) && parent::has($presence)) { 50 | return parent::get($presence); 51 | } 52 | 53 | throw new \InvalidArgumentException('Unable to resolve unknown presence'); 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | * @param string $key 59 | * @return bool 60 | */ 61 | function has($key) { 62 | return parent::has($key); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | * @param string $key 68 | * @return \CharlotteDunois\Yasmin\Models\Presence|null 69 | */ 70 | function get($key) { 71 | return parent::get($key); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | * @param string $key 77 | * @param \CharlotteDunois\Yasmin\Models\Presence $value 78 | * @return $this 79 | */ 80 | function set($key, $value) { 81 | if(!$this->enabled) { 82 | return $this; 83 | } 84 | 85 | parent::set($key, $value); 86 | if($this !== $this->client->presences) { 87 | $this->client->presences->set($key, $value); 88 | } 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | * @param string $key 96 | * @return $this 97 | */ 98 | function delete($key) { 99 | if(!$this->enabled) { 100 | return $this; 101 | } 102 | 103 | parent::delete($key); 104 | if($this !== $this->client->presences) { 105 | $this->client->presences->delete($key); 106 | } 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Factory to create presences. 113 | * @param array $data 114 | * @return \CharlotteDunois\Yasmin\Models\Presence 115 | * @internal 116 | */ 117 | function factory(array $data) { 118 | $presence = new \CharlotteDunois\Yasmin\Models\Presence($this->client, $data); 119 | $this->set($presence->userID, $presence); 120 | return $presence; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Models/RoleStorage.php: -------------------------------------------------------------------------------- 1 | guild = $guild; 28 | 29 | $this->baseStorageArgs[] = $this->guild; 30 | } 31 | 32 | /** 33 | * Resolves given data to a Role. 34 | * @param \CharlotteDunois\Yasmin\Models\Role|string|int $role string/int = role ID 35 | * @return \CharlotteDunois\Yasmin\Models\Role 36 | * @throws \InvalidArgumentException 37 | */ 38 | function resolve($role) { 39 | if($role instanceof \CharlotteDunois\Yasmin\Models\Role) { 40 | return $role; 41 | } 42 | 43 | if(\is_int($role)) { 44 | $role = (string) $role; 45 | } 46 | 47 | if(\is_string($role) && parent::has($role)) { 48 | return parent::get($role); 49 | } 50 | 51 | throw new \InvalidArgumentException('Unable to resolve unknown role'); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | * @param string $key 57 | * @return bool 58 | */ 59 | function has($key) { 60 | return parent::has($key); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | * @param string $key 66 | * @return \CharlotteDunois\Yasmin\Models\Role|null 67 | */ 68 | function get($key) { 69 | return parent::get($key); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | * @param string $key 75 | * @param \CharlotteDunois\Yasmin\Models\Role $value 76 | * @return $this 77 | */ 78 | function set($key, $value) { 79 | parent::set($key, $value); 80 | return $this; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | * @param string $key 86 | * @return $this 87 | */ 88 | function delete($key) { 89 | parent::delete($key); 90 | return $this; 91 | } 92 | 93 | /** 94 | * Factory to create (or retrieve existing) roles. 95 | * @param array $data 96 | * @return \CharlotteDunois\Yasmin\Models\Role 97 | * @internal 98 | */ 99 | function factory(array $data) { 100 | if(parent::has($data['id'])) { 101 | $role = parent::get($data['id']); 102 | $role->_patch($data); 103 | return $role; 104 | } 105 | 106 | $role = new \CharlotteDunois\Yasmin\Models\Role($this->client, $this->guild, $data); 107 | $this->set($role->id, $role); 108 | return $role; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Models/Shard.php: -------------------------------------------------------------------------------- 1 | id = $shardID; 37 | $this->ws = $connection; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | * @return mixed 43 | * @throws \RuntimeException 44 | * @internal 45 | */ 46 | function __get($name) { 47 | if(\property_exists($this, $name)) { 48 | return $this->$name; 49 | } 50 | 51 | return parent::__get($name); 52 | } 53 | 54 | /** 55 | * @return string 56 | * @internal 57 | */ 58 | function serialize() { 59 | $vars = \get_object_vars($this); 60 | unset($vars['client'], $vars['ws']); 61 | 62 | return \serialize($vars); 63 | } 64 | 65 | /** 66 | * @return string 67 | * @internal 68 | */ 69 | function __toString() { 70 | return ((string) $this->id); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Models/UserConnection.php: -------------------------------------------------------------------------------- 1 | user = $user; 58 | 59 | $this->id = (string) $connection['id']; 60 | $this->name = (string) $connection['name']; 61 | $this->type = (string) $connection['type']; 62 | $this->revoked = (bool) $connection['revoked']; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Models/VoiceRegion.php: -------------------------------------------------------------------------------- 1 | id = (string) $region['id']; 66 | $this->name = (string) $region['name']; 67 | $this->vip = (bool) $region['vip']; 68 | $this->optimal = (bool) $region['optimal']; 69 | $this->deprecated = (bool) $region['deprecated']; 70 | $this->custom = (bool) $region['custom']; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | * @return mixed 76 | * @throws \RuntimeException 77 | * @internal 78 | */ 79 | function __get($name) { 80 | if(\property_exists($this, $name)) { 81 | return $this->$name; 82 | } 83 | 84 | return parent::__get($name); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Utils/EventHelpers.php: -------------------------------------------------------------------------------- 1 | int, (if the event hasn't been found yet, this will define a timeout (in seconds) after which the promise gets rejected) 23 | * ) 24 | * ``` 25 | * 26 | * @param \CharlotteDunois\Events\EventEmitterInterface $emitter 27 | * @param string $event 28 | * @param callable|null $filter 29 | * @param array $options 30 | * @return \React\Promise\ExtendedPromiseInterface This promise is cancellable. 31 | * @throws \RangeException The exception the promise gets rejected with, if waiting times out. 32 | * @throws \OutOfBoundsException The exception the promise gets rejected with, if the promise gets cancelled. 33 | */ 34 | static function waitForEvent($emitter, string $event, ?callable $filter = null, array $options = array()) { 35 | $options['max'] = 1; 36 | $options['time'] = $options['time'] ?? 0; 37 | $options['errors'] = array('max'); 38 | 39 | $collector = new \CharlotteDunois\Yasmin\Utils\Collector($emitter, $event, function (...$a) { 40 | return [ 0, $a ]; 41 | }, $filter, $options); 42 | 43 | return $collector->collect()->then(function (\CharlotteDunois\Collect\Collection $bucket) { 44 | return $bucket->first(); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Utils/FileHelpers.php: -------------------------------------------------------------------------------- 1 | getContents($file); 74 | } 75 | 76 | return \React\Promise\resolve(\file_get_contents($rfile)); 77 | } elseif(\filter_var($file, FILTER_VALIDATE_URL)) { 78 | return \CharlotteDunois\Yasmin\Utils\URLHelpers::resolveURLToData($file); 79 | } 80 | 81 | return \React\Promise\reject(new \RuntimeException('Given file is not resolvable')); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Utils/ImageHelpers.php: -------------------------------------------------------------------------------- 1 | context = \inflate_init(\ZLIB_ENCODING_DEFLATE); 56 | if(!$this->context) { 57 | throw new \RuntimeException('Unable to initialize Zlib Inflate'); 58 | } 59 | } 60 | 61 | /** 62 | * Destroys the context. 63 | * @return void 64 | */ 65 | function destroy(): void { 66 | $this->context = null; 67 | } 68 | 69 | /** 70 | * Decompresses data. 71 | * @param string $data 72 | * @return string 73 | * @throws \CharlotteDunois\Yasmin\WebSocket\DiscordGatewayException 74 | */ 75 | function decompress(string $data): string { 76 | if(!$this->context) { 77 | throw new \CharlotteDunois\Yasmin\WebSocket\DiscordGatewayException('No inflate context initialized'); 78 | } 79 | 80 | $uncompressed = \inflate_add($this->context, $data); 81 | if($uncompressed === false) { 82 | throw new \CharlotteDunois\Yasmin\WebSocket\DiscordGatewayException('The inflate context was unable to decompress the data'); 83 | } 84 | 85 | return $uncompressed; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/WebSocket/DiscordGatewayException.php: -------------------------------------------------------------------------------- 1 | addFrame($frame); 74 | 75 | return $msg; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/WebSocket/Events/ChannelCreate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->factory($data); 30 | 31 | $prom = array(); 32 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\GuildChannelInterface) { 33 | foreach($channel->getPermissionOverwrites() as $overwrite) { 34 | if($overwrite->type === 'member' && $overwrite->target === null) { 35 | $prom[] = $channel->getGuild()->fetchMember($overwrite->id)->then(function (\CharlotteDunois\Yasmin\Models\GuildMember $member) use ($overwrite) { 36 | $overwrite->_patch(array('target' => $member)); 37 | }, function () { 38 | // Do nothing 39 | }); 40 | } 41 | } 42 | } 43 | 44 | \React\Promise\all($prom)->done(function () use ($channel) { 45 | $this->client->queuedEmit('channelCreate', $channel); 46 | }, array($this->client, 'handlePromiseRejection')); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/WebSocket/Events/ChannelDelete.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\ChannelInterface) { 31 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\GuildChannelInterface) { 32 | $channel->getGuild()->channels->delete($channel->getId()); 33 | } 34 | 35 | $this->client->channels->delete($channel->getId()); 36 | $this->client->queuedEmit('channelDelete', $channel); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/WebSocket/Events/ChannelPinsUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel) { 31 | $time = (!empty($data['last_pin_timestamp']) ? \CharlotteDunois\Yasmin\Utils\DataHelpers::makeDateTime((int) $data['last_pin_timestamp']) : null); 32 | $this->client->queuedEmit('channelPinsUpdate', $channel, $time); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/WebSocket/Events/ChannelUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $clones = $this->client->getOption('disableClones', array()); 34 | $this->clones = !($clones === true || \in_array('channelUpdate', (array) $clones)); 35 | } 36 | 37 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 38 | $channel = $this->client->channels->get($data['id']); 39 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\ChannelInterface) { 40 | $oldChannel = null; 41 | if($this->clones) { 42 | $oldChannel = clone $channel; 43 | } 44 | 45 | $channel->_patch($data); 46 | 47 | $prom = array(); 48 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\GuildChannelInterface) { 49 | foreach($channel->getPermissionOverwrites() as $overwrite) { 50 | if($overwrite->type === 'member' && $overwrite->target === null) { 51 | $prom[] = $channel->getGuild()->fetchMember($overwrite->id)->then(function (\CharlotteDunois\Yasmin\Models\GuildMember $member) use ($overwrite) { 52 | $overwrite->_patch(array('target' => $member)); 53 | }, function () { 54 | // Do nothing 55 | }); 56 | } 57 | } 58 | } 59 | 60 | \React\Promise\all($prom)->done(function () use ($channel, $oldChannel) { 61 | $this->client->queuedEmit('channelUpdate', $channel, $oldChannel); 62 | }, array($this->client, 'handlePromiseRejection')); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildBanAdd.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $user = $this->client->users->patch($data['user']); 32 | if($user) { 33 | $user = \React\Promise\resolve($user); 34 | } else { 35 | $user = $this->client->fetchUser($data['user']['id']); 36 | } 37 | 38 | $user->done(function (\CharlotteDunois\Yasmin\Models\User $user) use ($guild) { 39 | $this->client->queuedEmit('guildBanAdd', $guild, $user); 40 | }, array($this->client, 'handlePromiseRejection')); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildBanRemove.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $user = $this->client->users->patch($data['user']); 32 | if($user) { 33 | $user = \React\Promise\resolve($user); 34 | } else { 35 | $user = $this->client->fetchUser($data['user']['id']); 36 | } 37 | 38 | $user->done(function (\CharlotteDunois\Yasmin\Models\User $user) use ($guild) { 39 | $this->client->queuedEmit('guildBanRemove', $guild, $user); 40 | }, array($this->client, 'handlePromiseRejection')); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildCreate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $this->client->once('ready', function () { 34 | $this->ready = true; 35 | }); 36 | } 37 | 38 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 39 | $guild = $this->client->guilds->get($data['id']); 40 | if($guild) { 41 | if(empty($data['unavailable'])) { 42 | $guild->_patch($data); 43 | } 44 | 45 | if($this->ready) { 46 | $this->client->queuedEmit('guildUnavailable', $guild); 47 | } else { 48 | $ws->emit('guildCreate'); 49 | } 50 | } else { 51 | $guild = $this->client->guilds->factory($data, $ws->shardID); 52 | 53 | if(((bool) $this->client->getOption('fetchAllMembers', false)) && $guild->members->count() < $guild->memberCount) { 54 | $fetchAll = $guild->fetchMembers(); 55 | } elseif($guild->me === null) { 56 | $fetchAll = $guild->fetchMember($this->client->user->id); 57 | } else { 58 | $fetchAll = \React\Promise\resolve(); 59 | } 60 | 61 | $fetchAll->done(function () use ($guild, $ws) { 62 | if($this->ready) { 63 | $this->client->queuedEmit('guildCreate', $guild); 64 | } else { 65 | $ws->emit('guildCreate'); 66 | } 67 | }, array($this->client, 'handlePromiseRejection')); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildDelete.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['id']); 30 | if($guild) { 31 | foreach($guild->channels as $channel) { 32 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 33 | $channel->stopTyping(true); 34 | } 35 | } 36 | 37 | if(!empty($data['unavailable'])) { 38 | $guild->_patch(array('unavailable' => true)); 39 | $this->client->queuedEmit('guildUnavailable', $guild); 40 | } else { 41 | foreach($guild->channels as $channel) { 42 | $this->client->channels->delete($channel->getId()); 43 | } 44 | 45 | foreach($guild->emojis as $emoji) { 46 | $this->client->emojis->delete($emoji->id); 47 | } 48 | 49 | $this->client->guilds->delete($guild->id); 50 | $this->client->queuedEmit('guildDelete', $guild); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildEmojisUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $ids = array(); 32 | foreach($data['emojis'] as $emoji) { 33 | $ids[] = $emoji['id']; 34 | 35 | if($guild->emojis->has($emoji['id'])) { 36 | $guild->emojis->get($emoji['id'])->_patch($emoji); 37 | } else { 38 | $em = new \CharlotteDunois\Yasmin\Models\Emoji($this->client, $guild, $emoji); 39 | $guild->emojis->set($em->id, $em); 40 | } 41 | } 42 | 43 | foreach($guild->emojis as $emoji) { 44 | if(!in_array($emoji->id, $ids)) { 45 | $this->client->emojis->delete($emoji->id); 46 | $guild->emojis->delete($emoji->id); 47 | } 48 | } 49 | 50 | $this->client->queuedEmit('guildEmojisUpdate', $guild); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildIntegrationsUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $this->client->queuedEmit('guildIntegrationsUpdate', $guild); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildMemberAdd.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $guildmember = $guild->_addMember($data); 32 | $this->client->queuedEmit('guildMemberAdd', $guildmember); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildMemberRemove.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $guildmember = $guild->_removeMember($data['user']['id']); 32 | if($guildmember) { 33 | $this->client->queuedEmit('guildMemberRemove', $guildmember); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildMemberUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $clones = $this->client->getOption('disableClones', array()); 34 | $this->clones = !($clones === true || \in_array('guildMemberUpdate', (array) $clones)); 35 | } 36 | 37 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 38 | $guild = $this->client->guilds->get($data['guild_id']); 39 | if($guild) { 40 | $guildmember = $guild->members->get($data['user']['id']); 41 | if($guildmember) { 42 | $oldMember = null; 43 | if($this->clones) { 44 | $oldMember = clone $guildmember; 45 | } 46 | 47 | $guildmember->_patch($data); 48 | $this->client->queuedEmit('guildMemberUpdate', $guildmember, $oldMember); 49 | } else { 50 | $guild->fetchMember($data['user']['id'])->done(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildMembersChunk.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $members = new \CharlotteDunois\Collect\Collection(); 32 | foreach($data['members'] as $mdata) { 33 | $member = $guild->_addMember($mdata, true); 34 | $members->set($member->id, $member); 35 | } 36 | 37 | $this->client->queuedEmit('guildMembersChunk', $guild, $members); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildRoleCreate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $role = $guild->roles->factory($data['role']); 32 | $this->client->queuedEmit('roleCreate', $role); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildRoleDelete.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $guild = $this->client->guilds->get($data['guild_id']); 30 | if($guild) { 31 | $role = $guild->roles->get($data['role_id']); 32 | if($role) { 33 | $guild->roles->delete($role->id); 34 | $this->client->queuedEmit('roleDelete', $role); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildRoleUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $clones = $this->client->getOption('disableClones', array()); 34 | $this->clones = !($clones === true || \in_array('roleUpdate', (array) $clones)); 35 | } 36 | 37 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 38 | $guild = $this->client->guilds->get($data['guild_id']); 39 | if($guild) { 40 | $role = $guild->roles->get($data['role']['id']); 41 | if($role) { 42 | $oldRole = null; 43 | if($this->clones) { 44 | $oldRole = clone $role; 45 | } 46 | 47 | $role->_patch($data['role']); 48 | $this->client->queuedEmit('roleUpdate', $role, $oldRole); 49 | } else { 50 | $role = $guild->roles->factory($data['role']); 51 | $this->client->queuedEmit('roleCreate', $role); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/WebSocket/Events/GuildUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $clones = $this->client->getOption('disableClones', array()); 34 | $this->clones = !($clones === true || \in_array('guildUpdate', (array) $clones)); 35 | } 36 | 37 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 38 | $guild = $this->client->guilds->get($data['id']); 39 | if($guild) { 40 | if(($data['unavailable'] ?? false)) { 41 | $guild->_patch(array('unavailable' => true)); 42 | $this->client->queuedEmit('guildUnavailable', $guild); 43 | return; 44 | } 45 | 46 | $oldGuild = null; 47 | if($this->clones) { 48 | $oldGuild = clone $guild; 49 | } 50 | 51 | $guild->_patch($data); 52 | 53 | $this->client->queuedEmit('guildUpdate', $guild, $oldGuild); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageCreate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 31 | $user = $this->client->users->patch($data['author']); 32 | 33 | if(!empty($data['member']) && $channel instanceof \CharlotteDunois\Yasmin\Models\TextChannel && !$channel->getGuild()->members->has($user->id)) { 34 | $member = $data['member']; 35 | $member['user'] = array('id' => $user->id); 36 | $channel->getGuild()->_addMember($member, true); 37 | } 38 | 39 | $message = $channel->_createMessage($data); 40 | 41 | if($message->guild && $message->mentions->users->count() > 0 && $message->mentions->users->count() > $message->mentions->members->count()) { 42 | $promise = array(); 43 | 44 | foreach($message->mentions->users as $user) { 45 | $promise[] = $message->guild->fetchMember($user->id)->then(function (\CharlotteDunois\Yasmin\Models\GuildMember $member) use ($message) { 46 | $message->mentions->members->set($member->id, $member); 47 | }, function () { 48 | // Ignore failure 49 | }); 50 | } 51 | 52 | $prm = \React\Promise\all($promise); 53 | } else { 54 | $prm = \React\Promise\resolve(); 55 | } 56 | 57 | $prm->done(function () use ($message) { 58 | if($message->guild && !($message->member instanceof \CharlotteDunois\Yasmin\Models\GuildMember) && !$message->author->webhook) { 59 | return $message->guild->fetchMember($message->author->id)->then(null, function () { 60 | // Ignore failure 61 | }); 62 | } 63 | 64 | $this->client->queuedEmit('message', $message); 65 | }, array($this->client, 'handlePromiseRejection')); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageDelete.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 31 | $message = $channel->getMessages()->get($data['id']); 32 | if($message instanceof \CharlotteDunois\Yasmin\Models\Message) { 33 | $channel->getMessages()->delete($message->id); 34 | $this->client->queuedEmit('messageDelete', $message); 35 | } else { 36 | $this->client->queuedEmit('messageDeleteRaw', $channel, $data['id']); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageDeleteBulk.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 31 | $messages = new \CharlotteDunois\Collect\Collection(); 32 | $messagesRaw = array(); 33 | 34 | foreach($data['ids'] as $id) { 35 | $message = $channel->getMessages()->get($id); 36 | if($message instanceof \CharlotteDunois\Yasmin\Models\Message) { 37 | $channel->getMessages()->delete($message->id); 38 | $messages->set($message->id, $message); 39 | } else { 40 | $messagesRaw[] = $id; 41 | } 42 | } 43 | 44 | if($messages->count() > 0) { 45 | $this->client->queuedEmit('messageDeleteBulk', $messages); 46 | } 47 | 48 | if(\count($messagesRaw) > 0) { 49 | $this->client->queuedEmit('messageDeleteBulkRaw', $channel, $messagesRaw); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageReactionAdd.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 31 | $message = $channel->getMessages()->get($data['message_id']); 32 | $reaction = null; 33 | 34 | if($message) { 35 | $reaction = $message->_addReaction($data); 36 | $message = \React\Promise\resolve($message); 37 | } else { 38 | $message = $channel->fetchMessage($data['message_id']); 39 | } 40 | 41 | $message->done(function (\CharlotteDunois\Yasmin\Models\Message $message) use ($data, $reaction) { 42 | if($reaction === null) { 43 | $id = (!empty($data['emoji']['id']) ? ((string) $data['emoji']['id']) : $data['emoji']['name']); 44 | $reaction = $message->reactions->get($id); 45 | } 46 | 47 | $this->client->fetchUser($data['user_id'])->done(function (\CharlotteDunois\Yasmin\Models\User $user) use ($reaction) { 48 | $reaction->users->set($user->id, $user); 49 | $this->client->queuedEmit('messageReactionAdd', $reaction, $user); 50 | }, array($this->client, 'handlePromiseRejection')); 51 | }, function () { 52 | // Don't handle it 53 | }); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageReactionRemove.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 31 | $id = (!empty($data['emoji']['id']) ? ((string) $data['emoji']['id']) : $data['emoji']['name']); 32 | 33 | $message = $channel->getMessages()->get($data['message_id']); 34 | $reaction = null; 35 | 36 | if($message) { 37 | $reaction = $message->reactions->get($id); 38 | if($reaction !== null) { 39 | $reaction->_decrementCount(); 40 | 41 | if($reaction->users->has($data['user_id'])) { 42 | $reaction->_patch(array('me' => false)); 43 | } 44 | } 45 | 46 | $message = \React\Promise\resolve($message); 47 | } else { 48 | $message = $channel->fetchMessage($data['message_id']); 49 | } 50 | 51 | $message->done(function (\CharlotteDunois\Yasmin\Models\Message $message) use ($data, $channel, $id, $reaction) { 52 | if(!$reaction) { 53 | $reaction = $message->reactions->get($id); 54 | if(!$reaction) { 55 | $emoji = $this->client->emojis->get($id); 56 | if(!$emoji) { 57 | $guild = ($channel instanceof \CharlotteDunois\Yasmin\Interfaces\GuildChannelInterface ? $channel->getGuild() : null); 58 | 59 | $emoji = new \CharlotteDunois\Yasmin\Models\Emoji($this->client, $guild, $data['emoji']); 60 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\GuildChannelInterface) { 61 | $channel->guild->emojis->set($id, $emoji); 62 | } 63 | 64 | $this->client->emojis->set($id, $emoji); 65 | } 66 | 67 | $reaction = new \CharlotteDunois\Yasmin\Models\MessageReaction($this->client, $message, $emoji, array( 68 | 'count' => 0, 69 | 'me' => false, 70 | 'emoji' => $emoji 71 | )); 72 | } 73 | } 74 | 75 | $this->client->fetchUser($data['user_id'])->done(function (\CharlotteDunois\Yasmin\Models\User $user) use ($id, $message, $reaction) { 76 | $reaction->users->delete($user->id); 77 | if($reaction->count === 0) { 78 | $message->reactions->delete($id); 79 | } 80 | 81 | $this->client->queuedEmit('messageReactionRemove', $reaction, $user); 82 | }, array($this->client, 'handlePromiseRejection')); 83 | }, function () { 84 | // Don't handle it 85 | }); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageReactionRemoveAll.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $channel = $this->client->channels->get($data['channel_id']); 30 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 31 | $message = $channel->getMessages()->get($data['message_id']); 32 | if($message) { 33 | $message = \React\Promise\resolve($message); 34 | } else { 35 | $message = $channel->fetchMessage($data['message_id']); 36 | } 37 | 38 | $message->done(function (\CharlotteDunois\Yasmin\Models\Message $message) { 39 | $message->reactions->clear(); 40 | $this->client->queuedEmit('messageReactionRemoveAll', $message); 41 | }, function () { 42 | // Don't handle it 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/WebSocket/Events/MessageUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $clones = $this->client->getOption('disableClones', array()); 34 | $this->clones = !($clones === true || \in_array('messageUpdate', (array) $clones)); 35 | } 36 | 37 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 38 | $channel = $this->client->channels->get($data['channel_id']); 39 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 40 | $message = $channel->getMessages()->get($data['id']); 41 | if($message instanceof \CharlotteDunois\Yasmin\Models\Message) { 42 | $oldMessage = null; 43 | if($this->clones) { 44 | $oldMessage = clone $message; 45 | } 46 | 47 | $message->_patch($data); 48 | 49 | $this->client->queuedEmit('messageUpdate', $message, $oldMessage); 50 | } else { 51 | $this->client->queuedEmit('messageUpdateRaw', $channel, $data); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/WebSocket/Events/PresenceUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 38 | 39 | $clones = $this->client->getOption('disableClones', array()); 40 | $this->clones = !($clones === true || \in_array('presenceUpdate', (array) $clones)); 41 | $this->ignoreUnknown = (bool) $this->client->getOption('ws.presenceUpdate.ignoreUnknownUsers', false); 42 | } 43 | 44 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 45 | $user = $this->client->users->get($data['user']['id']); 46 | 47 | if(($data['status'] ?? null) === 'offline' && $user === null) { 48 | return; 49 | } 50 | 51 | if($user === null) { 52 | if($this->ignoreUnknown) { 53 | return; 54 | } 55 | 56 | $user = $this->client->fetchUser($data['user']['id']); 57 | } else { 58 | if(\count($data['user']) > 1 && $user->_shouldUpdate($data['user'])) { 59 | $oldUser = null; 60 | if($this->clones) { 61 | $oldUser = clone $user; 62 | } 63 | 64 | $user->_patch($data['user']); 65 | 66 | $this->client->queuedEmit('userUpdate', $user, $oldUser); 67 | return; 68 | } 69 | 70 | $user = \React\Promise\resolve($user); 71 | } 72 | 73 | $user->done(function (\CharlotteDunois\Yasmin\Models\User $user) use ($data) { 74 | $guild = $this->client->guilds->get($data['guild_id']); 75 | if($guild) { 76 | $presence = $guild->presences->get($user->id); 77 | $oldPresence = null; 78 | 79 | if($presence) { 80 | if($data['status'] === 'offline' && $presence->status === 'offline') { 81 | return; 82 | } 83 | 84 | if($this->clones) { 85 | $oldPresence = clone $presence; 86 | } 87 | 88 | $presence->_patch($data); 89 | } else { 90 | $presence = $guild->presences->factory($data); 91 | } 92 | 93 | $this->client->queuedEmit('presenceUpdate', $presence, $oldPresence); 94 | } 95 | }, array($this->client, 'handlePromiseRejection')); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/WebSocket/Events/Ready.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $this->client->once('ready', function () { 34 | $this->ready = true; 35 | }); 36 | } 37 | 38 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 39 | if(empty($data['user']['bot'])) { 40 | $ws->emit('self.error', 'User accounts are not supported'); 41 | return; 42 | } 43 | 44 | $ws->setAuthenticated(true); 45 | $ws->setSessionID($data['session_id']); 46 | 47 | $ws->emit('self.ready'); 48 | 49 | if($this->ready && $this->client->user !== null) { 50 | $this->client->user->_patch($data['user']); 51 | $this->client->wsmanager()->emit('ready'); 52 | return; 53 | } 54 | 55 | if($this->client->user === null) { 56 | $this->client->setClientUser($data['user']); 57 | } 58 | 59 | $unavailableGuilds = 0; 60 | 61 | foreach($data['guilds'] as $guild) { 62 | if(!$this->client->guilds->has($guild['id'])) { 63 | $guild = new \CharlotteDunois\Yasmin\Models\Guild($this->client, $guild); 64 | $this->client->guilds->set($guild->id, $guild); 65 | } 66 | 67 | $unavailableGuilds++; 68 | } 69 | 70 | // Already ready 71 | if($unavailableGuilds === 0) { 72 | $this->client->wsmanager()->emit('self.ws.ready'); 73 | return; 74 | } 75 | 76 | // Emit ready after waiting N guilds * 1.2 seconds - we waited long enough for Discord to get the guilds to us 77 | $gtime = \ceil(($unavailableGuilds * 1.2)); 78 | $timer = $this->client->addTimer(\max(5, $gtime), function () use (&$unavailableGuilds) { 79 | if($unavailableGuilds > 0) { 80 | $this->client->wsmanager()->emit('self.ws.ready'); 81 | } 82 | }); 83 | 84 | $listener = function () use ($timer, $ws, &$listener, &$unavailableGuilds) { 85 | $unavailableGuilds--; 86 | 87 | if($unavailableGuilds <= 0) { 88 | $this->client->cancelTimer($timer); 89 | $ws->removeListener('guildCreate', $listener); 90 | 91 | $this->client->wsmanager()->emit('self.ws.ready'); 92 | } 93 | }; 94 | 95 | $ws->on('guildCreate', $listener); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/WebSocket/Events/Resumed.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $ws->emit('self.ready'); 30 | $ws->emit('self.ws.ready'); 31 | $this->client->wsmanager()->emit('ready'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/WebSocket/Events/TypingStart.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $this->client->once('ready', function () { 34 | $this->ready = true; 35 | }); 36 | } 37 | 38 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 39 | if(!$this->ready) { 40 | return; 41 | } 42 | 43 | $channel = $this->client->channels->get($data['channel_id']); 44 | if($channel instanceof \CharlotteDunois\Yasmin\Interfaces\TextChannelInterface) { 45 | $user = $this->client->users->get($data['user_id']); 46 | if(!$user) { 47 | $user = $this->client->fetchUser($data['user_id']); 48 | } else { 49 | $user = \React\Promise\resolve($user); 50 | } 51 | 52 | $user->done(function (\CharlotteDunois\Yasmin\Models\User $user) use ($channel, $data) { 53 | if(!empty($data['member']) && $channel instanceof \CharlotteDunois\Yasmin\Models\TextChannel && !$channel->getGuild()->members->has($user->id)) { 54 | $member = $data['member']; 55 | $member['user'] = array('id' => $user->id); 56 | $channel->getGuild()->_addMember($member, true); 57 | } 58 | 59 | if($channel->_updateTyping($user, $data['timestamp'])) { 60 | $this->client->queuedEmit('typingStart', $channel, $user); 61 | } 62 | }, array($this->client, 'handlePromiseRejection')); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/WebSocket/Events/UserUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 32 | 33 | $clones = $this->client->getOption('disableClones', array()); 34 | $this->clones = !($clones === true || \in_array('userUpdate', (array) $clones)); 35 | } 36 | 37 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 38 | $user = $this->client->users->get($data['id']); 39 | if($user) { 40 | $oldUser = null; 41 | if($this->clones) { 42 | $oldUser = clone $user; 43 | } 44 | 45 | $user->_patch($data); 46 | 47 | $this->client->queuedEmit('userUpdate', $user, $oldUser); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/WebSocket/Events/VoiceServerUpdate.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 29 | $this->client->queuedEmit('voiceServerUpdate', $data); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/WebSocket/Handlers/Heartbeat.php: -------------------------------------------------------------------------------- 1 | wshandler = $wshandler; 22 | } 23 | 24 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $packet): void { 25 | $ws->heartbeat(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/WebSocket/Handlers/HeartbeatAck.php: -------------------------------------------------------------------------------- 1 | wshandler = $wshandler; 21 | } 22 | 23 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $packet): void { 24 | $ws->_pong(\microtime(true)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/WebSocket/Handlers/Hello.php: -------------------------------------------------------------------------------- 1 | wshandler = $wshandler; 24 | } 25 | 26 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $packet): void { 27 | $this->wshandler->wsmanager->client->emit('debug', 'Shard '.$ws->shardID.' connected to Gateway'); 28 | 29 | $this->wshandler->wsmanager->setLastIdentified(\time()); 30 | $ws->sendIdentify(); 31 | 32 | $interval = $packet['d']['heartbeat_interval'] / 1000; 33 | $ws->ratelimits['heartbeatRoom'] = (int) \ceil($ws->ratelimits['total'] / $interval); 34 | 35 | $ws->heartbeat = $this->wshandler->wsmanager->client->loop->addPeriodicTimer($interval, function () use (&$ws) { 36 | $ws->heartbeat(); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/WebSocket/Handlers/InvalidSession.php: -------------------------------------------------------------------------------- 1 | wshandler = $wshandler; 21 | } 22 | 23 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $data): void { 24 | if(!$data['d']) { 25 | $ws->setSessionID(null); 26 | } 27 | 28 | $this->wshandler->wsmanager->client->addTimer(\mt_rand(1, 5), function () use (&$ws) { 29 | $ws->sendIdentify(); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/WebSocket/Handlers/Reconnect.php: -------------------------------------------------------------------------------- 1 | wshandler = $wshandler; 21 | } 22 | 23 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $packet): void { 24 | $ws->reconnect($packet['d']); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/WebSocket/WSHandler.php: -------------------------------------------------------------------------------- 1 | wsmanager = $wsmanager; 37 | 38 | $this->register(\CharlotteDunois\Yasmin\WebSocket\WSManager::OPCODES['DISPATCH'], \CharlotteDunois\Yasmin\WebSocket\Handlers\Dispatch::class); 39 | $this->register(\CharlotteDunois\Yasmin\WebSocket\WSManager::OPCODES['HEARTBEAT'], \CharlotteDunois\Yasmin\WebSocket\Handlers\Heartbeat::class); 40 | $this->register(\CharlotteDunois\Yasmin\WebSocket\WSManager::OPCODES['RECONNECT'], \CharlotteDunois\Yasmin\WebSocket\Handlers\Reconnect::class); 41 | $this->register(\CharlotteDunois\Yasmin\WebSocket\WSManager::OPCODES['INVALID_SESSION'], \CharlotteDunois\Yasmin\WebSocket\Handlers\InvalidSession::class); 42 | $this->register(\CharlotteDunois\Yasmin\WebSocket\WSManager::OPCODES['HELLO'], \CharlotteDunois\Yasmin\WebSocket\Handlers\Hello::class); 43 | $this->register(\CharlotteDunois\Yasmin\WebSocket\WSManager::OPCODES['HEARTBEAT_ACK'], \CharlotteDunois\Yasmin\WebSocket\Handlers\HeartbeatAck::class); 44 | } 45 | 46 | function __get($name) { 47 | if(\property_exists($this, $name)) { 48 | return $this->$name; 49 | } 50 | 51 | throw new \RuntimeException('Undefined property: '.\get_class($this).'::$'.$name); 52 | } 53 | 54 | /** 55 | * Returns a WS handler. 56 | * @return \CharlotteDunois\Yasmin\Interfaces\WSHandlerInterface 57 | */ 58 | function getHandler(int $name) { 59 | if(isset($this->handlers[$name])) { 60 | return $this->handlers[$name]; 61 | } 62 | 63 | throw new \Exception('Unable to find handler'); 64 | } 65 | 66 | /** 67 | * Handles a message. 68 | * @return void 69 | */ 70 | function handle(\CharlotteDunois\Yasmin\WebSocket\WSConnection $ws, $message) { 71 | $packet = $this->wsmanager->encoding->decode($message); 72 | $this->wsmanager->client->emit('raw', $packet); 73 | 74 | if(isset($packet['s'])) { 75 | $ws->setSequence($packet['s']); 76 | } 77 | 78 | $this->wsmanager->emit('debug', 'Shard '.$ws->shardID.' received WS packet with OP code '.$packet['op']); 79 | 80 | if(isset($this->handlers[$packet['op']])) { 81 | $this->handlers[$packet['op']]->handle($ws, $packet); 82 | } 83 | } 84 | 85 | /** 86 | * Registers a handler. 87 | * @return void 88 | * @throws \RuntimeException 89 | */ 90 | function register(int $op, string $class) { 91 | if(!\in_array('CharlotteDunois\Yasmin\Interfaces\WSHandlerInterface', \class_implements($class))) { 92 | throw new \RuntimeException('Specified handler class does not implement interface'); 93 | } 94 | 95 | $this->handlers[$op] = new $class($this); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/WebhookClient.php: -------------------------------------------------------------------------------- 1 | $id, 37 | 'token' => $token 38 | )); 39 | } 40 | } 41 | --------------------------------------------------------------------------------