├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── FEATURE_REQUEST.yml │ ├── ISSUE_TEMPLATE.yml │ └── config.yml └── workflows │ └── codeql-analysis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── discord ├── __init__.py ├── __main__.py ├── abc.py ├── activity.py ├── appinfo.py ├── asset.py ├── audit_logs.py ├── backoff.py ├── bin │ ├── libopus-0.x64.dll │ └── libopus-0.x86.dll ├── calls.py ├── channel.py ├── client.py ├── colour.py ├── components.py ├── context_managers.py ├── embeds.py ├── emoji.py ├── enums.py ├── errors.py ├── ext │ ├── commands │ │ ├── __init__.py │ │ ├── _types.py │ │ ├── bot.py │ │ ├── cog.py │ │ ├── context.py │ │ ├── converter.py │ │ ├── cooldowns.py │ │ ├── core.py │ │ ├── errors.py │ │ ├── help.py │ │ └── view.py │ └── tasks │ │ └── __init__.py ├── file.py ├── flags.py ├── gateway.py ├── guild.py ├── http.py ├── integrations.py ├── interactions.py ├── invite.py ├── iterators.py ├── member.py ├── mentions.py ├── message.py ├── mixins.py ├── object.py ├── oggparse.py ├── opus.py ├── partial_emoji.py ├── permissions.py ├── player.py ├── raw_models.py ├── reaction.py ├── relationship.py ├── role.py ├── shard.py ├── state.py ├── sticker.py ├── team.py ├── template.py ├── user.py ├── utils.py ├── voice_client.py ├── webhook.py └── widget.py ├── docs ├── Makefile ├── _build │ ├── doctrees │ │ ├── components.doctree │ │ ├── environment.pickle │ │ ├── index.doctree │ │ └── interaction.doctree │ └── html │ │ ├── _sources │ │ ├── components.rst.txt │ │ ├── index.rst.txt │ │ └── interaction.rst.txt │ │ ├── _static │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── codeblocks.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── custom.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── fonts │ │ │ ├── Inconsolata-Bold.ttf │ │ │ ├── Inconsolata-Regular.ttf │ │ │ ├── Inconsolata.ttf │ │ │ ├── Lato-Bold.ttf │ │ │ ├── Lato-Regular.ttf │ │ │ ├── Lato │ │ │ │ ├── lato-bold.eot │ │ │ │ ├── lato-bold.ttf │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ ├── lato-bolditalic.woff2 │ │ │ │ ├── lato-italic.eot │ │ │ │ ├── lato-italic.ttf │ │ │ │ ├── lato-italic.woff │ │ │ │ ├── lato-italic.woff2 │ │ │ │ ├── lato-regular.eot │ │ │ │ ├── lato-regular.ttf │ │ │ │ ├── lato-regular.woff │ │ │ │ └── lato-regular.woff2 │ │ │ ├── RobotoSlab-Bold.ttf │ │ │ ├── RobotoSlab-Regular.ttf │ │ │ ├── RobotoSlab │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── jquery-3.5.1.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ ├── modernizr.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── style.css │ │ ├── underscore-1.13.1.js │ │ └── underscore.js │ │ ├── components.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── interaction.html │ │ ├── objects.inv │ │ ├── search.html │ │ └── searchindex.js ├── _static │ └── codeblocks.css ├── additions.rst ├── components.rst ├── conf.py ├── imgs │ ├── blurple.png │ ├── green.png │ ├── grey.png │ ├── ict4example.gif │ ├── ict5example.gif │ ├── ict6example.gif │ ├── ict7example.gif │ ├── long.png │ ├── long_date.png │ ├── long_time.png │ ├── red.png │ ├── relative.png │ ├── rtd-logo-wordmark-light.png │ ├── short.png │ ├── short_date.png │ ├── short_time.png │ └── url.png ├── index.rst ├── interaction.rst ├── make.bat └── requirements.txt ├── examples.py ├── images └── rtd-logo-wordmark-light.png ├── pyproject.toml ├── readthedocs.yaml ├── requirements.txt ├── setup.cfg ├── setup.py └── version.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mccoderpy] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a feature that should be added to discord4py 3 | title: "[Feature Request]:" 4 | labels: ["feature request"] 5 | assignees: ["mccoderpy"] 6 | body: 7 | - type: input 8 | attributes: 9 | label: Summary 10 | description: > 11 | A short summary of what your feature request is about. 12 | validations: 13 | required: true 14 | - type: dropdown 15 | attributes: 16 | multiple: false 17 | label: What is the feature request for? 18 | options: 19 | - The core library 20 | - discord.ext.commands 21 | - discord.ext.tasks 22 | - The documentation 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: The Problem 28 | description: > 29 | What problem is your feature trying to solve? 30 | What becomes easier or possible when this feature is implemented? 31 | validations: 32 | required: true 33 | - type: textarea 34 | attributes: 35 | label: The Ideal Solution 36 | description: > 37 | What is your ideal solution to the problem? 38 | What would you like this feature to do? 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: The Current Solution 44 | description: > 45 | What is the current solution to the problem, if any? 46 | validations: 47 | required: false 48 | - type: textarea 49 | attributes: 50 | label: Additional Context 51 | description: If there is anything else to say, please do so here. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | assignees: ["mccoderpy"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Please fill in the fields below as accurately as possible to help us fix the bug! 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: Description 14 | description: Also tell us, what did you expect to happen? 15 | placeholder: Tell us what you are trying and what is not working. 16 | validations: 17 | required: true 18 | - type: dropdown 19 | id: branch 20 | attributes: 21 | label: Branch used 22 | description: What branch of the library are you using? 23 | options: 24 | - main (stable) 25 | - developer 26 | validations: 27 | required: true 28 | - type: markdown 29 | attributes: 30 | value: | 31 | For the next 3 fields use the output of the `version` command 32 | ```ps 33 | # Windows 34 | py -m discord --version 35 | 36 | # Linux/macOS 37 | python3 -m discord --version # Replace 'python3' with the prefix of the Python version you are using 38 | ``` 39 | - type: input 40 | id: python-version 41 | attributes: 42 | label: The Python version you are using. 43 | placeholder: Python v3.8.10-final 44 | validations: 45 | required: true 46 | - type: input 47 | id: version-output 48 | attributes: 49 | label: What version (and release) of the library are you using 50 | placeholder: v2.0a294+gfb291a6 51 | validations: 52 | required: true 53 | - type: input 54 | id: system-info 55 | attributes: 56 | label: System info 57 | placeholder: Linux 5.4.0-126-generic 58 | validations: 59 | required: true 60 | - type: textarea 61 | id: traceback 62 | attributes: 63 | label: Full Traceback (Error) 64 | description: > 65 | Please copy and paste the complete traceback (error). 66 | This will be automatically formatted into code, so no need for backticks. 67 | render: shell 68 | validations: 69 | required: true 70 | - type: input 71 | id: contact-info 72 | attributes: 73 | label: Contact info 74 | description: Additional ways to contact you 75 | placeholder: mccuber04#2960 (Discord) 76 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord 4 | url: https://discord.gg/sb69muSqsg 5 | about: Official support server - get in contact with our community and devs 6 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '35 9 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | - name: Setup Python 74 | uses: actions/setup-python@v3.1.2 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2021 Rapptz & 2021-present mccoderpy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE.txt 3 | include discord/* 4 | include discord/bin/*.dll 5 | include discord/ext/commands/* 6 | include discord/ext/tasks/__init__.py -------------------------------------------------------------------------------- /discord/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Discord API Wrapper 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | A basic wrapper for the Discord API. 8 | 9 | :copyright: (c) 2015-present Rapptz 10 | :license: MIT, see LICENSE for more details. 11 | 12 | """ 13 | 14 | __title__ = 'discord' 15 | __author__ = 'Rapptz & mccoderpy' 16 | __license__ = 'MIT' 17 | __copyright__ = 'Copyright 2015-present Rapptz' 18 | __version__ = '1.7.5.4' 19 | 20 | __path__ = __import__('pkgutil').extend_path(__path__, __name__) 21 | 22 | from collections import namedtuple 23 | import logging 24 | 25 | from .client import Client 26 | from .appinfo import AppInfo 27 | from .user import User, ClientUser, Profile 28 | from .emoji import Emoji 29 | from .partial_emoji import PartialEmoji 30 | from .activity import * 31 | from .channel import * 32 | from .components import ActionRow, Button, SelectMenu, SelectOption 33 | from .guild import Guild 34 | from .flags import * 35 | from .relationship import Relationship 36 | from .member import Member, VoiceState 37 | from .message import * 38 | from .asset import Asset 39 | from .errors import * 40 | from .calls import CallMessage, GroupCall 41 | from .permissions import Permissions, PermissionOverwrite 42 | from .role import Role, RoleTags 43 | from .file import File 44 | from .colour import Color, Colour 45 | from .integrations import Integration, IntegrationAccount, BotIntegration, IntegrationApplication, StreamIntegration 46 | from .interactions import Interaction, ButtonClick, SelectionSelect 47 | from .invite import Invite, PartialInviteChannel, PartialInviteGuild 48 | from .template import Template 49 | from .widget import Widget, WidgetMember, WidgetChannel 50 | from .object import Object 51 | from .reaction import Reaction 52 | from . import utils, opus, abc 53 | from .enums import * 54 | from .embeds import Embed 55 | from .mentions import AllowedMentions 56 | from .shard import AutoShardedClient, ShardInfo 57 | from .player import * 58 | from .webhook import * 59 | from .voice_client import VoiceClient, VoiceProtocol 60 | from .audit_logs import AuditLogChanges, AuditLogEntry, AuditLogDiff 61 | from .raw_models import * 62 | from .team import * 63 | from .sticker import Sticker 64 | 65 | VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial') 66 | 67 | version_info = VersionInfo(major=1, minor=7, micro=2, releaselevel='final', serial=0) 68 | 69 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 70 | -------------------------------------------------------------------------------- /discord/appinfo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from . import utils 28 | from .user import User 29 | from .asset import Asset 30 | from .team import Team 31 | 32 | 33 | class AppInfo: 34 | """Represents the application info for the bot provided by Discord. 35 | 36 | 37 | Attributes 38 | ------------- 39 | id: :class:`int` 40 | The application ID. 41 | name: :class:`str` 42 | The application name. 43 | owner: :class:`User` 44 | The application owner. 45 | team: Optional[:class:`Team`] 46 | The application's team. 47 | 48 | .. versionadded:: 1.3 49 | 50 | icon: Optional[:class:`str`] 51 | The icon hash, if it exists. 52 | description: Optional[:class:`str`] 53 | The application description. 54 | bot_public: :class:`bool` 55 | Whether the bot can be invited by anyone or if it is locked 56 | to the application owner. 57 | bot_require_code_grant: :class:`bool` 58 | Whether the bot requires the completion of the full oauth2 code 59 | grant flow to join. 60 | rpc_origins: Optional[List[:class:`str`]] 61 | A list of RPC origin URLs, if RPC is enabled. 62 | summary: :class:`str` 63 | If this application is a game sold on Discord, 64 | this field will be the summary field for the store page of its primary SKU. 65 | 66 | .. versionadded:: 1.3 67 | 68 | verify_key: :class:`str` 69 | The hex encoded key for verification in interactions and the 70 | GameSDK's `GetTicket `_. 71 | 72 | .. versionadded:: 1.3 73 | 74 | guild_id: Optional[:class:`int`] 75 | If this application is a game sold on Discord, 76 | this field will be the guild to which it has been linked to. 77 | 78 | .. versionadded:: 1.3 79 | 80 | primary_sku_id: Optional[:class:`int`] 81 | If this application is a game sold on Discord, 82 | this field will be the id of the "Game SKU" that is created, 83 | if it exists. 84 | 85 | .. versionadded:: 1.3 86 | 87 | slug: Optional[:class:`str`] 88 | If this application is a game sold on Discord, 89 | this field will be the URL slug that links to the store page. 90 | 91 | .. versionadded:: 1.3 92 | 93 | cover_image: Optional[:class:`str`] 94 | If this application is a game sold on Discord, 95 | this field will be the hash of the image on store embeds 96 | 97 | .. versionadded:: 1.3 98 | """ 99 | __slots__ = ('_state', 'description', 'id', 'name', 'rpc_origins', 100 | 'bot_public', 'bot_require_code_grant', 'owner', 'icon', 101 | 'summary', 'verify_key', 'team', 'guild_id', 'primary_sku_id', 102 | 'slug', 'cover_image') 103 | 104 | def __init__(self, state, data): 105 | self._state = state 106 | 107 | self.id = int(data['id']) 108 | self.name = data['name'] 109 | self.description = data['description'] 110 | self.icon = data['icon'] 111 | self.rpc_origins = data['rpc_origins'] 112 | self.bot_public = data['bot_public'] 113 | self.bot_require_code_grant = data['bot_require_code_grant'] 114 | self.owner = User(state=self._state, data=data['owner']) 115 | 116 | team = data.get('team') 117 | self.team = Team(state, team) if team else None 118 | 119 | self.summary = data['summary'] 120 | self.verify_key = data['verify_key'] 121 | 122 | self.guild_id = utils._get_as_snowflake(data, 'guild_id') 123 | 124 | self.primary_sku_id = utils._get_as_snowflake(data, 'primary_sku_id') 125 | self.slug = data.get('slug') 126 | self.cover_image = data.get('cover_image') 127 | 128 | def __repr__(self): 129 | return '<{0.__class__.__name__} id={0.id} name={0.name!r} description={0.description!r} public={0.bot_public} ' \ 130 | 'owner={0.owner!r}>'.format(self) 131 | 132 | @property 133 | def icon_url(self): 134 | """:class:`.Asset`: Retrieves the application's icon asset. 135 | 136 | This is equivalent to calling :meth:`icon_url_as` with 137 | the default parameters ('webp' format and a size of 1024). 138 | 139 | .. versionadded:: 1.3 140 | """ 141 | return self.icon_url_as() 142 | 143 | def icon_url_as(self, *, format='webp', size=1024): 144 | """Returns an :class:`Asset` for the icon the application has. 145 | 146 | The format must be one of 'webp', 'jpeg', 'jpg' or 'png'. 147 | The size must be a power of 2 between 16 and 4096. 148 | 149 | .. versionadded:: 1.6 150 | 151 | Parameters 152 | ----------- 153 | format: :class:`str` 154 | The format to attempt to convert the icon to. Defaults to 'webp'. 155 | size: :class:`int` 156 | The size of the image to display. 157 | 158 | Raises 159 | ------ 160 | InvalidArgument 161 | Bad image format passed to ``format`` or invalid ``size``. 162 | 163 | Returns 164 | -------- 165 | :class:`Asset` 166 | The resulting CDN asset. 167 | """ 168 | return Asset._from_icon(self._state, self, 'app', format=format, size=size) 169 | 170 | 171 | @property 172 | def cover_image_url(self): 173 | """:class:`.Asset`: Retrieves the cover image on a store embed. 174 | 175 | This is equivalent to calling :meth:`cover_image_url_as` with 176 | the default parameters ('webp' format and a size of 1024). 177 | 178 | .. versionadded:: 1.3 179 | """ 180 | return self.cover_image_url_as() 181 | 182 | def cover_image_url_as(self, *, format='webp', size=1024): 183 | """Returns an :class:`Asset` for the image on store embeds 184 | if this application is a game sold on Discord. 185 | 186 | The format must be one of 'webp', 'jpeg', 'jpg' or 'png'. 187 | The size must be a power of 2 between 16 and 4096. 188 | 189 | .. versionadded:: 1.6 190 | 191 | Parameters 192 | ----------- 193 | format: :class:`str` 194 | The format to attempt to convert the image to. Defaults to 'webp'. 195 | size: :class:`int` 196 | The size of the image to display. 197 | 198 | Raises 199 | ------ 200 | InvalidArgument 201 | Bad image format passed to ``format`` or invalid ``size``. 202 | 203 | Returns 204 | -------- 205 | :class:`Asset` 206 | The resulting CDN asset. 207 | """ 208 | return Asset._from_cover_image(self._state, self, format=format, size=size) 209 | 210 | @property 211 | def guild(self): 212 | """Optional[:class:`Guild`]: If this application is a game sold on Discord, 213 | this field will be the guild to which it has been linked 214 | 215 | .. versionadded:: 1.3 216 | """ 217 | return self._state._get_guild(int(self.guild_id)) 218 | -------------------------------------------------------------------------------- /discord/backoff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | import time 28 | import random 29 | 30 | class ExponentialBackoff: 31 | """An implementation of the exponential backoff algorithm 32 | 33 | Provides a convenient interface to implement an exponential backoff 34 | for reconnecting or retrying transmissions in a distributed network. 35 | 36 | Once instantiated, the delay method will return the next interval to 37 | wait for when retrying a connection or transmission. The maximum 38 | delay increases exponentially with each retry up to a maximum of 39 | 2^10 * base, and is reset if no more attempts are needed in a period 40 | of 2^11 * base seconds. 41 | 42 | Parameters 43 | ---------- 44 | base: :class:`int` 45 | The base delay in seconds. The first retry-delay will be up to 46 | this many seconds. 47 | integral: :class:`bool` 48 | Set to ``True`` if whole periods of base is desirable, otherwise any 49 | number in between may be returned. 50 | """ 51 | 52 | def __init__(self, base=1, *, integral=False): 53 | self._base = base 54 | 55 | self._exp = 0 56 | self._max = 10 57 | self._reset_time = base * 2 ** 11 58 | self._last_invocation = time.monotonic() 59 | 60 | # Use our own random instance to avoid messing with global one 61 | rand = random.Random() 62 | rand.seed() 63 | 64 | self._randfunc = rand.randrange if integral else rand.uniform 65 | 66 | def delay(self): 67 | """Compute the next delay 68 | 69 | Returns the next delay to wait according to the exponential 70 | backoff algorithm. This is a value between 0 and base * 2^exp 71 | where exponent starts off at 1 and is incremented at every 72 | invocation of this method up to a maximum of 10. 73 | 74 | If a period of more than base * 2^11 has passed since the last 75 | retry, the exponent is reset to 1. 76 | """ 77 | invocation = time.monotonic() 78 | interval = invocation - self._last_invocation 79 | self._last_invocation = invocation 80 | 81 | if interval > self._reset_time: 82 | self._exp = 0 83 | 84 | self._exp = min(self._exp + 1, self._max) 85 | return self._randfunc(0, self._base * 2 ** self._exp) 86 | -------------------------------------------------------------------------------- /discord/bin/libopus-0.x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccoderpy/discord.py-message-components/3ba468b3af1ccec28e1250691d88316c9e80955c/discord/bin/libopus-0.x64.dll -------------------------------------------------------------------------------- /discord/bin/libopus-0.x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccoderpy/discord.py-message-components/3ba468b3af1ccec28e1250691d88316c9e80955c/discord/bin/libopus-0.x86.dll -------------------------------------------------------------------------------- /discord/calls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | import datetime 28 | 29 | from . import utils 30 | from .enums import VoiceRegion, try_enum 31 | from .member import VoiceState 32 | 33 | class CallMessage: 34 | """Represents a group call message from Discord. 35 | 36 | This is only received in cases where the message type is equivalent to 37 | :attr:`MessageType.call`. 38 | 39 | .. deprecated:: 1.7 40 | 41 | Attributes 42 | ----------- 43 | ended_timestamp: Optional[:class:`datetime.datetime`] 44 | A naive UTC datetime object that represents the time that the call has ended. 45 | participants: List[:class:`User`] 46 | The list of users that are participating in this call. 47 | message: :class:`Message` 48 | The message associated with this call message. 49 | """ 50 | 51 | def __init__(self, message, **kwargs): 52 | self.message = message 53 | self.ended_timestamp = utils.parse_time(kwargs.get('ended_timestamp')) 54 | self.participants = kwargs.get('participants') 55 | 56 | @property 57 | def call_ended(self): 58 | """:class:`bool`: Indicates if the call has ended. 59 | 60 | .. deprecated:: 1.7 61 | """ 62 | return self.ended_timestamp is not None 63 | 64 | @property 65 | def channel(self): 66 | r""":class:`GroupChannel`\: The private channel associated with this message. 67 | 68 | .. deprecated:: 1.7 69 | """ 70 | return self.message.channel 71 | 72 | @property 73 | def duration(self): 74 | """Queries the duration of the call. 75 | 76 | If the call has not ended then the current duration will 77 | be returned. 78 | 79 | .. deprecated:: 1.7 80 | 81 | Returns 82 | --------- 83 | :class:`datetime.timedelta` 84 | The timedelta object representing the duration. 85 | """ 86 | if self.ended_timestamp is None: 87 | return datetime.datetime.utcnow() - self.message.created_at 88 | else: 89 | return self.ended_timestamp - self.message.created_at 90 | 91 | class GroupCall: 92 | """Represents the actual group call from Discord. 93 | 94 | This is accompanied with a :class:`CallMessage` denoting the information. 95 | 96 | .. deprecated:: 1.7 97 | 98 | Attributes 99 | ----------- 100 | call: :class:`CallMessage` 101 | The call message associated with this group call. 102 | unavailable: :class:`bool` 103 | Denotes if this group call is unavailable. 104 | ringing: List[:class:`User`] 105 | A list of users that are currently being rung to join the call. 106 | region: :class:`VoiceRegion` 107 | The guild region the group call is being hosted on. 108 | """ 109 | 110 | def __init__(self, **kwargs): 111 | self.call = kwargs.get('call') 112 | self.unavailable = kwargs.get('unavailable') 113 | self._voice_states = {} 114 | 115 | for state in kwargs.get('voice_states', []): 116 | self._update_voice_state(state) 117 | 118 | self._update(**kwargs) 119 | 120 | def _update(self, **kwargs): 121 | self.region = try_enum(VoiceRegion, kwargs.get('region')) 122 | lookup = {u.id: u for u in self.call.channel.recipients} 123 | me = self.call.channel.me 124 | lookup[me.id] = me 125 | self.ringing = list(filter(None, map(lookup.get, kwargs.get('ringing', [])))) 126 | 127 | def _update_voice_state(self, data): 128 | user_id = int(data['user_id']) 129 | # left the voice channel? 130 | if data['channel_id'] is None: 131 | self._voice_states.pop(user_id, None) 132 | else: 133 | self._voice_states[user_id] = VoiceState(data=data, channel=self.channel) 134 | 135 | @property 136 | def connected(self): 137 | """List[:class:`User`]: A property that returns all users that are currently in this call. 138 | 139 | .. deprecated:: 1.7 140 | """ 141 | ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None] 142 | me = self.channel.me 143 | if self.voice_state_for(me) is not None: 144 | ret.append(me) 145 | 146 | return ret 147 | 148 | @property 149 | def channel(self): 150 | r""":class:`GroupChannel`\: Returns the channel the group call is in. 151 | 152 | .. deprecated:: 1.7 153 | """ 154 | return self.call.channel 155 | 156 | @utils.deprecated() 157 | def voice_state_for(self, user): 158 | """Retrieves the :class:`VoiceState` for a specified :class:`User`. 159 | 160 | If the :class:`User` has no voice state then this function returns 161 | ``None``. 162 | 163 | .. deprecated:: 1.7 164 | 165 | Parameters 166 | ------------ 167 | user: :class:`User` 168 | The user to retrieve the voice state for. 169 | 170 | Returns 171 | -------- 172 | Optional[:class:`VoiceState`] 173 | The voice state associated with this user. 174 | """ 175 | 176 | return self._voice_states.get(user.id) 177 | -------------------------------------------------------------------------------- /discord/colour.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | import colorsys 28 | import random 29 | 30 | class Colour: 31 | """Represents a Discord role colour. This class is similar 32 | to a (red, green, blue) :class:`tuple`. 33 | 34 | There is an alias for this called Color. 35 | 36 | .. container:: operations 37 | 38 | .. describe:: x == y 39 | 40 | Checks if two colours are equal. 41 | 42 | .. describe:: x != y 43 | 44 | Checks if two colours are not equal. 45 | 46 | .. describe:: hash(x) 47 | 48 | Return the colour's hash. 49 | 50 | .. describe:: str(x) 51 | 52 | Returns the hex format for the colour. 53 | 54 | Attributes 55 | ------------ 56 | value: :class:`int` 57 | The raw integer colour value. 58 | """ 59 | 60 | __slots__ = ('value',) 61 | 62 | def __init__(self, value): 63 | if not isinstance(value, int): 64 | raise TypeError('Expected int parameter, received %s instead.' % value.__class__.__name__) 65 | 66 | self.value = value 67 | 68 | def _get_byte(self, byte): 69 | return (self.value >> (8 * byte)) & 0xff 70 | 71 | def __eq__(self, other): 72 | return isinstance(other, Colour) and self.value == other.value 73 | 74 | def __ne__(self, other): 75 | return not self.__eq__(other) 76 | 77 | def __str__(self): 78 | return '#{:0>6x}'.format(self.value) 79 | 80 | def __repr__(self): 81 | return '' % self.value 82 | 83 | def __hash__(self): 84 | return hash(self.value) 85 | 86 | @property 87 | def r(self): 88 | """:class:`int`: Returns the red component of the colour.""" 89 | return self._get_byte(2) 90 | 91 | @property 92 | def g(self): 93 | """:class:`int`: Returns the green component of the colour.""" 94 | return self._get_byte(1) 95 | 96 | @property 97 | def b(self): 98 | """:class:`int`: Returns the blue component of the colour.""" 99 | return self._get_byte(0) 100 | 101 | def to_rgb(self): 102 | """Tuple[:class:`int`, :class:`int`, :class:`int`]: Returns an (r, g, b) tuple representing the colour.""" 103 | return (self.r, self.g, self.b) 104 | 105 | @classmethod 106 | def from_rgb(cls, r, g, b): 107 | """Constructs a :class:`Colour` from an RGB tuple.""" 108 | return cls((r << 16) + (g << 8) + b) 109 | 110 | @classmethod 111 | def from_hsv(cls, h, s, v): 112 | """Constructs a :class:`Colour` from an HSV tuple.""" 113 | rgb = colorsys.hsv_to_rgb(h, s, v) 114 | return cls.from_rgb(*(int(x * 255) for x in rgb)) 115 | 116 | @classmethod 117 | def default(cls): 118 | """A factory method that returns a :class:`Colour` with a value of ``0``.""" 119 | return cls(0) 120 | 121 | @classmethod 122 | def random(cls, *, seed=None): 123 | """A factory method that returns a :class:`Colour` with a random hue. 124 | 125 | .. note:: 126 | 127 | The random algorithm works by choosing a colour with a random hue but 128 | with maxed out saturation and value. 129 | 130 | .. versionadded:: 1.6 131 | 132 | Parameters 133 | ------------ 134 | seed: Optional[Union[:class:`int`, :class:`str`, :class:`float`, :class:`bytes`, :class:`bytearray`]] 135 | The seed to initialize the RNG with. If ``None`` is passed the default RNG is used. 136 | 137 | .. versionadded:: 1.7 138 | """ 139 | rand = random if seed is None else random.Random(seed) 140 | return cls.from_hsv(rand.random(), 1, 1) 141 | 142 | @classmethod 143 | def teal(cls): 144 | """A factory method that returns a :class:`Colour` with a value of ``0x1abc9c``.""" 145 | return cls(0x1abc9c) 146 | 147 | @classmethod 148 | def dark_teal(cls): 149 | """A factory method that returns a :class:`Colour` with a value of ``0x11806a``.""" 150 | return cls(0x11806a) 151 | 152 | @classmethod 153 | def green(cls): 154 | """A factory method that returns a :class:`Colour` with a value of ``0x2ecc71``.""" 155 | return cls(0x2ecc71) 156 | 157 | @classmethod 158 | def dark_green(cls): 159 | """A factory method that returns a :class:`Colour` with a value of ``0x1f8b4c``.""" 160 | return cls(0x1f8b4c) 161 | 162 | @classmethod 163 | def blue(cls): 164 | """A factory method that returns a :class:`Colour` with a value of ``0x3498db``.""" 165 | return cls(0x3498db) 166 | 167 | @classmethod 168 | def dark_blue(cls): 169 | """A factory method that returns a :class:`Colour` with a value of ``0x206694``.""" 170 | return cls(0x206694) 171 | 172 | @classmethod 173 | def purple(cls): 174 | """A factory method that returns a :class:`Colour` with a value of ``0x9b59b6``.""" 175 | return cls(0x9b59b6) 176 | 177 | @classmethod 178 | def dark_purple(cls): 179 | """A factory method that returns a :class:`Colour` with a value of ``0x71368a``.""" 180 | return cls(0x71368a) 181 | 182 | @classmethod 183 | def magenta(cls): 184 | """A factory method that returns a :class:`Colour` with a value of ``0xe91e63``.""" 185 | return cls(0xe91e63) 186 | 187 | @classmethod 188 | def dark_magenta(cls): 189 | """A factory method that returns a :class:`Colour` with a value of ``0xad1457``.""" 190 | return cls(0xad1457) 191 | 192 | @classmethod 193 | def gold(cls): 194 | """A factory method that returns a :class:`Colour` with a value of ``0xf1c40f``.""" 195 | return cls(0xf1c40f) 196 | 197 | @classmethod 198 | def dark_gold(cls): 199 | """A factory method that returns a :class:`Colour` with a value of ``0xc27c0e``.""" 200 | return cls(0xc27c0e) 201 | 202 | @classmethod 203 | def orange(cls): 204 | """A factory method that returns a :class:`Colour` with a value of ``0xe67e22``.""" 205 | return cls(0xe67e22) 206 | 207 | @classmethod 208 | def dark_orange(cls): 209 | """A factory method that returns a :class:`Colour` with a value of ``0xa84300``.""" 210 | return cls(0xa84300) 211 | 212 | @classmethod 213 | def red(cls): 214 | """A factory method that returns a :class:`Colour` with a value of ``0xe74c3c``.""" 215 | return cls(0xe74c3c) 216 | 217 | @classmethod 218 | def dark_red(cls): 219 | """A factory method that returns a :class:`Colour` with a value of ``0x992d22``.""" 220 | return cls(0x992d22) 221 | 222 | @classmethod 223 | def lighter_grey(cls): 224 | """A factory method that returns a :class:`Colour` with a value of ``0x95a5a6``.""" 225 | return cls(0x95a5a6) 226 | 227 | lighter_gray = lighter_grey 228 | 229 | @classmethod 230 | def dark_grey(cls): 231 | """A factory method that returns a :class:`Colour` with a value of ``0x607d8b``.""" 232 | return cls(0x607d8b) 233 | 234 | dark_gray = dark_grey 235 | 236 | @classmethod 237 | def light_grey(cls): 238 | """A factory method that returns a :class:`Colour` with a value of ``0x979c9f``.""" 239 | return cls(0x979c9f) 240 | 241 | light_gray = light_grey 242 | 243 | @classmethod 244 | def darker_grey(cls): 245 | """A factory method that returns a :class:`Colour` with a value of ``0x546e7a``.""" 246 | return cls(0x546e7a) 247 | 248 | darker_gray = darker_grey 249 | 250 | @classmethod 251 | def blurple(cls): 252 | """A factory method that returns a :class:`Colour` with a value of ``0x7289da``.""" 253 | return cls(0x7289da) 254 | 255 | @classmethod 256 | def greyple(cls): 257 | """A factory method that returns a :class:`Colour` with a value of ``0x99aab5``.""" 258 | return cls(0x99aab5) 259 | 260 | @classmethod 261 | def dark_theme(cls): 262 | """A factory method that returns a :class:`Colour` with a value of ``0x36393F``. 263 | This will appear transparent on Discord's dark theme. 264 | 265 | .. versionadded:: 1.5 266 | """ 267 | return cls(0x36393F) 268 | 269 | Color = Colour 270 | -------------------------------------------------------------------------------- /discord/context_managers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | import asyncio 28 | 29 | def _typing_done_callback(fut): 30 | # just retrieve any exception and call it a day 31 | try: 32 | fut.exception() 33 | except (asyncio.CancelledError, Exception): 34 | pass 35 | 36 | class Typing: 37 | def __init__(self, messageable): 38 | self.loop = messageable._state.loop 39 | self.messageable = messageable 40 | 41 | async def do_typing(self): 42 | try: 43 | channel = self._channel 44 | except AttributeError: 45 | channel = await self.messageable._get_channel() 46 | 47 | typing = channel._state.http.send_typing 48 | 49 | while True: 50 | await typing(channel.id) 51 | await asyncio.sleep(5) 52 | 53 | def __enter__(self): 54 | self.task = asyncio.ensure_future(self.do_typing(), loop=self.loop) 55 | self.task.add_done_callback(_typing_done_callback) 56 | return self 57 | 58 | def __exit__(self, exc_type, exc, tb): 59 | self.task.cancel() 60 | 61 | async def __aenter__(self): 62 | self._channel = channel = await self.messageable._get_channel() 63 | await channel._state.http.send_typing(channel.id) 64 | return self.__enter__() 65 | 66 | async def __aexit__(self, exc_type, exc, tb): 67 | self.task.cancel() 68 | -------------------------------------------------------------------------------- /discord/emoji.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from .asset import Asset 28 | from . import utils 29 | from .partial_emoji import _EmojiTag 30 | 31 | 32 | class Emoji(_EmojiTag): 33 | """Represents a custom emoji. 34 | 35 | Depending on the way this object was created, some of the attributes can 36 | have a value of ``None``. 37 | 38 | .. container:: operations 39 | 40 | .. describe:: x == y 41 | 42 | Checks if two emoji are the same. 43 | 44 | .. describe:: x != y 45 | 46 | Checks if two emoji are not the same. 47 | 48 | .. describe:: hash(x) 49 | 50 | Return the emoji's hash. 51 | 52 | .. describe:: iter(x) 53 | 54 | Returns an iterator of ``(field, value)`` pairs. This allows this class 55 | to be used as an iterable in list/dict/etc constructions. 56 | 57 | .. describe:: str(x) 58 | 59 | Returns the emoji rendered for discord. 60 | 61 | Attributes 62 | ----------- 63 | name: :class:`str` 64 | The name of the emoji. 65 | id: :class:`int` 66 | The emoji's ID. 67 | require_colons: :class:`bool` 68 | If colons are required to use this emoji in the client (:PJSalt: vs PJSalt). 69 | animated: :class:`bool` 70 | Whether an emoji is animated or not. 71 | managed: :class:`bool` 72 | If this emoji is managed by a Twitch integration. 73 | guild_id: :class:`int` 74 | The guild ID the emoji belongs to. 75 | available: :class:`bool` 76 | Whether the emoji is available for use. 77 | user: Optional[:class:`User`] 78 | The user that created the emoji. This can only be retrieved using :meth:`Guild.fetch_emoji` and 79 | having the :attr:`~Permissions.manage_emojis` permission. 80 | """ 81 | __slots__ = ('require_colons', 'animated', 'managed', 'id', 'name', '_roles', 'guild_id', 82 | '_state', 'user', 'available') 83 | 84 | def __init__(self, *, guild, state, data): 85 | self.guild_id = guild.id 86 | self._state = state 87 | self._from_data(data) 88 | 89 | def _from_data(self, emoji): 90 | self.require_colons = emoji['require_colons'] 91 | self.managed = emoji['managed'] 92 | self.id = int(emoji['id']) 93 | self.name = emoji['name'] 94 | self.animated = emoji.get('animated', False) 95 | self.available = emoji.get('available', True) 96 | self._roles = utils.SnowflakeList(map(int, emoji.get('roles', []))) 97 | user = emoji.get('user', None) 98 | if user: 99 | self.user = self._state.get_user(int(user['id'])) 100 | else: 101 | self.user = None 102 | 103 | def _iterator(self): 104 | for attr in self.__slots__: 105 | if attr[0] != '_': 106 | value = getattr(self, attr, None) 107 | if value is not None: 108 | yield (attr, value) 109 | 110 | def __iter__(self): 111 | return self._iterator() 112 | 113 | def __str__(self): 114 | if self.animated: 115 | return ''.format(self) 116 | return "<:{0.name}:{0.id}>".format(self) 117 | 118 | def __repr__(self): 119 | return ''.format(self) 120 | 121 | def __eq__(self, other): 122 | return isinstance(other, _EmojiTag) and self.id == other.id 123 | 124 | def __ne__(self, other): 125 | return not self.__eq__(other) 126 | 127 | def __hash__(self): 128 | return self.id >> 22 129 | 130 | @property 131 | def created_at(self): 132 | """:class:`datetime.datetime`: Returns the emoji's creation time in UTC.""" 133 | return utils.snowflake_time(self.id) 134 | 135 | @property 136 | def url(self): 137 | """:class:`Asset`: Returns the asset of the emoji. 138 | 139 | This is equivalent to calling :meth:`url_as` with 140 | the default parameters (i.e. png/gif detection). 141 | """ 142 | return self.url_as(format=None) 143 | 144 | @property 145 | def roles(self): 146 | """List[:class:`Role`]: A :class:`list` of roles that is allowed to use this emoji. 147 | 148 | If roles is empty, the emoji is unrestricted. 149 | """ 150 | guild = self.guild 151 | if guild is None: 152 | return [] 153 | 154 | return [role for role in guild.roles if self._roles.has(role.id)] 155 | 156 | @property 157 | def guild(self): 158 | """:class:`Guild`: The guild this emoji belongs to.""" 159 | return self._state._get_guild(self.guild_id) 160 | 161 | 162 | def url_as(self, *, format=None, static_format="png"): 163 | """Returns an :class:`Asset` for the emoji's url. 164 | 165 | The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'. 166 | 'gif' is only valid for animated emojis. 167 | 168 | .. versionadded:: 1.6 169 | 170 | Parameters 171 | ----------- 172 | format: Optional[:class:`str`] 173 | The format to attempt to convert the emojis to. 174 | If the format is ``None``, then it is automatically 175 | detected as either 'gif' or static_format, depending on whether the 176 | emoji is animated or not. 177 | static_format: Optional[:class:`str`] 178 | Format to attempt to convert only non-animated emoji's to. 179 | Defaults to 'png' 180 | 181 | Raises 182 | ------- 183 | InvalidArgument 184 | Bad image format passed to ``format`` or ``static_format``. 185 | 186 | Returns 187 | -------- 188 | :class:`Asset` 189 | The resulting CDN asset. 190 | """ 191 | return Asset._from_emoji(self._state, self, format=format, static_format=static_format) 192 | 193 | 194 | def is_usable(self): 195 | """:class:`bool`: Whether the bot can use this emoji. 196 | 197 | .. versionadded:: 1.3 198 | """ 199 | if not self.available: 200 | return False 201 | if not self._roles: 202 | return True 203 | emoji_roles, my_roles = self._roles, self.guild.me._roles 204 | return any(my_roles.has(role_id) for role_id in emoji_roles) 205 | 206 | async def delete(self, *, reason=None): 207 | """|coro| 208 | 209 | Deletes the custom emoji. 210 | 211 | You must have :attr:`~Permissions.manage_emojis` permission to 212 | do this. 213 | 214 | Parameters 215 | ----------- 216 | reason: Optional[:class:`str`] 217 | The reason for deleting this emoji. Shows up on the audit log. 218 | 219 | Raises 220 | ------- 221 | Forbidden 222 | You are not allowed to delete emojis. 223 | HTTPException 224 | An error occurred deleting the emoji. 225 | """ 226 | 227 | await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason) 228 | 229 | async def edit(self, *, name=None, roles=None, reason=None): 230 | r"""|coro| 231 | 232 | Edits the custom emoji. 233 | 234 | You must have :attr:`~Permissions.manage_emojis` permission to 235 | do this. 236 | 237 | Parameters 238 | ----------- 239 | name: :class:`str` 240 | The new emoji name. 241 | roles: Optional[list[:class:`Role`]] 242 | A :class:`list` of :class:`Role`\s that can use this emoji. Leave empty to make it available to everyone. 243 | reason: Optional[:class:`str`] 244 | The reason for editing this emoji. Shows up on the audit log. 245 | 246 | Raises 247 | ------- 248 | Forbidden 249 | You are not allowed to edit emojis. 250 | HTTPException 251 | An error occurred editing the emoji. 252 | """ 253 | 254 | name = name or self.name 255 | if roles: 256 | roles = [role.id for role in roles] 257 | await self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, roles=roles, reason=reason) 258 | -------------------------------------------------------------------------------- /discord/ext/commands/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | discord.ext.commands 5 | ~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | An extension module to facilitate creation of bot commands. 8 | 9 | :copyright: (c) 2015-present Rapptz 10 | :license: MIT, see LICENSE for more details. 11 | """ 12 | 13 | from .bot import Bot, AutoShardedBot, when_mentioned, when_mentioned_or 14 | from .context import Context 15 | from .core import * 16 | from .errors import * 17 | from .help import * 18 | from .converter import * 19 | from .cooldowns import * 20 | from .cog import * -------------------------------------------------------------------------------- /discord/ext/commands/_types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | # This is merely a tag type to avoid circular import issues. 28 | # Yes, this is a terrible solution but ultimately it is the only solution. 29 | class _BaseCommand: 30 | __slots__ = () 31 | -------------------------------------------------------------------------------- /discord/ext/commands/view.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError 28 | 29 | # map from opening quotes to closing quotes 30 | _quotes = { 31 | '"': '"', 32 | "‘": "’", 33 | "‚": "‛", 34 | "“": "”", 35 | "„": "‟", 36 | "⹂": "⹂", 37 | "「": "」", 38 | "『": "』", 39 | "〝": "〞", 40 | "﹁": "﹂", 41 | "﹃": "﹄", 42 | """: """, 43 | "「": "」", 44 | "«": "»", 45 | "‹": "›", 46 | "《": "》", 47 | "〈": "〉", 48 | } 49 | _all_quotes = set(_quotes.keys()) | set(_quotes.values()) 50 | 51 | class StringView: 52 | def __init__(self, buffer): 53 | self.index = 0 54 | self.buffer = buffer 55 | self.end = len(buffer) 56 | self.previous = 0 57 | 58 | @property 59 | def current(self): 60 | return None if self.eof else self.buffer[self.index] 61 | 62 | @property 63 | def eof(self): 64 | return self.index >= self.end 65 | 66 | def undo(self): 67 | self.index = self.previous 68 | 69 | def skip_ws(self): 70 | pos = 0 71 | while not self.eof: 72 | try: 73 | current = self.buffer[self.index + pos] 74 | if not current.isspace(): 75 | break 76 | pos += 1 77 | except IndexError: 78 | break 79 | 80 | self.previous = self.index 81 | self.index += pos 82 | return self.previous != self.index 83 | 84 | def skip_string(self, string): 85 | strlen = len(string) 86 | if self.buffer[self.index:self.index + strlen] == string: 87 | self.previous = self.index 88 | self.index += strlen 89 | return True 90 | return False 91 | 92 | def read_rest(self): 93 | result = self.buffer[self.index:] 94 | self.previous = self.index 95 | self.index = self.end 96 | return result 97 | 98 | def read(self, n): 99 | result = self.buffer[self.index:self.index + n] 100 | self.previous = self.index 101 | self.index += n 102 | return result 103 | 104 | def get(self): 105 | try: 106 | result = self.buffer[self.index + 1] 107 | except IndexError: 108 | result = None 109 | 110 | self.previous = self.index 111 | self.index += 1 112 | return result 113 | 114 | def get_word(self): 115 | pos = 0 116 | while not self.eof: 117 | try: 118 | current = self.buffer[self.index + pos] 119 | if current.isspace(): 120 | break 121 | pos += 1 122 | except IndexError: 123 | break 124 | self.previous = self.index 125 | result = self.buffer[self.index:self.index + pos] 126 | self.index += pos 127 | return result 128 | 129 | def get_quoted_word(self): 130 | current = self.current 131 | if current is None: 132 | return None 133 | 134 | close_quote = _quotes.get(current) 135 | is_quoted = bool(close_quote) 136 | if is_quoted: 137 | result = [] 138 | _escaped_quotes = (current, close_quote) 139 | else: 140 | result = [current] 141 | _escaped_quotes = _all_quotes 142 | 143 | while not self.eof: 144 | current = self.get() 145 | if not current: 146 | if is_quoted: 147 | # unexpected EOF 148 | raise ExpectedClosingQuoteError(close_quote) 149 | return ''.join(result) 150 | 151 | # currently we accept strings in the format of "hello world" 152 | # to embed a quote inside the string you must escape it: "a \"world\"" 153 | if current == '\\': 154 | next_char = self.get() 155 | if not next_char: 156 | # string ends with \ and no character after it 157 | if is_quoted: 158 | # if we're quoted then we're expecting a closing quote 159 | raise ExpectedClosingQuoteError(close_quote) 160 | # if we aren't then we just let it through 161 | return ''.join(result) 162 | 163 | if next_char in _escaped_quotes: 164 | # escaped quote 165 | result.append(next_char) 166 | else: 167 | # different escape character, ignore it 168 | self.undo() 169 | result.append(current) 170 | continue 171 | 172 | if not is_quoted and current in _all_quotes: 173 | # we aren't quoted 174 | raise UnexpectedQuoteError(current) 175 | 176 | # closing quote 177 | if is_quoted and current == close_quote: 178 | next_char = self.get() 179 | valid_eof = not next_char or next_char.isspace() 180 | if not valid_eof: 181 | raise InvalidEndOfQuotedStringError(next_char) 182 | 183 | # we're quoted so it's okay 184 | return ''.join(result) 185 | 186 | if current.isspace() and not is_quoted: 187 | # end of word found 188 | return ''.join(result) 189 | 190 | result.append(current) 191 | 192 | 193 | def __repr__(self): 194 | return ''.format(self) 195 | -------------------------------------------------------------------------------- /discord/file.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | import os.path 28 | import io 29 | 30 | class File: 31 | r"""A parameter object used for :meth:`abc.Messageable.send` 32 | for sending file objects. 33 | 34 | .. note:: 35 | 36 | File objects are single use and are not meant to be reused in 37 | multiple :meth:`abc.Messageable.send`\s. 38 | 39 | Attributes 40 | ----------- 41 | fp: Union[:class:`str`, :class:`io.BufferedIOBase`] 42 | A file-like object opened in binary mode and read mode 43 | or a filename representing a file in the hard drive to 44 | open. 45 | 46 | .. note:: 47 | 48 | If the file-like object passed is opened via ``open`` then the 49 | modes 'rb' should be used. 50 | 51 | To pass binary data, consider usage of ``io.BytesIO``. 52 | 53 | filename: Optional[:class:`str`] 54 | The filename to display when uploading to Discord. 55 | If this is not given then it defaults to ``fp.name`` or if ``fp`` is 56 | a string then the ``filename`` will default to the string given. 57 | spoiler: :class:`bool` 58 | Whether the attachment is a spoiler. 59 | """ 60 | 61 | __slots__ = ('fp', 'filename', 'spoiler', '_original_pos', '_owner', '_closer') 62 | 63 | def __init__(self, fp, filename=None, *, spoiler=False): 64 | self.fp = fp 65 | 66 | if isinstance(fp, io.IOBase): 67 | if not (fp.seekable() and fp.readable()): 68 | raise ValueError('File buffer {!r} must be seekable and readable'.format(fp)) 69 | self.fp = fp 70 | self._original_pos = fp.tell() 71 | self._owner = False 72 | else: 73 | self.fp = open(fp, 'rb') 74 | self._original_pos = 0 75 | self._owner = True 76 | 77 | # aiohttp only uses two methods from IOBase 78 | # read and close, since I want to control when the files 79 | # close, I need to stub it so it doesn't close unless 80 | # I tell it to 81 | self._closer = self.fp.close 82 | self.fp.close = lambda: None 83 | 84 | if filename is None: 85 | if isinstance(fp, str): 86 | _, self.filename = os.path.split(fp) 87 | else: 88 | self.filename = getattr(fp, 'name', None) 89 | else: 90 | self.filename = filename 91 | 92 | if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'): 93 | self.filename = 'SPOILER_' + self.filename 94 | 95 | self.spoiler = spoiler or (self.filename is not None and self.filename.startswith('SPOILER_')) 96 | 97 | def reset(self, *, seek=True): 98 | # The `seek` parameter is needed because 99 | # the retry-loop is iterated over multiple times 100 | # starting from 0, as an implementation quirk 101 | # the resetting must be done at the beginning 102 | # before a request is done, since the first index 103 | # is 0, and thus false, then this prevents an 104 | # unnecessary seek since it's the first request 105 | # done. 106 | if seek: 107 | self.fp.seek(self._original_pos) 108 | 109 | def close(self): 110 | self.fp.close = self._closer 111 | if self._owner: 112 | self._closer() 113 | -------------------------------------------------------------------------------- /discord/mentions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | class _FakeBool: 28 | def __repr__(self): 29 | return 'True' 30 | 31 | def __eq__(self, other): 32 | return other is True 33 | 34 | def __bool__(self): 35 | return True 36 | 37 | default = _FakeBool() 38 | 39 | class AllowedMentions: 40 | """A class that represents what mentions are allowed in a message. 41 | 42 | This class can be set during :class:`Client` initialisation to apply 43 | to every message sent. It can also be applied on a per message basis 44 | via :meth:`abc.Messageable.send` for more fine-grained control. 45 | 46 | Attributes 47 | ------------ 48 | everyone: :class:`bool` 49 | Whether to allow everyone and here mentions. Defaults to ``True``. 50 | users: Union[:class:`bool`, List[:class:`abc.Snowflake`]] 51 | Controls the users being mentioned. If ``True`` (the default) then 52 | users are mentioned based on the message content. If ``False`` then 53 | users are not mentioned at all. If a list of :class:`abc.Snowflake` 54 | is given then only the users provided will be mentioned, provided those 55 | users are in the message content. 56 | roles: Union[:class:`bool`, List[:class:`abc.Snowflake`]] 57 | Controls the roles being mentioned. If ``True`` (the default) then 58 | roles are mentioned based on the message content. If ``False`` then 59 | roles are not mentioned at all. If a list of :class:`abc.Snowflake` 60 | is given then only the roles provided will be mentioned, provided those 61 | roles are in the message content. 62 | replied_user: :class:`bool` 63 | Whether to mention the author of the message being replied to. Defaults 64 | to ``True``. 65 | 66 | .. versionadded:: 1.6 67 | """ 68 | 69 | __slots__ = ('everyone', 'users', 'roles', 'replied_user') 70 | 71 | def __init__(self, *, everyone=default, users=default, roles=default, replied_user=default): 72 | self.everyone = everyone 73 | self.users = users 74 | self.roles = roles 75 | self.replied_user = replied_user 76 | 77 | @classmethod 78 | def all(cls): 79 | """A factory method that returns a :class:`AllowedMentions` with all fields explicitly set to ``True`` 80 | 81 | .. versionadded:: 1.5 82 | """ 83 | return cls(everyone=True, users=True, roles=True, replied_user=True) 84 | 85 | @classmethod 86 | def none(cls): 87 | """A factory method that returns a :class:`AllowedMentions` with all fields set to ``False`` 88 | 89 | .. versionadded:: 1.5 90 | """ 91 | return cls(everyone=False, users=False, roles=False, replied_user=False) 92 | 93 | def to_dict(self): 94 | parse = [] 95 | data = {} 96 | 97 | if self.everyone: 98 | parse.append('everyone') 99 | 100 | if self.users == True: 101 | parse.append('users') 102 | elif self.users != False: 103 | data['users'] = [x.id for x in self.users] 104 | 105 | if self.roles == True: 106 | parse.append('roles') 107 | elif self.roles != False: 108 | data['roles'] = [x.id for x in self.roles] 109 | 110 | if self.replied_user: 111 | data['replied_user'] = True 112 | 113 | data['parse'] = parse 114 | return data 115 | 116 | def merge(self, other): 117 | # Creates a new AllowedMentions by merging from another one. 118 | # Merge is done by using the 'self' values unless explicitly 119 | # overridden by the 'other' values. 120 | everyone = self.everyone if other.everyone is default else other.everyone 121 | users = self.users if other.users is default else other.users 122 | roles = self.roles if other.roles is default else other.roles 123 | replied_user = self.replied_user if other.replied_user is default else other.replied_user 124 | return AllowedMentions(everyone=everyone, roles=roles, users=users, replied_user=replied_user) 125 | 126 | def __repr__(self): 127 | return '{0.__class__.__qualname__}(everyone={0.everyone}, users={0.users}, roles={0.roles}, replied_user={0.replied_user})'.format(self) 128 | -------------------------------------------------------------------------------- /discord/mixins.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | class EqualityComparable: 28 | __slots__ = () 29 | 30 | def __eq__(self, other): 31 | return isinstance(other, self.__class__) and other.id == self.id 32 | 33 | def __ne__(self, other): 34 | if isinstance(other, self.__class__): 35 | return other.id != self.id 36 | return True 37 | 38 | class Hashable(EqualityComparable): 39 | __slots__ = () 40 | 41 | def __hash__(self): 42 | return self.id >> 22 43 | -------------------------------------------------------------------------------- /discord/object.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from . import utils 28 | from .mixins import Hashable 29 | 30 | class Object(Hashable): 31 | """Represents a generic Discord object. 32 | 33 | The purpose of this class is to allow you to create 'miniature' 34 | versions of data classes if you want to pass in just an ID. Most functions 35 | that take in a specific data class with an ID can also take in this class 36 | as a substitute instead. Note that even though this is the case, not all 37 | objects (if any) actually inherit from this class. 38 | 39 | There are also some cases where some websocket events are received 40 | in :issue:`strange order <21>` and when such events happened you would 41 | receive this class rather than the actual data class. These cases are 42 | extremely rare. 43 | 44 | .. container:: operations 45 | 46 | .. describe:: x == y 47 | 48 | Checks if two objects are equal. 49 | 50 | .. describe:: x != y 51 | 52 | Checks if two objects are not equal. 53 | 54 | .. describe:: hash(x) 55 | 56 | Returns the object's hash. 57 | 58 | Attributes 59 | ----------- 60 | id: :class:`int` 61 | The ID of the object. 62 | """ 63 | 64 | def __init__(self, id): 65 | try: 66 | id = int(id) 67 | except ValueError: 68 | raise TypeError('id parameter must be convertable to int not {0.__class__!r}'.format(id)) from None 69 | else: 70 | self.id = id 71 | 72 | def __repr__(self): 73 | return '' % self.id 74 | 75 | @property 76 | def created_at(self): 77 | """:class:`datetime.datetime`: Returns the snowflake's creation time in UTC.""" 78 | return utils.snowflake_time(self.id) 79 | -------------------------------------------------------------------------------- /discord/oggparse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | import struct 28 | 29 | from .errors import DiscordException 30 | 31 | class OggError(DiscordException): 32 | """An exception that is thrown for Ogg stream parsing errors.""" 33 | pass 34 | 35 | # https://tools.ietf.org/html/rfc3533 36 | # https://tools.ietf.org/html/rfc7845 37 | 38 | class OggPage: 39 | _header = struct.Struct('$', string) 90 | if match: 91 | return cls(animated=bool(match.group(1)), name=match.group(2), id=int(match.group(3))) 92 | raise ValueError('The Passed Emoji is not a discord custom-emoji.') 93 | 94 | @classmethod 95 | def from_dict(cls, data): 96 | return cls( 97 | animated=data.get('animated', False), 98 | id=utils._get_as_snowflake(data, 'id'), 99 | name=data.get('name'), 100 | ) 101 | 102 | def to_dict(self): 103 | o = { 'name': self.name } 104 | if self.id: 105 | o['id'] = self.id 106 | if self.animated: 107 | o['animated'] = self.animated 108 | return o 109 | 110 | @classmethod 111 | def with_state(cls, state, *, name, animated=False, id=None): 112 | self = cls(name=name, animated=animated, id=id) 113 | self._state = state 114 | return self 115 | 116 | def __str__(self): 117 | if self.id is None: 118 | return self.name 119 | if self.animated: 120 | return '' % (self.name, self.id) 121 | return '<:%s:%s>' % (self.name, self.id) 122 | 123 | def __repr__(self): 124 | return '<{0.__class__.__name__} animated={0.animated} name={0.name!r} id={0.id}>'.format(self) 125 | 126 | def __eq__(self, other): 127 | if self.is_unicode_emoji(): 128 | return isinstance(other, PartialEmoji) and self.name == other.name 129 | 130 | if isinstance(other, _EmojiTag): 131 | return self.id == other.id 132 | return False 133 | 134 | def __ne__(self, other): 135 | return not self.__eq__(other) 136 | 137 | def __hash__(self): 138 | return hash((self.id, self.name)) 139 | 140 | def is_custom_emoji(self): 141 | """:class:`bool`: Checks if this is a custom non-Unicode emoji.""" 142 | return self.id is not None 143 | 144 | def is_unicode_emoji(self): 145 | """:class:`bool`: Checks if this is a Unicode emoji.""" 146 | return self.id is None 147 | 148 | def _as_reaction(self): 149 | if self.id is None: 150 | return self.name 151 | return '%s:%s' % (self.name, self.id) 152 | 153 | @property 154 | def created_at(self): 155 | """Optional[:class:`datetime.datetime`]: Returns the emoji's creation time in UTC, or None if Unicode emoji. 156 | 157 | .. versionadded:: 1.6 158 | """ 159 | if self.is_unicode_emoji(): 160 | return None 161 | 162 | return utils.snowflake_time(self.id) 163 | 164 | @property 165 | def url(self): 166 | """:class:`Asset`: Returns the asset of the emoji, if it is custom. 167 | 168 | This is equivalent to calling :meth:`url_as` with 169 | the default parameters (i.e. png/gif detection). 170 | """ 171 | return self.url_as(format=None) 172 | 173 | def url_as(self, *, format=None, static_format="png"): 174 | """Returns an :class:`Asset` for the emoji's url, if it is custom. 175 | 176 | The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'. 177 | 'gif' is only valid for animated emojis. 178 | 179 | .. versionadded:: 1.7 180 | 181 | Parameters 182 | ----------- 183 | format: Optional[:class:`str`] 184 | The format to attempt to convert the emojis to. 185 | If the format is ``None``, then it is automatically 186 | detected as either 'gif' or static_format, depending on whether the 187 | emoji is animated or not. 188 | static_format: Optional[:class:`str`] 189 | Format to attempt to convert only non-animated emoji's to. 190 | Defaults to 'png' 191 | 192 | Raises 193 | ------- 194 | InvalidArgument 195 | Bad image format passed to ``format`` or ``static_format``. 196 | 197 | Returns 198 | -------- 199 | :class:`Asset` 200 | The resulting CDN asset. 201 | """ 202 | if self.is_unicode_emoji(): 203 | return Asset(self._state) 204 | 205 | return Asset._from_emoji(self._state, self, format=format, static_format=static_format) 206 | -------------------------------------------------------------------------------- /discord/reaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from .iterators import ReactionIterator 28 | 29 | class Reaction: 30 | """Represents a reaction to a message. 31 | 32 | Depending on the way this object was created, some of the attributes can 33 | have a value of ``None``. 34 | 35 | .. container:: operations 36 | 37 | .. describe:: x == y 38 | 39 | Checks if two reactions are equal. This works by checking if the emoji 40 | is the same. So two messages with the same reaction will be considered 41 | "equal". 42 | 43 | .. describe:: x != y 44 | 45 | Checks if two reactions are not equal. 46 | 47 | .. describe:: hash(x) 48 | 49 | Returns the reaction's hash. 50 | 51 | .. describe:: str(x) 52 | 53 | Returns the string form of the reaction's emoji. 54 | 55 | Attributes 56 | ----------- 57 | emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`] 58 | The reaction emoji. May be a custom emoji, or a unicode emoji. 59 | count: :class:`int` 60 | Number of times this reaction was made 61 | me: :class:`bool` 62 | If the user sent this reaction. 63 | message: :class:`Message` 64 | Message this reaction is for. 65 | """ 66 | __slots__ = ('message', 'count', 'emoji', 'me') 67 | 68 | def __init__(self, *, message, data, emoji=None): 69 | self.message = message 70 | self.emoji = emoji or message._state.get_reaction_emoji(data['emoji']) 71 | self.count = data.get('count', 1) 72 | self.me = data.get('me') 73 | 74 | @property 75 | def custom_emoji(self): 76 | """:class:`bool`: If this is a custom emoji.""" 77 | return not isinstance(self.emoji, str) 78 | 79 | def __eq__(self, other): 80 | return isinstance(other, self.__class__) and other.emoji == self.emoji 81 | 82 | def __ne__(self, other): 83 | if isinstance(other, self.__class__): 84 | return other.emoji != self.emoji 85 | return True 86 | 87 | def __hash__(self): 88 | return hash(self.emoji) 89 | 90 | def __str__(self): 91 | return str(self.emoji) 92 | 93 | def __repr__(self): 94 | return ''.format(self) 95 | 96 | async def remove(self, user): 97 | """|coro| 98 | 99 | Remove the reaction by the provided :class:`User` from the message. 100 | 101 | If the reaction is not your own (i.e. ``user`` parameter is not you) then 102 | the :attr:`~Permissions.manage_messages` permission is needed. 103 | 104 | The ``user`` parameter must represent a user or member and meet 105 | the :class:`abc.Snowflake` abc. 106 | 107 | Parameters 108 | ----------- 109 | user: :class:`abc.Snowflake` 110 | The user or member from which to remove the reaction. 111 | 112 | Raises 113 | ------- 114 | HTTPException 115 | Removing the reaction failed. 116 | Forbidden 117 | You do not have the proper permissions to remove the reaction. 118 | NotFound 119 | The user you specified, or the reaction's message was not found. 120 | """ 121 | 122 | await self.message.remove_reaction(self.emoji, user) 123 | 124 | async def clear(self): 125 | """|coro| 126 | 127 | Clears this reaction from the message. 128 | 129 | You need the :attr:`~Permissions.manage_messages` permission to use this. 130 | 131 | .. versionadded:: 1.3 132 | 133 | Raises 134 | -------- 135 | HTTPException 136 | Clearing the reaction failed. 137 | Forbidden 138 | You do not have the proper permissions to clear the reaction. 139 | NotFound 140 | The emoji you specified was not found. 141 | InvalidArgument 142 | The emoji parameter is invalid. 143 | """ 144 | await self.message.clear_reaction(self.emoji) 145 | 146 | def users(self, limit=None, after=None): 147 | """Returns an :class:`AsyncIterator` representing the users that have reacted to the message. 148 | 149 | The ``after`` parameter must represent a member 150 | and meet the :class:`abc.Snowflake` abc. 151 | 152 | Examples 153 | --------- 154 | 155 | Usage :: 156 | 157 | # I do not actually recommend doing this. 158 | async for user in reaction.users(): 159 | await channel.send('{0} has reacted with {1.emoji}!'.format(user, reaction)) 160 | 161 | Flattening into a list: :: 162 | 163 | users = await reaction.users().flatten() 164 | # users is now a list of User... 165 | winner = random.choice(users) 166 | await channel.send('{} has won the raffle.'.format(winner)) 167 | 168 | Parameters 169 | ------------ 170 | limit: :class:`int` 171 | The maximum number of results to return. 172 | If not provided, returns all the users who 173 | reacted to the message. 174 | after: :class:`abc.Snowflake` 175 | For pagination, reactions are sorted by member. 176 | 177 | Raises 178 | -------- 179 | HTTPException 180 | Getting the users for the reaction failed. 181 | 182 | Yields 183 | -------- 184 | Union[:class:`User`, :class:`Member`] 185 | The member (if retrievable) or the user that has reacted 186 | to this message. The case where it can be a :class:`Member` is 187 | in a guild message context. Sometimes it can be a :class:`User` 188 | if the member has left the guild. 189 | """ 190 | 191 | if self.custom_emoji: 192 | emoji = '{0.name}:{0.id}'.format(self.emoji) 193 | else: 194 | emoji = self.emoji 195 | 196 | if limit is None: 197 | limit = self.count 198 | 199 | return ReactionIterator(self.message, emoji, limit, after) 200 | -------------------------------------------------------------------------------- /discord/relationship.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from .enums import RelationshipType, try_enum 28 | from . import utils 29 | 30 | class Relationship: 31 | """Represents a relationship in Discord. 32 | 33 | A relationship is like a friendship, a person who is blocked, etc. 34 | Only non-bot accounts can have relationships. 35 | 36 | .. deprecated:: 1.7 37 | 38 | Attributes 39 | ----------- 40 | user: :class:`User` 41 | The user you have the relationship with. 42 | type: :class:`RelationshipType` 43 | The type of relationship you have. 44 | """ 45 | 46 | __slots__ = ('type', 'user', '_state') 47 | 48 | def __init__(self, *, state, data): 49 | self._state = state 50 | self.type = try_enum(RelationshipType, data['type']) 51 | self.user = state.store_user(data['user']) 52 | 53 | def __repr__(self): 54 | return ''.format(self) 55 | 56 | @utils.deprecated() 57 | async def delete(self): 58 | """|coro| 59 | 60 | Deletes the relationship. 61 | 62 | .. deprecated:: 1.7 63 | 64 | Raises 65 | ------ 66 | HTTPException 67 | Deleting the relationship failed. 68 | """ 69 | 70 | await self._state.http.remove_relationship(self.user.id) 71 | 72 | @utils.deprecated() 73 | async def accept(self): 74 | """|coro| 75 | 76 | Accepts the relationship request. e.g. accepting a 77 | friend request. 78 | 79 | .. deprecated:: 1.7 80 | 81 | Raises 82 | ------- 83 | HTTPException 84 | Accepting the relationship failed. 85 | """ 86 | 87 | await self._state.http.add_relationship(self.user.id) 88 | -------------------------------------------------------------------------------- /discord/sticker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from .mixins import Hashable 28 | from .asset import Asset 29 | from .utils import snowflake_time 30 | from .enums import StickerType, try_enum 31 | 32 | class Sticker(Hashable): 33 | """Represents a sticker. 34 | 35 | .. versionadded:: 1.6 36 | 37 | .. container:: operations 38 | 39 | .. describe:: str(x) 40 | 41 | Returns the name of the sticker. 42 | 43 | .. describe:: x == y 44 | 45 | Checks if the sticker is equal to another sticker. 46 | 47 | .. describe:: x != y 48 | 49 | Checks if the sticker is not equal to another sticker. 50 | 51 | Attributes 52 | ---------- 53 | name: :class:`str` 54 | The sticker's name. 55 | id: :class:`int` 56 | The id of the sticker. 57 | description: :class:`str` 58 | The description of the sticker. 59 | pack_id: :class:`int` 60 | The id of the sticker's pack. 61 | format: :class:`StickerType` 62 | The format for the sticker's image. 63 | image: :class:`str` 64 | The sticker's image. 65 | tags: List[:class:`str`] 66 | A list of tags for the sticker. 67 | preview_image: Optional[:class:`str`] 68 | The sticker's preview asset hash. 69 | """ 70 | __slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', 'image', 'tags', 'preview_image') 71 | 72 | def __init__(self, *, state, data): 73 | self._state = state 74 | self.id = int(data['id']) 75 | self.name: str = data['name'] 76 | self.description: str = data['description'] 77 | self.pack_id: int = int(data.get('pack_id', 0)) 78 | self.format: StickerType = try_enum(StickerType, data['format_type']) 79 | self.image: str = data['asset'] 80 | 81 | try: 82 | self.tags = [tag.strip() for tag in data['tags'].split(',')] 83 | except KeyError: 84 | self.tags = [] 85 | 86 | self.preview_image = data.get('preview_asset') 87 | 88 | def __repr__(self): 89 | return '<{0.__class__.__name__} id={0.id} name={0.name!r}>'.format(self) 90 | 91 | def __str__(self): 92 | return self.name 93 | 94 | @property 95 | def created_at(self): 96 | """:class:`datetime.datetime`: Returns the sticker's creation time in UTC as a naive datetime.""" 97 | return snowflake_time(self.id) 98 | 99 | @property 100 | def image_url(self): 101 | """Returns an :class:`Asset` for the sticker's image. 102 | 103 | .. note:: 104 | This will return ``None`` if the format is ``StickerType.lottie``. 105 | 106 | Returns 107 | ------- 108 | Optional[:class:`Asset`] 109 | The resulting CDN asset. 110 | """ 111 | return self.image_url_as() 112 | 113 | def image_url_as(self, *, size=1024): 114 | """Optionally returns an :class:`Asset` for the sticker's image. 115 | 116 | The size must be a power of 2 between 16 and 4096. 117 | 118 | .. note:: 119 | This will return ``None`` if the format is ``StickerType.lottie``. 120 | 121 | Parameters 122 | ----------- 123 | size: :class:`int` 124 | The size of the image to display. 125 | 126 | Raises 127 | ------ 128 | InvalidArgument 129 | Invalid ``size``. 130 | 131 | Returns 132 | ------- 133 | Optional[:class:`Asset`] 134 | The resulting CDN asset or ``None``. 135 | """ 136 | if self.format is StickerType.lottie: 137 | return None 138 | 139 | return Asset._from_sticker_url(self._state, self, size=size) 140 | -------------------------------------------------------------------------------- /discord/team.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from . import utils 28 | from .user import BaseUser 29 | from .asset import Asset 30 | from .enums import TeamMembershipState, try_enum 31 | 32 | __all__ = ( 33 | 'Team', 34 | 'TeamMember', 35 | ) 36 | 37 | class Team: 38 | """Represents an application team for a bot provided by Discord. 39 | 40 | Attributes 41 | ------------- 42 | id: :class:`int` 43 | The team ID. 44 | name: :class:`str` 45 | The team name 46 | icon: Optional[:class:`str`] 47 | The icon hash, if it exists. 48 | owner_id: :class:`int` 49 | The team's owner ID. 50 | members: List[:class:`TeamMember`] 51 | A list of the members in the team 52 | 53 | .. versionadded:: 1.3 54 | """ 55 | __slots__ = ('_state', 'id', 'name', 'icon', 'owner_id', 'members') 56 | 57 | def __init__(self, state, data): 58 | self._state = state 59 | 60 | self.id = utils._get_as_snowflake(data, 'id') 61 | self.name = data['name'] 62 | self.icon = data['icon'] 63 | self.owner_id = utils._get_as_snowflake(data, 'owner_user_id') 64 | self.members = [TeamMember(self, self._state, member) for member in data['members']] 65 | 66 | def __repr__(self): 67 | return '<{0.__class__.__name__} id={0.id} name={0.name}>'.format(self) 68 | 69 | @property 70 | def icon_url(self): 71 | """:class:`.Asset`: Retrieves the team's icon asset. 72 | 73 | This is equivalent to calling :meth:`icon_url_as` with 74 | the default parameters ('webp' format and a size of 1024). 75 | """ 76 | return self.icon_url_as() 77 | 78 | def icon_url_as(self, *, format='webp', size=1024): 79 | """Returns an :class:`Asset` for the icon the team has. 80 | 81 | The format must be one of 'webp', 'jpeg', 'jpg' or 'png'. 82 | The size must be a power of 2 between 16 and 4096. 83 | 84 | .. versionadded:: 2.0 85 | 86 | Parameters 87 | ----------- 88 | format: :class:`str` 89 | The format to attempt to convert the icon to. Defaults to 'webp'. 90 | size: :class:`int` 91 | The size of the image to display. 92 | 93 | Raises 94 | ------ 95 | InvalidArgument 96 | Bad image format passed to ``format`` or invalid ``size``. 97 | 98 | Returns 99 | -------- 100 | :class:`Asset` 101 | The resulting CDN asset. 102 | """ 103 | return Asset._from_icon(self._state, self, 'team', format=format, size=size) 104 | 105 | @property 106 | def owner(self): 107 | """Optional[:class:`TeamMember`]: The team's owner.""" 108 | return utils.get(self.members, id=self.owner_id) 109 | 110 | class TeamMember(BaseUser): 111 | """Represents a team member in a team. 112 | 113 | .. container:: operations 114 | 115 | .. describe:: x == y 116 | 117 | Checks if two team members are equal. 118 | 119 | .. describe:: x != y 120 | 121 | Checks if two team members are not equal. 122 | 123 | .. describe:: hash(x) 124 | 125 | Return the team member's hash. 126 | 127 | .. describe:: str(x) 128 | 129 | Returns the team member's name with discriminator. 130 | 131 | .. versionadded:: 1.3 132 | 133 | Attributes 134 | ------------- 135 | name: :class:`str` 136 | The team member's username. 137 | id: :class:`int` 138 | The team member's unique ID. 139 | discriminator: :class:`str` 140 | The team member's discriminator. This is given when the username has conflicts. 141 | avatar: Optional[:class:`str`] 142 | The avatar hash the team member has. Could be None. 143 | bot: :class:`bool` 144 | Specifies if the user is a bot account. 145 | team: :class:`Team` 146 | The team that the member is from. 147 | membership_state: :class:`TeamMembershipState` 148 | The membership state of the member (e.g. invited or accepted) 149 | """ 150 | __slots__ = BaseUser.__slots__ + ('team', 'membership_state', 'permissions') 151 | 152 | def __init__(self, team, state, data): 153 | self.team = team 154 | self.membership_state = try_enum(TeamMembershipState, data['membership_state']) 155 | self.permissions = data['permissions'] 156 | super().__init__(state=state, data=data['user']) 157 | 158 | def __repr__(self): 159 | return '<{0.__class__.__name__} id={0.id} name={0.name!r} ' \ 160 | 'discriminator={0.discriminator!r} membership_state={0.membership_state!r}>'.format(self) 161 | -------------------------------------------------------------------------------- /discord/template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015-present Rapptz 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | from .utils import parse_time, _get_as_snowflake, _bytes_to_base64_data 28 | from .enums import VoiceRegion 29 | from .guild import Guild 30 | 31 | __all__ = ( 32 | 'Template', 33 | ) 34 | 35 | class _FriendlyHttpAttributeErrorHelper: 36 | __slots__ = () 37 | 38 | def __getattr__(self, attr): 39 | raise AttributeError('PartialTemplateState does not support http methods.') 40 | 41 | class _PartialTemplateState: 42 | def __init__(self, *, state): 43 | self.__state = state 44 | self.http = _FriendlyHttpAttributeErrorHelper() 45 | 46 | @property 47 | def is_bot(self): 48 | return self.__state.is_bot 49 | 50 | @property 51 | def shard_count(self): 52 | return self.__state.shard_count 53 | 54 | @property 55 | def user(self): 56 | return self.__state.user 57 | 58 | @property 59 | def self_id(self): 60 | return self.__state.user.id 61 | 62 | @property 63 | def member_cache_flags(self): 64 | return self.__state.member_cache_flags 65 | 66 | def store_emoji(self, guild, packet): 67 | return None 68 | 69 | def _get_voice_client(self, id): 70 | return None 71 | 72 | def _get_message(self, id): 73 | return None 74 | 75 | async def query_members(self, **kwargs): 76 | return [] 77 | 78 | def __getattr__(self, attr): 79 | raise AttributeError('PartialTemplateState does not support {0!r}.'.format(attr)) 80 | 81 | class Template: 82 | """Represents a Discord template. 83 | 84 | .. versionadded:: 1.4 85 | 86 | Attributes 87 | ----------- 88 | code: :class:`str` 89 | The template code. 90 | uses: :class:`int` 91 | How many times the template has been used. 92 | name: :class:`str` 93 | The name of the template. 94 | description: :class:`str` 95 | The description of the template. 96 | creator: :class:`User` 97 | The creator of the template. 98 | created_at: :class:`datetime.datetime` 99 | When the template was created. 100 | updated_at: :class:`datetime.datetime` 101 | When the template was last updated (referred to as "last synced" in the client). 102 | source_guild: :class:`Guild` 103 | The source guild. 104 | """ 105 | 106 | def __init__(self, *, state, data): 107 | self._state = state 108 | self._store(data) 109 | 110 | def _store(self, data): 111 | self.code = data['code'] 112 | self.uses = data['usage_count'] 113 | self.name = data['name'] 114 | self.description = data['description'] 115 | creator_data = data.get('creator') 116 | self.creator = None if creator_data is None else self._state.store_user(creator_data) 117 | 118 | self.created_at = parse_time(data.get('created_at')) 119 | self.updated_at = parse_time(data.get('updated_at')) 120 | 121 | id = _get_as_snowflake(data, 'source_guild_id') 122 | 123 | guild = self._state._get_guild(id) 124 | 125 | if guild is None: 126 | source_serialised = data['serialized_source_guild'] 127 | source_serialised['id'] = id 128 | state = _PartialTemplateState(state=self._state) 129 | guild = Guild(data=source_serialised, state=state) 130 | 131 | self.source_guild = guild 132 | 133 | def __repr__(self): 134 | return '