From b940dddaa979ebf97a9114a3023083b396c1c83a Mon Sep 17 00:00:00 2001 From: jezscha Date: Mon, 7 Jun 2021 08:23:49 +0000 Subject: [PATCH 01/41] Create draft PR for #1651 From fe794a26535496a7b1efb38111714b60f21a7439 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 7 Jun 2021 12:01:12 +0200 Subject: [PATCH 02/41] Nuke: refactory prenode write node creation --- openpype/hosts/nuke/api/lib.py | 54 ++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3c41574dbf..7c274a03c7 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -298,18 +298,21 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): review (bool): adding review knob Example: - prenodes = [( - "NameNode", # string - "NodeClass", # string - ( # OrderDict: knob and values pairs - ("knobName", "knobValue"), - ("knobName", "knobValue") - ), - ( # list outputs - "firstPostNodeName", - "secondPostNodeName" - ) - ) + prenodes = [ + { + "nodeName": { + "class": "" # string + "knobs": [ + ("knobName": value), + ... + ], + "dependent": [ + following_node_01, + ... + ] + } + }, + ... ] Return: @@ -385,35 +388,42 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): prev_node.hideControlPanel() # creating pre-write nodes `prenodes` if prenodes: - for name, klass, properties, set_output_to in prenodes: + for node in prenodes: + # get attributes + name = node["name"] + klass = node["class"] + knobs = node["knobs"] + dependent = node["dependent"] + # create node now_node = nuke.createNode(klass, "name {}".format(name)) now_node.hideControlPanel() # add data to knob - for k, v in properties: + for _knob in knobs: + knob, value = _knob try: - now_node[k].value() + now_node[knob].value() except NameError: log.warning( "knob `{}` does not exist on node `{}`".format( - k, now_node["name"].value() + knob, now_node["name"].value() )) continue - if k and v: - now_node[k].setValue(str(v)) + if knob and value: + now_node[knob].setValue(value) # connect to previous node - if set_output_to: - if isinstance(set_output_to, (tuple or list)): - for i, node_name in enumerate(set_output_to): + if dependent: + if isinstance(dependent, (tuple or list)): + for i, node_name in enumerate(dependent): input_node = nuke.createNode( "Input", "name {}".format(node_name)) input_node.hideControlPanel() now_node.setInput(1, input_node) - elif isinstance(set_output_to, str): + elif isinstance(dependent, str): input_node = nuke.createNode( "Input", "name {}".format(node_name)) input_node.hideControlPanel() From e723e456347e6b544aed3d2c783610d388497498 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 7 Jun 2021 12:01:33 +0200 Subject: [PATCH 03/41] Nuke: adding crop node before write node --- .../plugins/create/create_write_render.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 04983e9c75..9ddf0e4a87 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -99,10 +99,28 @@ class CreateWriteRender(plugin.PypeCreator): "fpath_template": ("{work}/renders/nuke/{subset}" "/{subset}.{frame}.{ext}")}) + # add crop node to cut off all outside of format bounding box + _prenodes = [ + { + "name": "Crop01", + "class": "Crop", + "knobs": [ + ("box", [ + 0.0, + 0.0, + selected_node.width(), + selected_node.height() + ]) + ], + "dependent": None + } + ] + write_node = lib.create_write_node( self.data["subset"], write_data, - input=selected_node) + input=selected_node, + prenodes=_prenodes) # relinking to collected connections for i, input in enumerate(inputs): From 50ab558291e487fcf2883351ea48aea82d409fda Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 7 Jun 2021 12:05:15 +0200 Subject: [PATCH 04/41] Nuke: removing `ValidateNukeWriteBoundingBox` plugin and settings --- .../publish/validate_write_bounding_box.py | 106 ------------------ .../defaults/project_settings/nuke.json | 5 - .../schemas/schema_nuke_publish.json | 4 - 3 files changed, 115 deletions(-) delete mode 100644 openpype/hosts/nuke/plugins/publish/validate_write_bounding_box.py diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_bounding_box.py b/openpype/hosts/nuke/plugins/publish/validate_write_bounding_box.py deleted file mode 100644 index e4b7c77a25..0000000000 --- a/openpype/hosts/nuke/plugins/publish/validate_write_bounding_box.py +++ /dev/null @@ -1,106 +0,0 @@ -import nuke - -import pyblish.api - - -class RepairNukeBoundingBoxAction(pyblish.api.Action): - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - - # Get the errored instances - failed = [] - for result in context.data["results"]: - if (result["error"] is not None and result["instance"] is not None - and result["instance"] not in failed): - failed.append(result["instance"]) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(failed, plugin) - - for instance in instances: - crop = instance[0].dependencies()[0] - if crop.Class() != "Crop": - crop = nuke.nodes.Crop(inputs=[instance[0].input(0)]) - - xpos = instance[0].xpos() - ypos = instance[0].ypos() - 26 - - dependent_ypos = instance[0].dependencies()[0].ypos() - if (instance[0].ypos() - dependent_ypos) <= 51: - xpos += 110 - - crop.setXYpos(xpos, ypos) - - instance[0].setInput(0, crop) - - crop["box"].setValue( - ( - 0.0, - 0.0, - instance[0].input(0).width(), - instance[0].input(0).height() - ) - ) - - -class ValidateNukeWriteBoundingBox(pyblish.api.InstancePlugin): - """Validates write bounding box. - - Ffmpeg does not support bounding boxes outside of the image - resolution a crop is needed. This needs to validate all frames, as each - rendered exr can break the ffmpeg transcode. - """ - - order = pyblish.api.ValidatorOrder - optional = True - families = ["render", "render.local", "render.farm"] - label = "Write Bounding Box" - hosts = ["nuke"] - actions = [RepairNukeBoundingBoxAction] - - def process(self, instance): - - # Skip bounding box check if a crop node exists. - if instance[0].dependencies()[0].Class() == "Crop": - return - - msg = "Bounding box is outside the format." - assert self.check_bounding_box(instance), msg - - def check_bounding_box(self, instance): - node = instance[0] - - first_frame = instance.data["frameStart"] - last_frame = instance.data["frameEnd"] - - format_width = node.format().width() - format_height = node.format().height() - - # The trick is that we need to execute() some node every time we go to - # a next frame, to update the context. - # So we create a CurveTool that we can execute() on every frame. - temporary_node = nuke.nodes.CurveTool() - bbox_check = True - for frame in range(first_frame, last_frame + 1): - # Workaround to update the tree - nuke.execute(temporary_node, frame, frame) - - x = node.bbox().x() - y = node.bbox().y() - w = node.bbox().w() - h = node.bbox().h() - - if x < 0 or (x + w) > format_width: - bbox_check = False - break - - if y < 0 or (y + h) > format_height: - bbox_check = False - break - - nuke.delete(temporary_node) - return bbox_check diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 13e1924b36..3736f67268 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -43,11 +43,6 @@ "optional": true, "active": true }, - "ValidateNukeWriteBoundingBox": { - "enabled": true, - "optional": true, - "active": true - }, "ExtractThumbnail": { "enabled": true, "nodes": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 087e6c13a9..6873ed5190 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -64,10 +64,6 @@ { "key": "ValidateScript", "label": "Validate script settings" - }, - { - "key": "ValidateNukeWriteBoundingBox", - "label": "Validate and Write Bounding Box" } ] }, From 9d055e9adbded495707bded36f86d3ca625f2040 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 11:44:39 +0200 Subject: [PATCH 05/41] client/#75 - Added settings and defaults for Slack integration --- .../defaults/project_settings/slack.json | 18 ++++ .../defaults/system_settings/modules.json | 3 + .../schemas/projects_schema/schema_main.json | 4 + .../projects_schema/schema_project_slack.json | 89 +++++++++++++++++++ .../schemas/system_schema/schema_modules.json | 21 ++++- 5 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 openpype/settings/defaults/project_settings/slack.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_slack.json diff --git a/openpype/settings/defaults/project_settings/slack.json b/openpype/settings/defaults/project_settings/slack.json new file mode 100644 index 0000000000..2be2c222ae --- /dev/null +++ b/openpype/settings/defaults/project_settings/slack.json @@ -0,0 +1,18 @@ +{ + "publish": { + "CollectSlackFamilies": { + "enabled": true, + "optional": true, + "token": "", + "profiles": [ + { + "families": [], + "hosts": [], + "tasks": [], + "channel": [], + "message": "" + } + ] + } + } +} \ No newline at end of file diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 31da9e9e7b..f759546dca 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -167,5 +167,8 @@ }, "project_manager": { "enabled": true + }, + "slack": { + "enabled": true } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 64c5a7f366..bee9712878 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -66,6 +66,10 @@ "type": "schema", "name": "schema_project_deadline" }, + { + "type": "schema", + "name": "schema_project_slack" + }, { "type": "schema", "name": "schema_project_maya" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json new file mode 100644 index 0000000000..10ab86fa97 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -0,0 +1,89 @@ +{ + "type": "dict", + "key": "slack", + "label": "Slack", + "collapsible": true, + "is_file": true, + "children": [ + + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "key": "CollectSlackFamilies", + "label": "Notification to Slack", + "use_label_wrap": true, + "children": [ + { + "type": "label", + "label": "" + }, + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "text", + "key": "token", + "label": "Auth Token" + }, + { + "type": "list", + "collapsible": true, + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Host names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "list", + "object_type": "text", + "key": "channel", + "label": "Channel" + }, + { + "type": "text", + "multiline": true, + "key": "message", + "label": "Message" + } + ] + } + } + ] + } + ] + } + ] +} diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index d6527f368d..7d734ff4fd 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -82,7 +82,8 @@ "label": "Workspace name" } ] - }, { + }, + { "type": "dict", "key": "sync_server", "label": "Site Sync", @@ -114,7 +115,8 @@ } } ] - },{ + }, + { "type": "dict", "key": "deadline", "label": "Deadline", @@ -206,6 +208,21 @@ "label": "Enabled" } ] + }, + { + "type": "dict", + "key": "slack", + "label": "Slack Notifications", + "collapsible": true, + "require_restart": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] } ] } From 841124e87c9aa888b89f4318feeab51b6302aa64 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 11:48:32 +0200 Subject: [PATCH 06/41] client/#75 - Added Slack module Added collector Added integrator --- openpype/modules/__init__.py | 5 +- openpype/modules/slack/README.md | 46 ++++++++++++++++ openpype/modules/slack/__init__.py | 7 +++ openpype/modules/slack/manifest.yml | 22 ++++++++ .../plugins/publish/collect_slack_family.py | 55 +++++++++++++++++++ .../plugins/publish/integrate_slack_api.py | 54 ++++++++++++++++++ openpype/modules/slack/slack_module.py | 26 +++++++++ 7 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 openpype/modules/slack/README.md create mode 100644 openpype/modules/slack/__init__.py create mode 100644 openpype/modules/slack/manifest.yml create mode 100644 openpype/modules/slack/plugins/publish/collect_slack_family.py create mode 100644 openpype/modules/slack/plugins/publish/integrate_slack_api.py create mode 100644 openpype/modules/slack/slack_module.py diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index debeeed6bf..d6fb9c0aef 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -39,6 +39,7 @@ from .deadline import DeadlineModule from .project_manager_action import ProjectManagerAction from .standalonepublish_action import StandAlonePublishAction from .sync_server import SyncServerModule +from .slack import SlackIntegrationModule __all__ = ( @@ -77,5 +78,7 @@ __all__ = ( "ProjectManagerAction", "StandAlonePublishAction", - "SyncServerModule" + "SyncServerModule", + + "SlackIntegrationModule" ) diff --git a/openpype/modules/slack/README.md b/openpype/modules/slack/README.md new file mode 100644 index 0000000000..5e099be9e7 --- /dev/null +++ b/openpype/modules/slack/README.md @@ -0,0 +1,46 @@ +Slack notification for publishing +--------------------------------- + +This module allows configuring profiles(when to trigger, for which combination of task, host and family) +and templates(could contain {} placeholder, as "{asset} published"). + +These need to be configured in +```Project settings > Slack > Publish plugins > Notification to Slack``` + +Slack module must be enabled in System Setting, could be configured per Project. + +## App installation + +Slack app needs to be installed to company's workspace. Attached .yaml file could be +used, follow instruction https://api.slack.com/reference/manifests#using + +## Settings + +### Token +Most important for module to work is to fill authentication token +```Project settings > Slack > Publish plugins > Token``` + +This token should be available after installation of app in Slack dashboard. +It is possible to create multiple tokens and configure different scopes for them. + +### Profiles +Profiles are used to select when to trigger notification. One or multiple profiles +could be configured, 'family', 'task name' (regex available) and host combination is needed. + +Eg. If I want to be notified when render is published from Maya, setting is: + +- family: 'render' +- host: 'Maya' + +### Channel +Message could be delivered to one or multiple channels, by default app allows Slack bot +to send messages to 'public' channels (eg. bot doesn't need to join channel first). + +This could be configured in Slack dashboard and scopes might be modified. + +### Message +Placeholders {} could be used in message content which will be filled during runtime. +Only keys available in 'anatomyData' are currently implemented. + +Example of message content: +```{SUBSET} for {Asset} was published.``` \ No newline at end of file diff --git a/openpype/modules/slack/__init__.py b/openpype/modules/slack/__init__.py new file mode 100644 index 0000000000..3c2a50aa35 --- /dev/null +++ b/openpype/modules/slack/__init__.py @@ -0,0 +1,7 @@ +from .slack_module import ( + SlackIntegrationModule +) + +__all__ = ( + "SlackIntegrationModule" +) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml new file mode 100644 index 0000000000..290093388a --- /dev/null +++ b/openpype/modules/slack/manifest.yml @@ -0,0 +1,22 @@ +_metadata: + major_version: 1 + minor_version: 1 +display_information: + name: OpenPypeNotifier +features: + app_home: + home_tab_enabled: false + messages_tab_enabled: true + messages_tab_read_only_enabled: true + bot_user: + display_name: OpenPypeNotifier + always_online: false +oauth_config: + scopes: + bot: + - chat:write + - chat:write.public +settings: + org_deploy_enabled: false + socket_mode_enabled: false + is_hosted: false diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py new file mode 100644 index 0000000000..51eebac052 --- /dev/null +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -0,0 +1,55 @@ +from avalon import io +import pyblish.api + +from openpype.lib.profiles_filtering import filter_profiles + + +class CollectSlackFamilies(pyblish.api.InstancePlugin): + """Collect family for Slack notification + + Expects configured profile in + Project settings > Slack > Publish plugins > Notification to Slack + + Add Slack family to those instance that should be messaged to Slack + """ + order = pyblish.api.CollectorOrder + 0.4999 + label = 'Collect Slack family' + + profiles = None + + def process(self, instance): + task_name = io.Session.get("AVALON_TASK") + family = self.main_family_from_instance(instance) + + key_values = { + "families": family, + "tasks": task_name, + "hosts": instance.data["anatomyData"]["app"], + } + self.log.debug("key_values {}".format(key_values)) + profile = filter_profiles(self.profiles, key_values, + logger=self.log) + + # make slack publishable + if profile: + if instance.data.get('families'): + instance.data['families'].append('slack') + else: + instance.data['families'] = ['slack'] + + instance.data["slack_channel"] = profile["channel"] + instance.data["slack_message"] = profile["message"] + + slack_token = (instance.context.data["project_settings"] + ["slack"] + ["publish"] + ["CollectSlackFamilies"] + ["token"]) + instance.data["slack_token"] = slack_token + + def main_family_from_instance(self, instance): # TODO yank from integrate + """Returns main family of entered instance.""" + family = instance.data.get("family") + if not family: + family = instance.data["families"][0] + return family diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py new file mode 100644 index 0000000000..d9a172b89b --- /dev/null +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -0,0 +1,54 @@ +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +import pyblish.api +from openpype.lib.plugin_tools import prepare_template_data + + +class IntegrateSlackAPI(pyblish.api.InstancePlugin): + """ Send message notification to a channel. + + Triggers on instances with "slack" family, filled by + 'collect_slack_family'. + + Expects configured profile in + Project settings > Slack > Publish plugins > Notification to Slack + + Message template can contain {} placeholders from anatomyData. + """ + order = pyblish.api.IntegratorOrder+0.499 + label = "Integrate Slack Api" + families = ["slack"] + + optional = True + + def process(self, instance): + message_templ = instance.data["slack_message"] + + fill_pairs = set() + for key, value in instance.data["anatomyData"].items(): + if not isinstance(value, str): + continue + fill_pairs.add((key, value)) + self.log.debug("fill_pairs:: {}".format(fill_pairs)) + + message = message_templ.format(**prepare_template_data(fill_pairs)) + + self.log.debug("message:: {}".format(message)) + if '{' in message: + self.log.warning( + "Missing values to fill message properly {}".format(message)) + + return + + for channel in instance.data["slack_channel"]: + try: + client = WebClient(token=instance.data["slack_token"]) + _response = client.chat_postMessage( + channel=channel, + text=message + ) + except SlackApiError as e: + # You will get a SlackApiError if "ok" is False + self.log.warning("Error happened {}".format(e.response[ + "error"])) diff --git a/openpype/modules/slack/slack_module.py b/openpype/modules/slack/slack_module.py new file mode 100644 index 0000000000..53173f6cd0 --- /dev/null +++ b/openpype/modules/slack/slack_module.py @@ -0,0 +1,26 @@ +import os +from openpype.modules import ( + PypeModule, IPluginPaths) + + +class SlackIntegrationModule(PypeModule, IPluginPaths): + """Allows sending notification to Slack channels during publishing.""" + + name = "slack" + + def initialize(self, modules_settings): + slack_settings = modules_settings[self.name] + self.enabled = slack_settings["enabled"] + + def connect_with_modules(self, _enabled_modules): + """Nothing special.""" + return + + def get_plugin_paths(self): + """Deadline plugin paths.""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + return { + "publish": [os.path.join(current_dir, "plugins", "publish")] + } + + From 83d97df0dbc47ebd00dfdab9763e20f67a86e3f7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 11:48:56 +0200 Subject: [PATCH 07/41] client/#75 - Added Slack module documentation --- website/docs/assets/slack_project.png | Bin 0 -> 40559 bytes website/docs/assets/slack_system.png | Bin 0 -> 22176 bytes website/docs/assets/slack_token.png | Bin 0 -> 128036 bytes website/docs/module_slack.md | 55 ++++++++++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 website/docs/assets/slack_project.png create mode 100644 website/docs/assets/slack_system.png create mode 100644 website/docs/assets/slack_token.png create mode 100644 website/docs/module_slack.md diff --git a/website/docs/assets/slack_project.png b/website/docs/assets/slack_project.png new file mode 100644 index 0000000000000000000000000000000000000000..0c608759f819f9c31034ff571d09b580cf36047e GIT binary patch literal 40559 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/slack_system.png b/website/docs/assets/slack_system.png new file mode 100644 index 0000000000000000000000000000000000000000..31183206c9875a17265276633b71757199ce6fcc GIT binary patch literal 22176 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 literal 0 HcmV?d00001 diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md new file mode 100644 index 0000000000..c8e42ababa --- /dev/null +++ b/website/docs/module_slack.md @@ -0,0 +1,55 @@ +--- +id: module_site_sync +title: Slack Integration Administration +sidebar_label: Slack +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +This module allows configuring profiles(when to trigger, for which combination of task, host and family) +and templates(could contain {} placeholder) to send notification to Slack channel(s) +whenever configured asset type is published. + + +## App installation + +Slack application must be installed to company's Slack first. + +Please locate `openpype/modules/slack/manifest.yml` file in deployed OpenPype installation and follow instruction at +https://api.slack.com/reference/manifests#using + + +## System Settings + +To use notifications, *Slack Notifications* needs to be enabled globally in **OpenPype Settings/System/Modules/Slack Notifications**. + +![Configure module](assets/slack_system.png) + + +## Project Settings + +### Token +Most important for module to work is to fill authentication token +```Project settings > Slack > Publish plugins > Token``` + +This token should be available after installation of the app in the Slack dashboard. +It is possible to create multiple tokens and configure different scopes for them. + +![Get token](assets/slack_token.png) + +### Profiles +Profiles are used to select when to trigger notification. One or multiple profiles +could be configured, `Families`, `Task names` (regex available), `Host names` and host combination is needed. + +Eg. If I want to be notified when render is published from Maya, setting is: + +- family: 'render' +- host: 'Maya' + +### Channel +Message could be delivered to one or multiple channels, by default app allows Slack bot +to send messages to 'public' channels (eg. bot doesn't need to join the channel first). + +![Configure module](assets/slack_system.png) From dfb52778a6f0bd19932f8ded09213b90cd8955cf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 11:56:12 +0200 Subject: [PATCH 08/41] client/#75 - Hound --- .../modules/slack/plugins/publish/collect_slack_family.py | 2 +- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 4 ++-- openpype/modules/slack/slack_module.py | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index 51eebac052..6daca1ac3e 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -9,7 +9,7 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): Expects configured profile in Project settings > Slack > Publish plugins > Notification to Slack - + Add Slack family to those instance that should be messaged to Slack """ order = pyblish.api.CollectorOrder + 0.4999 diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index d9a172b89b..6aee6287cc 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -16,7 +16,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): Message template can contain {} placeholders from anatomyData. """ - order = pyblish.api.IntegratorOrder+0.499 + order = pyblish.api.IntegratorOrder + 0.499 label = "Integrate Slack Api" families = ["slack"] @@ -44,7 +44,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): for channel in instance.data["slack_channel"]: try: client = WebClient(token=instance.data["slack_token"]) - _response = client.chat_postMessage( + _ = client.chat_postMessage( channel=channel, text=message ) diff --git a/openpype/modules/slack/slack_module.py b/openpype/modules/slack/slack_module.py index 53173f6cd0..008caf410d 100644 --- a/openpype/modules/slack/slack_module.py +++ b/openpype/modules/slack/slack_module.py @@ -22,5 +22,3 @@ class SlackIntegrationModule(PypeModule, IPluginPaths): return { "publish": [os.path.join(current_dir, "plugins", "publish")] } - - From 5cf841b2ef4a2147a9a79b0feed0eac07e6d9839 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 12:08:45 +0200 Subject: [PATCH 09/41] add new process information to mac launch arguments --- openpype/hooks/pre_mac_launch.py | 2 +- openpype/modules/standalonepublish_action.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_mac_launch.py b/openpype/hooks/pre_mac_launch.py index 3f07ae07db..f85557a4f0 100644 --- a/openpype/hooks/pre_mac_launch.py +++ b/openpype/hooks/pre_mac_launch.py @@ -31,4 +31,4 @@ class LaunchWithTerminal(PreLaunchHook): if len(self.launch_context.launch_args) > 1: self.launch_context.launch_args.insert(1, "--args") # Prepend open arguments - self.launch_context.launch_args.insert(0, ["open", "-a"]) + self.launch_context.launch_args.insert(0, ["open", "-na"]) diff --git a/openpype/modules/standalonepublish_action.py b/openpype/modules/standalonepublish_action.py index 78d87cb6c7..4f87f9704c 100644 --- a/openpype/modules/standalonepublish_action.py +++ b/openpype/modules/standalonepublish_action.py @@ -37,7 +37,7 @@ class StandAlonePublishAction(PypeModule, ITrayAction): args = get_pype_execute_args("standalonepublisher") kwargs = {} if platform.system().lower() == "darwin": - new_args = ["open", "-a", args.pop(0), "--args"] + new_args = ["open", "-na", args.pop(0), "--args"] new_args.extend(args) args = new_args From 0cd3b977ea373cba4a8bdaed7b4753f3238b46e8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 13:00:27 +0200 Subject: [PATCH 10/41] client/#75 - added missed requirements --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 7bde62a848..1425b88236 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ jinxed = [ ] python3-xlib = { version="*", markers = "sys_platform == 'linux'"} enlighten = "^1.9.0" +slack-sdk = "^3.6.0" [tool.poetry.dev-dependencies] flake8 = "^3.7" From b3765e933122245565949d81c6987ecec7d2a6ff Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 14:49:28 +0200 Subject: [PATCH 11/41] client/#75 - added Python2 support --- openpype/modules/slack/__init__.py | 6 +- .../slack/launch_hooks/pre_python2_vendor.py | 34 ++++++++++++ .../plugins/publish/collect_slack_family.py | 1 + .../plugins/publish/integrate_slack_api.py | 55 ++++++++++++++----- openpype/modules/slack/slack_module.py | 10 +++- 5 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 openpype/modules/slack/launch_hooks/pre_python2_vendor.py diff --git a/openpype/modules/slack/__init__.py b/openpype/modules/slack/__init__.py index 3c2a50aa35..0a09d24ed4 100644 --- a/openpype/modules/slack/__init__.py +++ b/openpype/modules/slack/__init__.py @@ -1,7 +1,9 @@ from .slack_module import ( - SlackIntegrationModule + SlackIntegrationModule, + SLACK_MODULE_DIR ) __all__ = ( - "SlackIntegrationModule" + "SlackIntegrationModule", + "SLACK_MODULE_DIR" ) diff --git a/openpype/modules/slack/launch_hooks/pre_python2_vendor.py b/openpype/modules/slack/launch_hooks/pre_python2_vendor.py new file mode 100644 index 0000000000..a2c1f8a9e0 --- /dev/null +++ b/openpype/modules/slack/launch_hooks/pre_python2_vendor.py @@ -0,0 +1,34 @@ +import os +from openpype.lib import PreLaunchHook +from openpype.modules.slack import SLACK_MODULE_DIR + + +class PrePython2Support(PreLaunchHook): + """Add python slack api module for Python 2 to PYTHONPATH. + + Path to vendor modules is added to the beginning of PYTHONPATH. + """ + + def execute(self): + if not self.application.use_python_2: + return + + self.log.info("Adding Slack Python 2 packages to PYTHONPATH.") + + # Prepare vendor dir path + python_2_vendor = os.path.join(SLACK_MODULE_DIR, "python2_vendor") + + # Add Python 2 modules + python_paths = [ + # `python-ftrack-api` + os.path.join(python_2_vendor, "python-slack-sdk-1", "slackclient"), + os.path.join(python_2_vendor, "python-slack-sdk-1") + ] + self.log.info("python_paths {}".format(python_paths)) + # Load PYTHONPATH from current launch context + python_path = self.launch_context.env.get("PYTHONPATH") + if python_path: + python_paths.append(python_path) + + # Set new PYTHONPATH to launch context environments + self.launch_context.env["PYTHONPATH"] = os.pathsep.join(python_paths) diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index 6daca1ac3e..9ca5b9d718 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -30,6 +30,7 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): profile = filter_profiles(self.profiles, key_values, logger=self.log) + self.log.debug("profile ::{}".format(profile)) # make slack publishable if profile: if instance.data.get('families'): diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 6aee6287cc..3244df6b16 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -1,5 +1,10 @@ -from slack_sdk import WebClient -from slack_sdk.errors import SlackApiError +try: + from slackclient import SlackClient + python2 = True +except ImportError: + python2 = False + from slack_sdk import WebClient + from slack_sdk.errors import SlackApiError import pyblish.api from openpype.lib.plugin_tools import prepare_template_data @@ -30,7 +35,6 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if not isinstance(value, str): continue fill_pairs.add((key, value)) - self.log.debug("fill_pairs:: {}".format(fill_pairs)) message = message_templ.format(**prepare_template_data(fill_pairs)) @@ -42,13 +46,38 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): return for channel in instance.data["slack_channel"]: - try: - client = WebClient(token=instance.data["slack_token"]) - _ = client.chat_postMessage( - channel=channel, - text=message - ) - except SlackApiError as e: - # You will get a SlackApiError if "ok" is False - self.log.warning("Error happened {}".format(e.response[ - "error"])) + if python2: + self._python2_call(instance.data["slack_token"], + channel, + message) + else: + self._python3_call(instance.data["slack_token"], + channel, + message) + + def _python2_call(self, token, channel, message): + try: + client = SlackClient(token) + response = client.api_call( + "chat.postMessage", + channel=channel, + text=message + ) + if response.get("error"): + self.log.warning("Error happened: {}".format( + response.get("error"))) + except Exception as e: + # You will get a SlackApiError if "ok" is False + self.log.warning("Error happened: {}".format(str(e))) + + def _python3_call(self, token, channel, message): + try: + client = WebClient(token=token) + _ = client.chat_postMessage( + channel=channel, + text=message + ) + except SlackApiError as e: + # You will get a SlackApiError if "ok" is False + self.log.warning("Error happened {}".format(e.response[ + "error"])) diff --git a/openpype/modules/slack/slack_module.py b/openpype/modules/slack/slack_module.py index 008caf410d..9dd5a3d02b 100644 --- a/openpype/modules/slack/slack_module.py +++ b/openpype/modules/slack/slack_module.py @@ -1,9 +1,11 @@ import os from openpype.modules import ( - PypeModule, IPluginPaths) + PypeModule, IPluginPaths, ILaunchHookPaths) + +SLACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class SlackIntegrationModule(PypeModule, IPluginPaths): +class SlackIntegrationModule(PypeModule, IPluginPaths, ILaunchHookPaths): """Allows sending notification to Slack channels during publishing.""" name = "slack" @@ -16,6 +18,10 @@ class SlackIntegrationModule(PypeModule, IPluginPaths): """Nothing special.""" return + def get_launch_hook_paths(self): + """Implementation of `ILaunchHookPaths`.""" + return os.path.join(SLACK_MODULE_DIR, "launch_hooks") + def get_plugin_paths(self): """Deadline plugin paths.""" current_dir = os.path.dirname(os.path.abspath(__file__)) From 0651bfe5225f8094b1e20c5817d704042eef4f69 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 16:47:16 +0200 Subject: [PATCH 12/41] client/#75 - added possibility to upload 'thumbnail' file --- openpype/modules/slack/README.md | 5 +- .../plugins/publish/integrate_slack_api.py | 94 +++++++++++++++---- website/docs/module_slack.md | 3 + 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/openpype/modules/slack/README.md b/openpype/modules/slack/README.md index 5e099be9e7..a189fdf978 100644 --- a/openpype/modules/slack/README.md +++ b/openpype/modules/slack/README.md @@ -43,4 +43,7 @@ Placeholders {} could be used in message content which will be filled during run Only keys available in 'anatomyData' are currently implemented. Example of message content: -```{SUBSET} for {Asset} was published.``` \ No newline at end of file +```{SUBSET} for {Asset} was published.``` + +Integration can upload 'thumbnail' file (if present in instance), for that bot must be +manually added to target channel by Slack admin! \ No newline at end of file diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 3244df6b16..0c7d3c5ebd 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -1,3 +1,4 @@ +import os try: from slackclient import SlackClient python2 = True @@ -17,7 +18,10 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): 'collect_slack_family'. Expects configured profile in - Project settings > Slack > Publish plugins > Notification to Slack + Project settings > Slack > Publish plugins > Notification to Slack. + + If instance contains 'thumbnail' it uploads it. Bot must be present + in the target channel. Message template can contain {} placeholders from anatomyData. """ @@ -45,39 +49,91 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): return + published_path = self._get_thumbnail_path(instance) + for channel in instance.data["slack_channel"]: if python2: self._python2_call(instance.data["slack_token"], channel, - message) + message, + published_path) else: self._python3_call(instance.data["slack_token"], channel, - message) + message, + published_path) - def _python2_call(self, token, channel, message): + def _get_thumbnail_path(self, instance): + """Returns abs url for thumbnail if present in instance repres""" + published_path = None + for comp in instance.data['representations']: + self.log.debug('component {}'.format(comp)) + + if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])): + self.log.debug("thumbnail present") + + comp_files = comp["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + published_path = os.path.join( + comp['stagingDir'], filename + ) + break + return published_path + + def _python2_call(self, token, channel, message, published_path): try: client = SlackClient(token) - response = client.api_call( - "chat.postMessage", - channel=channel, - text=message - ) + if not published_path: + response = client.api_call( + "chat.postMessage", + channel=channel, + text=message + ) + else: + response = client.api_call( + "files.upload", + channels=channel, + initial_comment=message, + file=published_path, + ) if response.get("error"): - self.log.warning("Error happened: {}".format( - response.get("error"))) + error_str = self._enrich_error(str(response.get("error")), + channel) + self.log.warning("Error happened: {}".format(error_str)) except Exception as e: # You will get a SlackApiError if "ok" is False - self.log.warning("Error happened: {}".format(str(e))) + error_str = self._enrich_error(str(e), channel) + self.log.warning("Error happened: {}".format(error_str)) - def _python3_call(self, token, channel, message): + def _python3_call(self, token, channel, message, published_path): try: client = WebClient(token=token) - _ = client.chat_postMessage( - channel=channel, - text=message - ) + if not published_path: + _ = client.chat_postMessage( + channel=channel, + text=message + ) + else: + _ = client.files_upload( + channels=channel, + initial_comment=message, + file=published_path, + ) except SlackApiError as e: # You will get a SlackApiError if "ok" is False - self.log.warning("Error happened {}".format(e.response[ - "error"])) + error_str = self._enrich_error(str(e.response["error"]), channel) + self.log.warning("Error happened {}".format(error_str)) + + def _enrich_error(self, error_str, channel): + """Enhance known errors with more helpful notations.""" + if 'not_in_channel' in error_str: + # there is no file.write.public scope, app must be explicitly in + # the channel + msg = " - application must added to channel '{}'.".format(channel) + error_str += msg + " Ask Slack admin." + + return error_str diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index c8e42ababa..de7014c203 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -53,3 +53,6 @@ Message could be delivered to one or multiple channels, by default app allows Sl to send messages to 'public' channels (eg. bot doesn't need to join the channel first). ![Configure module](assets/slack_system.png) + +Integration can upload 'thumbnail' file (if present in instance), for that bot must be +manually added to target channel by Slack admin! \ No newline at end of file From f1c858fa7d8127f25160e5c14d4584c366cc5a4a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 8 Jun 2021 17:41:30 +0200 Subject: [PATCH 13/41] add trigger for prerelease --- .github/workflows/nightly_merge.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml index 9e6a721f99..98d8cd41f3 100644 --- a/.github/workflows/nightly_merge.yml +++ b/.github/workflows/nightly_merge.yml @@ -1,4 +1,4 @@ -name: Nightly Merge +name: Merge Develop to Main on: schedule: @@ -20,4 +20,10 @@ jobs: github_token: ${{ secrets.ADMIN_TOKEN }} source_ref: 'develop' target_branch: 'main' - commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' \ No newline at end of file + commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' + + - name: Invoke pre-release workflow + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: Nightly Prerelease + token: ${{ secrets.ADMIN_TOKEN }} \ No newline at end of file From 667e344b3d6f18c2d6e9bd9f6331c3503e7fb72f Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 8 Jun 2021 17:56:16 +0200 Subject: [PATCH 14/41] only run prerelease on dispatch --- .github/workflows/prerelease.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 63c7e8081f..45604e431d 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -1,15 +1,12 @@ name: Nightly Prerelease on: - push: - branches: [main] workflow_dispatch: jobs: create_nightly: runs-on: ubuntu-latest - if: github.actor != 'pypebot' steps: - name: 🚛 Checkout Code From a5a9465a9f0ee20c0c1f3dec926bd7cfd5265450 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Tue, 8 Jun 2021 16:01:10 +0000 Subject: [PATCH 15/41] [Automated] Bump version --- CHANGELOG.md | 4 +++- openpype/version.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb7866420..9fae98ec11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,17 @@ # Changelog -## [3.1.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.1.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.0.0...HEAD) #### 🚀 Enhancements +- Nuke - Publish simplification [\#1653](https://github.com/pypeclub/OpenPype/pull/1653) - \#1333 - added tooltip hints to Pyblish buttons [\#1649](https://github.com/pypeclub/OpenPype/pull/1649) #### 🛠Bug fixes +- Mac launch arguments fix [\#1660](https://github.com/pypeclub/OpenPype/pull/1660) - Fix missing dbm python module [\#1652](https://github.com/pypeclub/OpenPype/pull/1652) - Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648) - Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646) diff --git a/openpype/version.py b/openpype/version.py index 24ceaccea9..d6d6a4544b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.1.0-nightly.1" +__version__ = "3.1.0-nightly.2" From 2e4fcfe99de8a35b642ac13c90136d2b7db4d58f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Jun 2021 18:19:15 +0200 Subject: [PATCH 16/41] client/#75 - modifications after review --- .../plugins/publish/collect_slack_family.py | 4 +-- .../plugins/publish/integrate_slack_api.py | 31 +++++++++++------- .../defaults/project_settings/slack.json | 2 +- .../projects_schema/schema_project_slack.json | 11 +++---- website/docs/assets/slack_project.png | Bin 40559 -> 34468 bytes 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index 9ca5b9d718..fefc0c8f56 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -26,7 +26,7 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): "tasks": task_name, "hosts": instance.data["anatomyData"]["app"], } - self.log.debug("key_values {}".format(key_values)) + profile = filter_profiles(self.profiles, key_values, logger=self.log) @@ -43,8 +43,6 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): slack_token = (instance.context.data["project_settings"] ["slack"] - ["publish"] - ["CollectSlackFamilies"] ["token"]) instance.data["slack_token"] = slack_token diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 0c7d3c5ebd..8611e1ebd1 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -34,20 +34,27 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def process(self, instance): message_templ = instance.data["slack_message"] - fill_pairs = set() - for key, value in instance.data["anatomyData"].items(): - if not isinstance(value, str): - continue - fill_pairs.add((key, value)) - - message = message_templ.format(**prepare_template_data(fill_pairs)) + fill_pairs = ( + ("project_name", instance.data["anatomyData"]["project"]["name"]), + ("project_code", instance.data["anatomyData"]["project"]["code"]), + ("asset", instance.data["anatomyData"]["asset"]), + ("subset", instance.data["anatomyData"]["subset"]), + ("task", instance.data["anatomyData"]["task"]), + ("username", instance.data["anatomyData"]["username"]), + ("app", instance.data["anatomyData"]["app"]), + ("family", instance.data["anatomyData"]["family"]), + ("version", str(instance.data["anatomyData"]["version"])), + ) + message = None + try: + message = message_templ.format( + **prepare_template_data(fill_pairs)) + except Exception: + self.log.warning( + "Some keys are missing in {}".format(message_templ), + exc_info=True) self.log.debug("message:: {}".format(message)) - if '{' in message: - self.log.warning( - "Missing values to fill message properly {}".format(message)) - - return published_path = self._get_thumbnail_path(instance) diff --git a/openpype/settings/defaults/project_settings/slack.json b/openpype/settings/defaults/project_settings/slack.json index 2be2c222ae..8453945a5e 100644 --- a/openpype/settings/defaults/project_settings/slack.json +++ b/openpype/settings/defaults/project_settings/slack.json @@ -1,9 +1,9 @@ { + "token": "", "publish": { "CollectSlackFamilies": { "enabled": true, "optional": true, - "token": "", "profiles": [ { "families": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json index 10ab86fa97..7479924d36 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -5,7 +5,11 @@ "collapsible": true, "is_file": true, "children": [ - + { + "type": "text", + "key": "token", + "label": "Auth Token" + }, { "type": "dict", "collapsible": true, @@ -32,11 +36,6 @@ "key": "optional", "label": "Optional" }, - { - "type": "text", - "key": "token", - "label": "Auth Token" - }, { "type": "list", "collapsible": true, diff --git a/website/docs/assets/slack_project.png b/website/docs/assets/slack_project.png index 0c608759f819f9c31034ff571d09b580cf36047e..496013800f8eebbf03828a3018095becd61320b1 100644 GIT binary patch literal 34468 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 Dx4{?{ literal 40559 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 From 7a0ec6bb93be87d5d7487091857b8206c426308a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 12:26:27 +0200 Subject: [PATCH 17/41] implemented get_default_variant for render pass creator --- .../plugins/create/create_render_pass.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 09c68930f2..58158685c7 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -1,4 +1,8 @@ -from avalon.tvpaint import pipeline, lib +from avalon.tvpaint import ( + pipeline, + lib, + CommunicationWrapper +) from openpype.hosts.tvpaint.api import plugin @@ -16,6 +20,36 @@ class CreateRenderPass(plugin.Creator): subset_template = "{family}_{render_layer}_{pass}" + @classmethod + def get_default_variant(cls): + """Default value for variant in Creator tool. + + Method checks if TVPaint implementation is running and tries to find + selected layers from TVPaint. If only one is selected it's name is + returned. + + Returns: + str: Default variant name for Creator tool. + """ + # Validate that communication is initialized + if CommunicationWrapper.communicator: + # Get currently selected layers + layers_data = lib.layers_data() + + selected_layers = [ + layer + for layer in layers_data + if layer["selected"] + ] + # Return layer name if only one is selected + if len(selected_layers) == 1: + return selected_layers[0]["name"] + + # Use defaults + if cls.defaults: + return cls.defaults[0] + return None + def process(self): self.log.debug("Query data from workfile.") instances = pipeline.list_instances() From 8c30e790c5d6575b3d448ca07838a545c9b4146d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 13:47:53 +0200 Subject: [PATCH 18/41] client/#75 - added scope for uploading thumbnails --- openpype/modules/slack/manifest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 290093388a..37d4669903 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -16,6 +16,7 @@ oauth_config: bot: - chat:write - chat:write.public + - files:write settings: org_deploy_enabled: false socket_mode_enabled: false From ac047fbc4f6390933036998201242158a7f73c20 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 14:14:39 +0200 Subject: [PATCH 19/41] client/#75 - refactor, change name --- .../plugins/publish/integrate_slack_api.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 8611e1ebd1..a2a1ce4d55 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -73,20 +73,17 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def _get_thumbnail_path(self, instance): """Returns abs url for thumbnail if present in instance repres""" published_path = None - for comp in instance.data['representations']: - self.log.debug('component {}'.format(comp)) + for repre in instance.data['representations']: + if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])): - self.log.debug("thumbnail present") - - comp_files = comp["files"] - if isinstance(comp_files, (tuple, list, set)): - filename = comp_files[0] + repre_files = repre["files"] + if isinstance(repre_files, (tuple, list, set)): + filename = repre_files[0] else: - filename = comp_files + filename = repre_files published_path = os.path.join( - comp['stagingDir'], filename + repre['stagingDir'], filename ) break return published_path From 0dbf23d463430b5cc9ce36fd95f7cc5a3f9279c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 14:22:52 +0200 Subject: [PATCH 20/41] set outline to none by default --- openpype/style/style.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 8bea01c54e..645e68ce64 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -22,6 +22,7 @@ Enabled vs Disabled logic in most of stylesheets font-size: 9pt; font-family: "Spartan"; font-weight: 450; + outline: none; } QWidget { @@ -145,7 +146,6 @@ QComboBox:disabled { QComboBox QAbstractItemView { border: 1px solid {color:border}; background: {color:bg-inputs}; - outline: none; } QComboBox QAbstractItemView::item:selected { @@ -545,7 +545,6 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #CompleterView { border: 1px solid {color:border}; background: {color:bg-inputs}; - outline: none; } #CompleterView::item { @@ -583,9 +582,6 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } /* Standalone publisher */ -#ComponentList { - outline: none; -} #ComponentItem { background: transparent; From 5c34f996ce28bbe9446d464f9106715b6ca133bc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 15:05:28 +0200 Subject: [PATCH 21/41] client/#75 - added missed vendored slackclient for Python2 --- .../python-slack-sdk-1/.appveyor.yml | 32 + .../python-slack-sdk-1/.coveragerc | 13 + .../python2_vendor/python-slack-sdk-1/.flake8 | 2 + .../.github/contributing.md | 60 + .../.github/issue_template.md | 48 + .../.github/maintainers_guide.md | 100 + .../.github/pull_request_template.md | 8 + .../python-slack-sdk-1/.gitignore | 26 + .../python-slack-sdk-1/.travis.yml | 17 + .../python2_vendor/python-slack-sdk-1/LICENSE | 21 + .../python-slack-sdk-1/MANIFEST.in | 1 + .../python-slack-sdk-1/README.rst | 355 + .../python-slack-sdk-1/docs-src/.gitignore | 1 + .../python-slack-sdk-1/docs-src/Makefile | 225 + .../docs-src/_themes/slack/conf.py | 342 + .../docs-src/_themes/slack/layout.html | 104 + .../docs-src/_themes/slack/localtoc.html | 4 + .../docs-src/_themes/slack/relations.html | 19 + .../docs-src/_themes/slack/sidebar.html | 15 + .../_themes/slack/static/default.css_t | 74 + .../docs-src/_themes/slack/static/docs.css_t | 34 + .../_themes/slack/static/pygments.css_t | 60 + .../docs-src/_themes/slack/theme.conf | 6 + .../python-slack-sdk-1/docs-src/about.rst | 18 + .../python-slack-sdk-1/docs-src/auth.rst | 150 + .../docs-src/basic_usage.rst | 435 + .../python-slack-sdk-1/docs-src/changelog.rst | 156 + .../python-slack-sdk-1/docs-src/conf.py | 338 + .../docs-src/conversations.rst | 166 + .../python-slack-sdk-1/docs-src/faq.rst | 63 + .../python-slack-sdk-1/docs-src/index.rst | 46 + .../python-slack-sdk-1/docs-src/make.bat | 281 + .../python-slack-sdk-1/docs-src/metadata.rst | 19 + .../docs-src/real_time_messaging.rst | 130 + .../python2_vendor/python-slack-sdk-1/docs.sh | 3 + .../python-slack-sdk-1/docs/.buildinfo | 4 + .../python-slack-sdk-1/docs/.nojekyll | 0 .../docs/_static/ajax-loader.gif | Bin 0 -> 673 bytes .../python-slack-sdk-1/docs/_static/basic.css | 676 + .../docs/_static/classic.css | 261 + .../docs/_static/comment-bright.png | Bin 0 -> 756 bytes .../docs/_static/comment-close.png | Bin 0 -> 829 bytes .../docs/_static/comment.png | Bin 0 -> 641 bytes .../docs/_static/default.css | 74 + .../python-slack-sdk-1/docs/_static/docs.css | 34 + .../docs/_static/doctools.js | 315 + .../docs/_static/documentation_options.js | 10 + .../docs/_static/down-pressed.png | Bin 0 -> 222 bytes .../python-slack-sdk-1/docs/_static/down.png | Bin 0 -> 202 bytes .../python-slack-sdk-1/docs/_static/file.png | Bin 0 -> 286 bytes .../docs/_static/jquery-3.2.1.js | 10253 ++++++++++++++++ .../python-slack-sdk-1/docs/_static/jquery.js | 4 + .../docs/_static/language_data.js | 297 + .../python-slack-sdk-1/docs/_static/minus.png | Bin 0 -> 90 bytes .../python-slack-sdk-1/docs/_static/plus.png | Bin 0 -> 90 bytes .../docs/_static/pygments.css | 60 + .../docs/_static/searchtools.js | 481 + .../docs/_static/sidebar.js | 159 + .../docs/_static/underscore-1.3.1.js | 999 ++ .../docs/_static/underscore.js | 31 + .../docs/_static/up-pressed.png | Bin 0 -> 214 bytes .../python-slack-sdk-1/docs/_static/up.png | Bin 0 -> 203 bytes .../docs/_static/websupport.js | 808 ++ .../python-slack-sdk-1/docs/about.html | 196 + .../python-slack-sdk-1/docs/auth.html | 300 + .../python-slack-sdk-1/docs/basic_usage.html | 545 + .../python-slack-sdk-1/docs/changelog.html | 365 + .../docs/conversations.html | 313 + .../python-slack-sdk-1/docs/faq.html | 230 + .../python-slack-sdk-1/docs/genindex.html | 189 + .../python-slack-sdk-1/docs/index.html | 211 + .../python-slack-sdk-1/docs/metadata.html | 182 + .../python-slack-sdk-1/docs/objects.inv | 7 + .../docs/real_time_messaging.html | 290 + .../python-slack-sdk-1/docs/search.html | 204 + .../python-slack-sdk-1/docs/searchindex.js | 1 + .../python-slack-sdk-1/requirements.txt | 3 + .../python-slack-sdk-1/setup.cfg | 5 + .../python-slack-sdk-1/setup.py | 55 + .../slackclient/__init__.py | 1 + .../python-slack-sdk-1/slackclient/channel.py | 46 + .../python-slack-sdk-1/slackclient/client.py | 295 + .../slackclient/exceptions.py | 29 + .../python-slack-sdk-1/slackclient/im.py | 40 + .../python-slack-sdk-1/slackclient/server.py | 377 + .../slackclient/slackrequest.py | 118 + .../python-slack-sdk-1/slackclient/user.py | 27 + .../python-slack-sdk-1/slackclient/util.py | 30 + .../python-slack-sdk-1/slackclient/version.py | 2 + .../python-slack-sdk-1/test_requirements.txt | 7 + .../python-slack-sdk-1/tests/conftest.py | 39 + .../tests/data/channel.created.json | 9 + .../tests/data/im.created.json | 9 + .../tests/data/rtm.start.json | 321 + .../tests/data/slack_logo.png | Bin 0 -> 64759 bytes .../python-slack-sdk-1/tests/test_channel.py | 47 + .../python-slack-sdk-1/tests/test_server.py | 236 + .../tests/test_slackclient.py | 295 + .../tests/test_slackrequest.py | 114 + .../python2_vendor/python-slack-sdk-1/tox.ini | 35 + 100 files changed, 23011 insertions(+) create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.appveyor.yml create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.coveragerc create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.flake8 create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/contributing.md create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/issue_template.md create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/maintainers_guide.md create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/pull_request_template.md create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.gitignore create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/.travis.yml create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/LICENSE create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/MANIFEST.in create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/README.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/.gitignore create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/Makefile create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/conf.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/layout.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/localtoc.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/relations.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/sidebar.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/default.css_t create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/docs.css_t create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/pygments.css_t create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/theme.conf create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/about.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/auth.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/basic_usage.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/changelog.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conf.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conversations.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/faq.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/index.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/make.bat create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/metadata.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/real_time_messaging.rst create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs.sh create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.buildinfo create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.nojekyll create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/ajax-loader.gif create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/basic.css create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/classic.css create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/comment-bright.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/comment-close.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/comment.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/default.css create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/docs.css create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/doctools.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/documentation_options.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/down-pressed.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/down.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/file.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/jquery-3.2.1.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/jquery.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/language_data.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/minus.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/plus.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/pygments.css create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/searchtools.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/sidebar.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/underscore-1.3.1.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/underscore.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/up-pressed.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/up.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/websupport.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/about.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/auth.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/basic_usage.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/changelog.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/conversations.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/faq.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/genindex.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/index.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/metadata.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/objects.inv create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/real_time_messaging.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/search.html create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/searchindex.js create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/requirements.txt create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.cfg create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/__init__.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/channel.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/client.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/exceptions.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/im.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/server.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/slackrequest.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/user.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/util.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/version.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/test_requirements.txt create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/conftest.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/channel.created.json create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/im.created.json create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/rtm.start.json create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/slack_logo.png create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_channel.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_server.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackclient.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackrequest.py create mode 100644 openpype/modules/slack/python2_vendor/python-slack-sdk-1/tox.ini diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.appveyor.yml b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.appveyor.yml new file mode 100644 index 0000000000..79475caa47 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.appveyor.yml @@ -0,0 +1,32 @@ +# credit: https://packaging.python.org/guides/supporting-windows-using-appveyor/ + +environment: + matrix: + - PYTHON: "C:\\Python27" + PYTHON_VERSION: "py27-x86" + - PYTHON: "C:\\Python34" + PYTHON_VERSION: "py34-x86" + - PYTHON: "C:\\Python35" + PYTHON_VERSION: "py35-x86" + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "py27-x64" + - PYTHON: "C:\\Python34-x64" + PYTHON_VERSION: "py34-x64" + - PYTHON: "C:\\Python35-x64" + PYTHON_VERSION: "py35-x64" + +install: + - "%PYTHON%\\python.exe -m pip install wheel" + - "%PYTHON%\\python.exe -m pip install -r requirements.txt" + - "%PYTHON%\\python.exe -m pip install flake8" + - "%PYTHON%\\python.exe -m pip install -r test_requirements.txt" + +build: off + +test_script: + - "%PYTHON%\\python.exe -m flake8 slackclient" + - "%PYTHON%\\python.exe -m pytest --cov-report= --cov=slackclient tests" + +# maybe `after_test:`? +on_success: + - "%PYTHON%\\python.exe -m codecov -e win-%PYTHON_VERSION%" \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.coveragerc b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.coveragerc new file mode 100644 index 0000000000..8d395b7e3b --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True +source = slackclient + +[report] +exclude_lines = + if self.debug: + pragma: no cover + raise NotImplementedError + if __name__ == .__main__.: +ignore_errors = True +omit = + tests/* \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.flake8 b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.flake8 new file mode 100644 index 0000000000..51b50a0465 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 100 \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/contributing.md b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/contributing.md new file mode 100644 index 0000000000..614140b037 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/contributing.md @@ -0,0 +1,60 @@ +# Contributors Guide + +Interested in contributing? Awesome! Before you do though, please read our +[Code of Conduct](https://slackhq.github.io/code-of-conduct). We take it very seriously, and expect that you will as +well. + +There are many ways you can contribute! :heart: + +### Bug Reports and Fixes :bug: +- If you find a bug, please search for it in the [Issues](https://github.com/slackapi/python-slackclient/issues), and if it isn't already tracked, + [create a new issue](https://github.com/slackapi/python-slackclient/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still + be reviewed. +- Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`. +- If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number. + - Include tests that isolate the bug and verifies that it was fixed. + +### New Features :bulb: +- If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackapi/python-slackclient/issues/new). +- Issues that have been identified as a feature request will be labelled `enhancement`. +- If you'd like to implement the new feature, please wait for feedback from the project + maintainers before spending too much time writing the code. In some cases, `enhancement`s may + not align well with the project objectives at the time. + +### Tests :mag:, Documentation :books:, Miscellaneous :sparkles: +- If you'd like to improve the tests, you want to make the documentation clearer, you have an + alternative implementation of something that may have advantages over the way its currently + done, or you have any other change, we would be happy to hear about it! + - If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind. + - If not, [open an Issue](https://github.com/slackapi/python-slackclient/issues/new) to discuss the idea first. + +If you're new to our project and looking for some way to make your first contribution, look for +Issues labelled `good first contribution`. + +## Requirements + +For your contribution to be accepted: + +- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackapi/python-slackclient). +- [x] The test suite must be complete and pass. +- [x] The changes must be approved by code review. +- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number. + +If the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created. + +[Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z) + +## Creating a Pull Request + +1. :fork_and_knife: Fork the repository on GitHub. +2. :runner: Clone/fetch your fork to your local development machine. It's a good idea to run the tests just + to make sure everything is in order. +3. :herb: Create a new branch and check it out. +4. :crystal_ball: Make your changes and commit them locally. Magic happens here! +5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-16`). +6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `master` in this + repository. + +## Maintainers + +There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md). \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/issue_template.md b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/issue_template.md new file mode 100644 index 0000000000..a39638a658 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/issue_template.md @@ -0,0 +1,48 @@ +### Description + +Describe your issue here. + +### What type of issue is this? (place an `x` in one of the `[ ]`) +- [ ] bug +- [ ] enhancement (feature request) +- [ ] question +- [ ] documentation related +- [ ] testing related +- [ ] discussion + +### Requirements (place an `x` in each of the `[ ]`) +* [ ] I've read and understood the [Contributing guidelines](https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md) and have done my best effort to follow them. +* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). +* [ ] I've searched for any related issues and avoided creating a duplicate issue. + +--- + +### Bug Report + +Filling out the following details about bugs will help us solve your issue sooner. + +#### Reproducible in: + +slackclient version: + +python version: + +OS version(s): + +#### Steps to reproduce: + +1. +2. +3. + +#### Expected result: + +What you expected to happen + +#### Actual result: + +What actually happened + +#### Attachments: + +Logs, screenshots, screencast, sample project, funny gif, etc. \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/maintainers_guide.md b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/maintainers_guide.md new file mode 100644 index 0000000000..c1f6a22232 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/maintainers_guide.md @@ -0,0 +1,100 @@ +# Maintainers Guide + +This document describes tools, tasks and workflow that one needs to be familiar with in order to effectively maintain +this project. If you use this package within your own software as is but don't plan on modifying it, this guide is +**not** for you. + +## Tools + +### Python (and friends) + +Not surprisingly, you will need to have Python installed on your system to work on this package. We support non-EOL, +stable versions of CPython. The current supported versions are listed in the CI configurations (e.g. `.travis.yml`). +At a minimum, you should have the latest version of Python 2 and the latest version of Python 3 to develop against. +It's tricky to set up a system that has more than that, so you can lean on the CI servers to test changes on the +in-between versions for you. + +You should also make sure you have the latest versions of `pip`, `setuptools`, `virtualenv`, `wheel`, `twine` and +[`tox`](https://tox.readthedocs.io/en/latest/) installed with your version of Python. + +On macOS, the easiest way to install these tools is by using [Homebrew](https://brew.sh/) and installing the `python` +and `python3` packages. Some of the above packages are preinstalled and you can install the remaining on your own: +`pip install virtualenv wheel twine tox && pip3 install virtualenv twine tox`. + +## Tasks + +### Testing + +Tox is used to run the test suite across multiple isolated versions of Python. It is configured in `tox.ini` to +run all the supported versions of Python, but when you invoke it, you should only select the versions you have on your +system. For example, on a system with Python 2.7.13 and Python 3.6.1, you would run the tests using the following +command: `tox -e flake8,py27,py36` (flake8 is a quality analysis tool). + +### Generating Documentation + +The documentation is generated from the source and templates in the `docs-src` directory. The generated documentation +gets committed to the repo in `docs` and also published to a GitHub Pages website. + +You can generate the documentation by running `tox -e docs`. + +### Releasing + +1. Create the commit for the release: + * Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slackclient/version.py`. + * Commit with a message including the new version number. For example `1.0.6`. + +2. Distribute the release + * Build the distribtuions: `python setup.py sdist bdist_wheel`. This will create artifacts in the `dist` directory. + * Publish to PyPI: `twine upload dist/*`. You must have access to the credentials to publish. + * Create a GitHub Release. You will select the commit with updated version number (e.g. `1.0.6`) to assiociate with + the tag, and name the tag after this version (e.g. `1.0.6`). This will also serve as a Changelog for the project. + Add a description of changes to the Release. Mention Issue and PR #'s and @-mention contributors. + +3. (Slack Internal) Communicate the release internally. Include a link to the GitHub Release. + +4. Announce on Slack Team dev4slack in #slack-api + +5. (Slack Internal) Tweet? Not necessary for patch updates, might be needed for minor updates, definitely needed for + major updates. Include a link to the GitHub Release. + +## Workflow + +### Versioning and Tags + +This project uses semantic versioning, expressed through the numbering scheme of +[PEP-0440](https://www.python.org/dev/peps/pep-0440/). + +### Branches + +`master` is where active development occurs. Long running named feature branches are occasionally created for +collaboration on a feature that has a large scope (because everyone cannot push commits to another person's open Pull +Request). At some point in the future after a major version increment, there may be maintenance branches for older major +versions. + +### Issue Management + +Labels are used to run issues through an organized workflow. Here are the basic definitions: + +* `bug`: A confirmed bug report. A bug is considered confirmed when reproduction steps have been + documented and the issue has been reproduced. +* `enhancement`: A feature request for something this package might not already do. +* `docs`: An issue that is purely about documentation work. +* `tests`: An issue that is purely about testing work. +* `needs feedback`: An issue that may have claimed to be a bug but was not reproducible, or was otherwise missing some information. +* `discussion`: An issue that is purely meant to hold a discussion. Typically the maintainers are looking for feedback in this issues. +* `question`: An issue that is like a support request because the user's usage was not correct. +* `semver:major|minor|patch`: Metadata about how resolving this issue would affect the version number. +* `security`: An issue that has special consideration for security reasons. +* `good first contribution`: An issue that has a well-defined relatively-small scope, with clear expectations. It helps when the testing approach is also known. +* `duplicate`: An issue that is functionally the same as another issue. Apply this only if you've linked the other issue by number. + +**Triage** is the process of taking new issues that aren't yet "seen" and marking them with a basic level of information +with labels. An issue should have **one** of the following labels applied: `bug`, `enhancement`, `question`, +`needs feedback`, `docs`, `tests`, or `discussion`. + +Issues are closed when a resolution has been reached. If for any reason a closed issue seems relevant once again, +reopening is great and better than creating a duplicate issue. + +## Everything else + +When in doubt, find the other maintainers and ask. \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/pull_request_template.md b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/pull_request_template.md new file mode 100644 index 0000000000..ce8640a6bf --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.github/pull_request_template.md @@ -0,0 +1,8 @@ +### Summary + +Describe the goal of this PR. Mention any related Issue numbers. + +### Requirements (place an `x` in each `[ ]`) + +* [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md) and have done my best effort to follow them. +* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.gitignore b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.gitignore new file mode 100644 index 0000000000..7290c13d39 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.gitignore @@ -0,0 +1,26 @@ +# general things to ignore +build/ +dist/ +docs/_sources/ +docs/.doctrees +*.egg-info/ +*.egg +*.py[cod] +__pycache__/ +*.so +*~ + +# virtualenv +env/ +venv/ + +# codecov / coverage +.coverage +cov_* + +# due to using tox and pytest +.tox +.cache +.pytest_cache/ +.python-version +pip \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.travis.yml b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.travis.yml new file mode 100644 index 0000000000..0ce6c9b039 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/.travis.yml @@ -0,0 +1,17 @@ +sudo: false +dist: trusty +language: python +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" +install: + - travis_retry pip install -r requirements.txt + - travis_retry pip install flake8 + - travis_retry pip install -r test_requirements.txt +script: + - flake8 slackclient + - py.test --cov-report= --cov=slackclient tests +after_success: + - codecov -e $TRAVIS_PYTHON_VERSION diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/LICENSE b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/LICENSE new file mode 100644 index 0000000000..73da6e9751 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Slack Technologies, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/MANIFEST.in b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/MANIFEST.in new file mode 100644 index 0000000000..1aba38f67a --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/README.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/README.rst new file mode 100644 index 0000000000..3c7c031f9a --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/README.rst @@ -0,0 +1,355 @@ +python-slackclient +=================== + +A client for Slack, which supports the Slack Web API and Real Time Messaging (RTM) API. + +|build-status| |windows-build-status| |codecov| |doc-status| |pypi-version| |python-version| + +.. |build-status| image:: https://travis-ci.org/slackapi/python-slackclient.svg?branch=master + :target: https://travis-ci.org/slackapi/python-slackclient +.. |windows-build-status| image:: https://ci.appveyor.com/api/projects/status/rif04t60ptslj32x/branch/master?svg=true + :target: https://ci.appveyor.com/project/slackapi/python-slackclient +.. |codecov| image:: https://codecov.io/gh/slackapi/python-slackclient/branch/master/graph/badge.svg + :target: https://codecov.io/gh/slackapi/python-slackclient +.. |doc-status| image:: https://readthedocs.org/projects/python-slackclient/badge/?version=latest + :target: http://python-slackclient.readthedocs.io/en/latest/?badge=latest +.. |pypi-version| image:: https://badge.fury.io/py/slackclient.svg + :target: https://pypi.python.org/pypi/slackclient +.. |python-version| image:: https://img.shields.io/pypi/pyversions/slackclient.svg + :target: https://pypi.python.org/pypi/slackclient + +Overview +-------- + +Whether you're building a custom app for your team, or integrating a third party +service into your Slack workflows, Slack Developer Kit for Python allows you to leverage the flexibility +of Python to get your project up and running as quickly as possible. + +Documentation +*************** + +For comprehensive method information and usage examples, see the `full documentation `_. + +If you're building a project to receive content and events from Slack, check out the `Python Slack Events API Adapter `_ library. + +You may also review our `Development Roadmap `_ in the project wiki. + + +Requirements and Installation +****************************** + +We recommend using `PyPI `_ to install Slack Developer Kit for Python + +.. code-block:: bash + + pip install slackclient + +Of course, if you prefer doing things the hard way, you can always implement Slack Developer Kit for Python +by pulling down the source code directly into your project: + +.. code-block:: bash + + git clone https://github.com/slackapi/python-slackclient.git + pip install -r requirements.txt + +Getting Help +************* + +If you get stuck, we’re here to help. The following are the best ways to get assistance working through your issue: + +- Use our `Github Issue Tracker `_ for reporting bugs or requesting features. +- Visit the `Bot Developer Hangout `_ for getting help using Slack Developer Kit for Python or just generally bond with your fellow Slack developers. + +Basic Usage +------------ +The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations +we provide out of the box. + +This package is a modular wrapper designed to make Slack `Web API `_ calls simpler and easier for your +app. Provided below are examples of how to interact with commonly used API endpoints, but this is by no means +a complete list. Review the full list of available methods `here `_. + + +Sending a message +******************** +The primary use of Slack is sending messages. Whether you're sending a message +to a user or to a channel, this method handles both. + +To send a message to a channel, use the channel's ID. For IMs, use the user's ID. + +.. code-block:: python + + import os + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + text="Hello from Python! :tada:" + ) + +There are some unique options specific to sending IMs, so be sure to read the **channels** +section of the `chat.postMessage `_ +page for a full list of formatting and authorship options. + +Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same +as sending a regular message, but with an additional ``user`` parameter. + +.. code-block:: python + + import os + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postEphemeral", + channel="C0XXXXXX", + text="Hello from Python! :tada:", + user="U0XXXXXXX" + ) + +See `chat.postEphemeral `_ for more info. + + +Replying to messages and creating threads +***************************************** +Threaded messages are just like regular messages, except thread replies are grouped together to provide greater context +to the user. You can reply to a thread or start a new threaded conversation by simply passing the original message's ``ts`` +ID in the ``thread_ts`` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` +ID of the message you're replying to. + +A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. +When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not +appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message. + +.. code-block:: python + + import os + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + text="Hello from Python! :tada:", + thread_ts="1476746830.000003" + ) + + +By default, ``reply_broadcast`` is set to ``False``. To indicate your reply is germane to all members of a channel, +set the ``reply_broadcast`` boolean parameter to ``True``. + +.. code-block:: python + + import os + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + text="Hello from Python! :tada:", + thread_ts="1476746830.000003", + reply_broadcast=True + ) + + +**Note:** While threaded messages may contain attachments and message buttons, when your reply is broadcast to the +channel, it'll actually be a reference to your reply, not the reply itself. +So, when appearing in the channel, it won't contain any attachments or message buttons. Also note that updates and +deletion of threaded replies works the same as regular messages. + +See the `Threading messages together `_ +article for more information. + + +Deleting a message +******************** +Sometimes you need to delete things. + +.. code-block:: python + + import os + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.delete", + channel="C0XXXXXX", + ts="1476745373.000002" + ) + +See `chat.delete `_ for more info. + +Adding or removing an emoji reaction +**************************************** +You can quickly respond to any message on Slack with an emoji reaction. Reactions +can be used for any purpose: voting, checking off to-do items, showing excitement — and just for fun. + +This method adds a reaction (emoji) to an item (``file``, ``file comment``, ``channel message``, ``group message``, or ``direct message``). One of file, file_comment, or the combination of channel and timestamp must be specified. + +.. code-block:: python + + import os + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "reactions.add", + channel="C0XXXXXXX", + name="thumbsup", + timestamp="1234567890.123456" + ) + +Removing an emoji reaction is basically the same format, but you'll use ``reactions.remove`` instead of ``reactions.add`` + +.. code-block:: python + + sc.api_call( + "reactions.remove", + channel="C0XXXXXXX", + name="thumbsup", + timestamp="1234567890.123456" + ) + + +See `reactions.add `_ and `reactions.remove `_ for more info. + +Getting a list of channels +****************************** +At some point, you'll want to find out what channels are available to your app. This is how you get that list. + +**Note:** This call requires the ``channels:read`` scope. + +.. code-block:: python + + sc.api_call("channels.list") + +Archived channels are included by default. You can exclude them by passing ``exclude_archived=1`` to your request. + +.. code-block:: python + + sc.api_call( + "channels.list", + exclude_archived=1 + ) + +See `channels.list `_ for more info. + +Getting a channel's info +************************* +Once you have the ID for a specific channel, you can fetch information about that channel. + +.. code-block:: python + + sc.api_call( + "channels.info", + channel="C0XXXXXXX" + ) + +See `channels.info `_ for more info. + +Joining a channel +******************** +Channels are the social hub of most Slack teams. Here's how you hop into one: + +.. code-block:: python + + sc.api_call( + "channels.join", + channel="C0XXXXXXY" + ) + +If you are already in the channel, the response is slightly different. +``already_in_channel`` will be true, and a limited ``channel`` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. + +See `channels.join `_ for more info. + +Leaving a channel +******************** +Maybe you've finished up all the business you had in a channel, or maybe you +joined one by accident. This is how you leave a channel. + +.. code-block:: python + + sc.api_call( + "channels.leave", + channel="C0XXXXXXX" + ) + +See `channels.leave `_ for more info. + + +Tokens and Authentication +************************** + +The simplest way to create an instance of the client, as shown in the samples above, is to use a bot (xoxb) access token: + +.. code-block:: python + + # Get the access token from environmental variable + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + +The SlackClient library allows you to use a variety of Slack authentication tokens. + +To take advantage of automatic token refresh, you'll need to instantiate the client a little differently than when using +a bot access token. With a bot token, you have the access (xoxb) token when you create the client, when using refresh tokens, +you won't know the access token when the client is created. + +Upon the first request, the SlackClient will request a new access (xoxa) token on behalf of your application, using your app's +refresh token, client ID, and client secret. + +.. code-block:: python + + # Get the access token from environmental variable + slack_refresh_token = os.environ["SLACK_REFRESH_TOKEN"] + slack_client_id = os.environ["SLACK_CLIENT_ID"] + slack_client_secret = os.environ["SLACK_CLIENT_SECRET"] + + +Since your app's access tokens will be expiring and refreshed, the client requires a callback method to be passed in on creation of the client. +Once Slack returns an access token for your app, the SlackClient will call your provided callback to update the access token in your datastore. + +.. code-block:: python + + # This is where you'll add your data store update logic + def token_update_callback(update_data): + print("Enterprise ID: {}".format(update_data["enterprise_id"])) + print("Workspace ID: {}".format(update_data["team_id"])) + print("Access Token: {}".format(update_data["access_token"])) + print("Access Token expires in (ms): {}".format(update_data["expires_in"])) + + # When creating an instance of the client, pass the client details and token update callback + sc = SlackClient( + refresh_token=slack_refresh_token, + client_id=slack_client_id, + client_secret=slack_client_secret, + token_update_callback=token_update_callback + ) + + +Slack will send your callback function the **app's access token**, **token expiration TTL**, **team ID**, and **enterprise ID** (for enterprise workspaces) + + +See `Tokens & Authentication `_ for API token handling best practices. + + + +Additional Information +******************************************************************************************** +For comprehensive method information and usage examples, see the `full documentation`_. diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/.gitignore b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/.gitignore new file mode 100644 index 0000000000..e35d8850c9 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/.gitignore @@ -0,0 +1 @@ +_build diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/Makefile b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/Makefile new file mode 100644 index 0000000000..ecbc9e80a7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/Makefile @@ -0,0 +1,225 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = ../docs/ + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-slackclient.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-slackclient.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/python-slackclient" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-slackclient" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/conf.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/conf.py new file mode 100644 index 0000000000..bc81579bbe --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/conf.py @@ -0,0 +1,342 @@ +# -*- coding: utf-8 -*- +# +# python-slackclient documentation build configuration file, created by +# sphinx-quickstart on Mon Jun 27 17:36:09 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['../../_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Slack Developer Kit for Python' +copyright = u'2015–2016 Slack Technologies, Inc. and contributors' +author = u'Slack Technologies, Inc. and contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'1.0' +# The full version, including alpha/beta/rc tags. +release = u'1.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'emacs' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "slack" +html_theme_path = ["../../_themes", ] + +highlight_language = "python" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'python-slackclient v1.0.1' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['static'] + +html_context = { + 'css_files': ['static/pygments.css'], +} + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-slackclientdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'python-slackclient.tex', u'python-slackclient Documentation', + u'Ryan Huber, Jeff Ammons', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'python-slackclient', u'python-slackclient Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'python-slackclient', u'python-slackclient Documentation', + author, 'python-slackclient', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/layout.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/layout.html new file mode 100644 index 0000000000..e0e77d1eb5 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/layout.html @@ -0,0 +1,104 @@ + + + + + + {{ metatags }} + + {%- block htmltitle %} + {{ title|striptags|e + " — "|safe + project|e }} + {%- endblock %} + + {%- macro css() %} + + + + + + {%- endmacro %} + + + + + {{ css() }} + {%- block linktags %} + + + {%- endblock %} + + + + + + + + + + + {%- block header %} + + {% endblock %} + +
+
+ + + + + +
+
{{ title }}
+
+ {%- block body %} + {{ body }} + {% endblock %} +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/localtoc.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/localtoc.html new file mode 100644 index 0000000000..e0ffc3f4f7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/localtoc.html @@ -0,0 +1,4 @@ +
Table of Contents?
+
    + {{ toc }} +
\ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/relations.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/relations.html new file mode 100644 index 0000000000..e3faec04a7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/relations.html @@ -0,0 +1,19 @@ +{# + basic/relations.html + ~~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: relation links. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- if prev %} +

{{ _('Previous topic') }}

+

{{ prev.title }}

+{%- endif %} +{%- if next %} +

{{ _('Next topic') }}

+

{{ next.title }}

+{%- endif %} diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/sidebar.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/sidebar.html new file mode 100644 index 0000000000..d92ca908f7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/sidebar.html @@ -0,0 +1,15 @@ +{{ toctree(maxdepth=-1, collapse=False,includehidden=True) }} + + diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/default.css_t b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/default.css_t new file mode 100644 index 0000000000..93fc2c66b5 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/default.css_t @@ -0,0 +1,74 @@ +a.headerlink { + display: none !important; +} + +.section-title { + font-size: 2rem; + line-height: 2.5rem; + letter-spacing: -1px; + font-weight: 700; + margin: 0 0 1rem; +} + +nav#api_nav .toctree-l1 { + margin-bottom: 1.5rem; +} + +nav#api_nav #api_sections ul { + list-style: none; + margin: 0; + padding: 0; +} + +nav#api_nav #api_sections ul li.toctree-l1>a { + color: #1264a3; + letter-spacing: 0; + font-size: .8rem; + font-weight: 800; + text-transform: uppercase; + border: none; + padding: 0; +} + +nav#api_nav #api_sections ul li.toctree-l2 { + margin: 0; + padding: 0; +} + +nav#api_nav #api_sections ul li.toctree-l2 a { + color: #1d1c1d; + text-transform: none; + font-weight: inherit; + padding: 0; + display: block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-size: 15px!important; + line-height:15px; + padding: 4px 8px; + border: 1px solid transparent; + border-radius: 4px; +} + +nav#api_nav #api_sections ul li.toctree-l2 a:hover { + cursor: pointer; + text-decoration: none; + background-color:#e8f5fa; + border-color:#dcf0fb; +} + +nav#api_nav #footer #footer_nav { + font-size: .9375rem; +} + +nav#api_nav #footer #footer_nav a { + border: none; + padding: 0; + color: #616061; +} + +nav#api_nav #footer #footer_nav a:hover { + text-decoration: none; + color: #1c1c1c; +} diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/docs.css_t b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/docs.css_t new file mode 100644 index 0000000000..7f360ac666 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/docs.css_t @@ -0,0 +1,34 @@ +/* Updates body font */ +body { + font-family: Slack-Lato,appleLogo,sans-serif; +} + +/* Replaces old sidebar styled links */ +.sidebar_menu h5 { + font-size: 0.8rem; + font-weight: 800; + margin-bottom: 3px; +} + +/* Aligns footer navigation to the left of the sidebar */ +.footer_nav { + margin: 0 !important; +} + +/* Styles the signature all nice and pretty <3 */ +#footer_signature { + color:#e01e5a; + font-size:.9rem; + margin-top: 10px; +} + +/* Fixes link hover state */ +a:hover { + text-decoration: underline; +} + +/* Makes footer consistent */ +footer { + background-color: transparent; + border: 0; +} \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/pygments.css_t b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/pygments.css_t new file mode 100644 index 0000000000..a94b170ffb --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/static/pygments.css_t @@ -0,0 +1,60 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { font-weight: bold } /* Keyword */ +.highlight .o { font-weight: bold } /* Operator */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { font-weight: bold } /* Keyword.Constant */ +.highlight .kd { font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #bb8844 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #999999 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sb { color: #bb8844 } /* Literal.String.Backtick */ +.highlight .sc { color: #bb8844 } /* Literal.String.Char */ +.highlight .sd { color: #bb8844 } /* Literal.String.Doc */ +.highlight .s2 { color: #bb8844 } /* Literal.String.Double */ +.highlight .se { color: #bb8844 } /* Literal.String.Escape */ +.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ +.highlight .si { color: #bb8844 } /* Literal.String.Interpol */ +.highlight .sx { color: #bb8844 } /* Literal.String.Other */ +.highlight .sr { color: #808000 } /* Literal.String.Regex */ +.highlight .s1 { color: #bb8844 } /* Literal.String.Single */ +.highlight .ss { color: #bb8844 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/theme.conf b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/theme.conf new file mode 100644 index 0000000000..b13de3cbb4 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/_themes/slack/theme.conf @@ -0,0 +1,6 @@ +[theme] +inherit = default +stylesheet = default.css +show_sphinx = False + +[options] diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/about.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/about.rst new file mode 100644 index 0000000000..17fad157f5 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/about.rst @@ -0,0 +1,18 @@ +============================================== +About +============================================== + +|product_name| +************** + + +Access the Slack Platform from your Python app. |product_name| lets you build on the Slack Web APIs pythonically. + +|product_name| is proudly maintained with 💖 by the Slack Developer Tools team + +- `License`_ +- `Code of Conduct`_ +- `Contributing`_ +- `Contributor License Agreement`_ + +.. include:: metadata.rst \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/auth.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/auth.rst new file mode 100644 index 0000000000..9a587c3f21 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/auth.rst @@ -0,0 +1,150 @@ +============================================== +Tokens & Authentication +============================================== +.. _handling-tokens: + +Handling tokens and other sensitive data +---------------------------------------- +âš ï¸ **Slack tokens are the keys to your—or your customers’—data.Keep them secret. Keep them safe.** + +One way to do that is to never explicitly hardcode them. + +Try to avoid this when possible: + +.. code-block:: python + + token = 'xoxb-abc-1232' + +âš ï¸ **Never share test tokens with other users or applications. Do not publish test tokens in public code repositories.** + +We recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as: + +.. code-block:: python + + SLACK_BOT_TOKEN="xoxb-abc-1232" python myapp.py + +Then retrieve the key with: + +.. code-block:: python + + import os + SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] + +You can use the same technique for other kinds of sensitive data that ne’er-do-wells could use in nefarious ways, including + +- Incoming webhook URLs +- Slash command verification tokens +- App client ids and client secrets + +For additional information, please see our `Safely Storing Credentials `_ page. + +Single-Workspace Apps +----------------------- +If you're building an application for a single Slack workspace, there's no need to build out the entire OAuth flow. + +Once you've setup your features, click on the **Install App to Team** button found on the **Install App** page. +If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to +your workspace for changes to take effect. + +For additional information, see the `Installing Apps `_ of our `Building Slack apps `_ page. + +The OAuth flow +------------------------- +Authentication for Slack's APIs is done using OAuth, so you'll want to read up on `OAuth `_. + +In order to implement OAuth in your app, you will need to include a web server. In this example, we'll use `Flask `_. + +As mentioned above, we're setting the app tokens and other configs in environment variables and pulling them into global variables. + +Depending on what actions your app will need to perform, you'll need different OAuth permission scopes. Review the available scopes `here `_. + +.. code-block:: python + + import os + from flask import Flask, request + from slackclient import SlackClient + + client_id = os.environ["SLACK_CLIENT_ID"] + client_secret = os.environ["SLACK_CLIENT_SECRET"] + oauth_scope = os.environ["SLACK_BOT_SCOPE"] + + app = Flask(__name__) + +**The OAuth initiation link:** + +To begin the OAuth flow, you'll need to provide the user with a link to Slack's OAuth page. +This directs the user to Slack's OAuth acceptance page, where the user will review and accept +or refuse the permissions your app is requesting as defined by the requested scope(s). + +For the best user experience, use the `Add to Slack button `_ to direct users to approve your application for access or `Sign in with Slack `_ to log users in. + +.. code-block:: python + + @app.route("/begin_auth", methods=["GET"]) + def pre_install(): + return ''' + + Add to Slack + + '''.format(oauth_scope, client_id) + +**The OAuth completion page** + +Once the user has agreed to the permissions you've requested on Slack's OAuth +page, Slack will redirect the user to your auth completion page. Included in this +redirect is a ``code`` query string param which you’ll use to request access +tokens from the ``oauth.access`` endpoint. + +Generally, Web API requests require a valid OAuth token, but there are a few endpoints +which do not require a token. ``oauth.access`` is one example of this. Since this +is the endpoint you'll use to retrieve the tokens for later API requests, +an empty string ``""`` is acceptable for this request. + +.. code-block:: python + + @app.route("/finish_auth", methods=["GET", "POST"]) + def post_install(): + + # Retrieve the auth code from the request params + auth_code = request.args['code'] + + # An empty string is a valid token for this request + sc = SlackClient("") + + # Request the auth tokens from Slack + auth_response = sc.api_call( + "oauth.access", + client_id=client_id, + client_secret=client_secret, + code=auth_code + ) + +A successful request to ``oauth.access`` will yield two tokens: A ``user`` +token and a ``bot`` token. The ``user`` token ``auth_response['access_token']`` +is used to make requests on behalf of the authorizing user and the ``bot`` +token ``auth_response['bot']['bot_access_token']`` is for making requests +on behalf of your app's bot user. + +If your Slack app includes a bot user, upon approval the JSON response will contain +an additional node containing an access token to be specifically used for your bot +user, within the context of the approving team. + +When you use Web API methods on behalf of your bot user, you should use this bot +user access token instead of the top-level access token granted to your application. + +.. code-block:: python + + # Save the bot token to an environmental variable or to your data store + # for later use + os.environ["SLACK_USER_TOKEN"] = auth_response['access_token'] + os.environ["SLACK_BOT_TOKEN"] = auth_response['bot']['bot_access_token'] + + # Don't forget to let the user know that auth has succeeded! + return "Auth complete!" + +Once your user has completed the OAuth flow, you'll be able to use the provided +tokens to make a variety of Web API calls on behalf of the user and your app's bot user. + +See the :ref:`Web API usage ` section of this documentation for usage examples. + +.. include:: metadata.rst diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/basic_usage.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/basic_usage.rst new file mode 100644 index 0000000000..e61365c8a4 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/basic_usage.rst @@ -0,0 +1,435 @@ +.. _web-api-examples: + +============================================== +Basic Usage +============================================== +The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations +we provide out of the box. + +This package is a modular wrapper designed to make Slack `Web API`_ calls simpler and easier for your +app. Provided below are examples of how to interact with commonly used API endpoints, but this is by no means +a complete list. Review the full list of available methods `here `_. + +See :ref:`Tokens & Authentication ` for API token handling best practices. + +-------- + +Sending a message +----------------------- +The primary use of Slack is sending messages. Whether you're sending a message +to a user or to a channel, this method handles both. + +To send a message to a channel, use the channel's ID. For IMs, use the user's ID. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + text="Hello from Python! :tada:" + ) + +There are some unique options specific to sending IMs, so be sure to read the **channels** +section of the `chat.postMessage `_ +page for a full list of formatting and authorship options. + +Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same +as sending a regular message, but with an additional ``user`` parameter. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postEphemeral", + channel="C0XXXXXX", + text="Hello from Python! :tada:", + user="U0XXXXXXX" + ) + +See `chat.postEphemeral `_ for more info. + +-------- + +Customizing a message's layout +----------------------- +The chat.postMessage method takes an optional blocks argument that allows you to customize the layout of a message. +Blocks for Web API methods are all specified in a single object literal, so just add additional keys for any optional argument. + +To send a message to a channel, use the channel's ID. For IMs, use the user's ID. + +.. code-block:: python + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Danny Torrence left the following review for your property:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": " \n :star: \n Doors had too many axe holes, guest in room " + + "237 was far too rowdy, whole place felt stuck in the 1920s." + }, + "accessory": { + "type": "image", + "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg", + "alt_text": "Haunted hotel image" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Average Rating*\n1.0" + } + ] + } + ] + ) + +**Note:** You can use the `Block Kit Builder `for a playground where you can prototype your message's look and feel. + +-------- + +Replying to messages and creating threads +------------------------------------------ +Threaded messages are just like regular messages, except thread replies are grouped together to provide greater context +to the user. You can reply to a thread or start a new threaded conversation by simply passing the original message's ``ts`` +ID in the ``thread_ts`` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` +ID of the message you're replying to. + +A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. +When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not +appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + text="Hello from Python! :tada:", + thread_ts="1476746830.000003" + ) + + +By default, ``reply_broadcast`` is set to ``False``. To indicate your reply is germane to all members of a channel, +set the ``reply_broadcast`` boolean parameter to ``True``. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.postMessage", + channel="C0XXXXXX", + text="Hello from Python! :tada:", + thread_ts="1476746830.000003", + reply_broadcast=True + ) + + +**Note:** While threaded messages may contain attachments and message buttons, when your reply is broadcast to the +channel, it'll actually be a reference to your reply, not the reply itself. +So, when appearing in the channel, it won't contain any attachments or message buttons. Also note that updates and +deletion of threaded replies works the same as regular messages. + +See the `Threading messages together `_ +article for more information. + + +-------- + +Updating the content of a message +---------------------------------- +Let's say you have a bot which posts the status of a request. When that request +is updated, you'll want to update the message to reflect it's state. Or your user +might want to fix a typo or change some wording. This is how you'll make those changes. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.update", + ts="1476746830.000003", + channel="C0XXXXXX", + text="Hello from Python! :tada:" + ) + +See `chat.update `_ for formatting options +and some special considerations when calling this with a bot user. + +-------- + +Deleting a message +------------------- +Sometimes you need to delete things. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "chat.delete", + channel="C0XXXXXX", + ts="1476745373.000002" + ) + +See `chat.delete `_ for more info. + +-------- + +Adding or removing an emoji reaction +--------------------------------------- +You can quickly respond to any message on Slack with an emoji reaction. Reactions +can be used for any purpose: voting, checking off to-do items, showing excitement — and just for fun. + +This method adds a reaction (emoji) to an item (``file``, ``file comment``, ``channel message``, ``group message``, or ``direct message``). One of file, file_comment, or the combination of channel and timestamp must be specified. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "reactions.add", + channel="C0XXXXXXX", + name="thumbsup", + timestamp="1234567890.123456" + ) + +Removing an emoji reaction is basically the same format, but you'll use ``reactions.remove`` instead of ``reactions.add`` + +.. code-block:: python + + sc.api_call( + "reactions.remove", + channel="C0XXXXXXX", + name="thumbsup", + timestamp="1234567890.123456" + ) + + +See `reactions.add `_ and `reactions.remove `_ for more info. + +-------- + +Getting a list of channels +--------------------------- +At some point, you'll want to find out what channels are available to your app. This is how you get that list. + +**Note:** This call requires the ``channels:read`` scope. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call("channels.list") + +Archived channels are included by default. You can exclude them by passing ``exclude_archived=1`` to your request. + + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "channels.list", + exclude_archived=1 + ) + +See `channels.list `_ for more info. + +-------- + +Getting a channel's info +------------------------- +Once you have the ID for a specific channel, you can fetch information about that channel. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "channels.info", + channel="C0XXXXXXX" + ) + +See `channels.info `_ for more info. + +-------- + +Joining a channel +------------------ +Channels are the social hub of most Slack teams. Here's how you hop into one: + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "channels.join", + channel="C0XXXXXXY" + ) + +If you are already in the channel, the response is slightly different. +``already_in_channel`` will be true, and a limited ``channel`` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. + +See `channels.join `_ for more info. + +-------- + +Leaving a channel +------------------ +Maybe you've finished up all the business you had in a channel, or maybe you +joined one by accident. This is how you leave a channel. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "channels.leave", + channel="C0XXXXXXX" + ) + +See `channels.leave `_ for more info. + +-------- + +Get a list of team members +------------------------------ + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call("users.list") + +See `users.list `_ for more info. + + +-------- + +Uploading files +------------------------------ + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + with open('thinking_very_much.png') as file_content: + sc.api_call( + "files.upload", + channels="C3UKJTQAC", + file=file_content, + title="Test upload" + ) + +See `files.upload `_ for more info. + + +-------- + +Web API Rate Limits +-------------------- +Slack allows applications to send no more than one message per second. We allow bursts over that +limit for short periods. However, if your app continues to exceed the limit over a longer period +of time it will be rate limited. + +Here's a very basic example of how one might deal with rate limited requests. + +If you go over these limits, Slack will start returning a HTTP 429 Too Many Requests error, +a JSON object containing the number of calls you have been making, and a Retry-After header +containing the number of seconds until you can retry. + + +.. code-block:: python + + from slackclient import SlackClient + import time + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + # Simple wrapper for sending a Slack message + def send_slack_message(channel, message): + return sc.api_call( + "chat.postMessage", + channel=channel, + text=message + ) + + # Make the API call and save results to `response` + response = send_slack_message("C0XXXXXX", "Hello, from Python!") + + # Check to see if the message sent successfully. + # If the message succeeded, `response["ok"]`` will be `True` + if response["ok"]: + print("Message posted successfully: " + response["message"]["ts"]) + # If the message failed, check for rate limit headers in the response + elif response["ok"] is False and response["headers"]["Retry-After"]: + # The `Retry-After` header will tell you how long to wait before retrying + delay = int(response["headers"]["Retry-After"]) + print("Rate limited. Retrying in " + str(delay) + " seconds") + time.sleep(delay) + send_slack_message(message, channel) + +See the documentation on `Rate Limiting `_ for more info. + +.. include:: metadata.rst + diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/changelog.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/changelog.rst new file mode 100644 index 0000000000..74300a43a0 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/changelog.rst @@ -0,0 +1,156 @@ +============================================== +Changelog +============================================== + +v1.3.2 (2019-05-30) +------------------- +- Fixing an issue where reconnects used rtm.start istead of rtm.connect. #422 + + +v1.3.1 (2019-02-28) +------------------- + +- Lock websocket-client version to < 0.55.0: temp fix for #385 + + +v1.3.0 (2018-09-11) +------------------- + +## New Features +- Adds support for short lived tokens and automatic token refresh #347 (Thanks @roach!) + +## Other +- update RTM rate limiting comment and error message #308 (Thanks @benoitlavigne!) +- Use logging instead of traceback #309 (Thanks @harlowja!) +- Remove Python 3.3 from test environments #346 (Thanks @roach!) +- Enforced linting when using VSCode. #347 (Thanks @roach!) + + +v1.2.1 (2018-03-26) +------------------- + +- Added rate limit handling for rtm connections (thanks @jayalane!) + + +v1.2.0 (2018-03-20) +------------------- + +- You can now tell the RTM client to automatically reconnect by passing `auto_reconnect=True` + +v1.1.3 (2018-03-01) +------------------- + +- Fixed another API param encoding bug. It encodes things properly now. + +v1.1.2 (2018-01-31) +------------------- + +- Fixed an encoding issue which was encoding some Web API params incorrectly (sorry) + +v1.1.1 (2018-01-30) +------------------- + + - Adds HTTP response headers to `api_call` responses to expose things like rate limit info + - Moves `token` into auth header rather than request params + +v1.1.0 (2017-11-21) +------------------- + + - Aadds new SlackClientError and ResponseParseError types to describe errors - thanks @aoberoi! + - Fix Build Error (#245) - thanks @stasfilin! + - include email as user property (#173) - thanks @acaire! + - Add http reply into slack login and slack connection error (#216) - thanks @harlowja! + - Removed unused exception class (#233) + - Fix rtm_send_message bug (#225) - thanks @kt5356! + - Allow use of custom parameters on rtm_connect() (#210) - thanks @kamushadenes! + - Fix link to rtm.connect docs (#223) - @sampart! + +v1.0.9 (2017-08-31) +------------------- + + - Fixed rtm_send_message ID bug introduced in 1.0.8 + +v1.0.8 (2017-08-31) +------------------- + + - Added rtm.connect support + +v1.0.7 (2017-08-02) +------------------- + + - Fixes an issue where connecting over RTM to large teams may result in “Websocket URL expired†errors + - A load of packaging improvements + +v1.0.6 (2017-06-12) +------------------- + + - Added proxy support (thanks @timfeirg!) + - Tidied up docs (thanks @schlueter!) + - Added tox settings for Python 3 testing (thanks @cclauss!) + +v1.0.5 (2017-01-23) +------------------- + + - Allow RTM Channel.send_message to reply to a thread + - Index users by ID instead of Name (non-breaking change) + - Added timeout to api calls. + - Fixed a typo about token access in auth.rst, thanks @kelvintaywl! + - Added Message Threads to the docs + +v1.0.4 (2016-12-15) +------------------- + + - fixed the ability to search for a user by ID + +v1.0.3 (2016-12-13) +------------------- + + - fixed an issue causing RTM connections to fail for large teams + +v1.0.2 (2016-09-22) +------------------- + + - removed unused ping counter + - fixed contributor guidelines links + - updated documentation + - Fix bug preventing API calls requiring a file ID + - Removes files from api_calls before JSON encoding, so the request is properly formatted + +v1.0.1 (2016-03-25) +------------------- + + - fix for __eq__ comparison in channels using '#' in channel name + - added copyright info to the LICENSE file + +v1.0.0 (2016-02-28) +------------------- + + - the ``api_call`` function now returns a decoded JSON object, rather than a JSON encoded string + - some ``api_call`` calls now call actions on the parent server object: + - ``im.open`` + - ``mpim.open``, ``groups.create``, ``groups.createChild`` + - ``channels.create``, `channels.join`` + +v0.18.0 (2016-02-21) +-------------------- + + - Moves to use semver for versioning + - Adds support for private groups and MPDMs + - Switches to use requests instead of urllib + - Gets Travis CI integration working + - Fixes some formatting issues so the code will work for python 2.6 + - Cleans up some unused imports, some PEP-8 fixes and a couple bad default args fixes + +v0.17.0 (2016-02-15) +-------------------- + + - Fixes the server so that it doesn't add duplicate users or channels to its internal lists, https://github.com/slackapi/python-slackclient/commit/0cb4bcd6e887b428e27e8059b6278b86ee661aaa + - README updates: + - Updates the URLs pointing to Slack docs for configuring authentication, https://github.com/slackapi/python-slackclient/commit/7d01515cebc80918a29100b0e4793790eb83e7b9 + - s/channnels/channels, https://github.com/slackapi/python-slackclient/commit/d45285d2f1025899dcd65e259624ee73771f94bb + - Adds users to the local cache when they join the team, https://github.com/slackapi/python-slackclient/commit/f7bb8889580cc34471ba1ddc05afc34d1a5efa23 + - Fixes urllib py 2/3 compatibility, https://github.com/slackapi/python-slackclient/commit/1046cc2375a85a22e94573e2aad954ba7287c886 + + + +.. include:: metadata.rst diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conf.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conf.py new file mode 100644 index 0000000000..37a4011949 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conf.py @@ -0,0 +1,338 @@ +# -*- coding: utf-8 -*- +# +# python-slackclient documentation build configuration file, created by +# sphinx-quickstart on Mon Jun 27 17:36:09 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'python-slackclient' +copyright = u'2016, Ryan Huber, Jeff Ammons' +author = u'Ryan Huber, Jeff Ammons' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'1.0' +# The full version, including alpha/beta/rc tags. +release = u'1.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +import sphinx_rtd_theme # noqa +html_theme = "sphinx_rtd_theme" + +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'python-slackclient v1.0.1' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-slackclientdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'python-slackclient.tex', u'python-slackclient Documentation', + u'Ryan Huber, Jeff Ammons', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'python-slackclient', u'python-slackclient Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'python-slackclient', u'python-slackclient Documentation', + author, 'python-slackclient', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conversations.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conversations.rst new file mode 100644 index 0000000000..77b0a25bb1 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/conversations.rst @@ -0,0 +1,166 @@ +.. _conversations_api: + +============================================== +Conversations API +============================================== +The Slack Conversations API provides your app with a unified interface to work with all the +channel-like things encountered in Slack; public channels, private channels, direct messages, group +direct messages, and our newest channel type, Shared Channels. + + +See `Conversations API `_ docs for more info. + +-------- + +Creating a direct message or multi-person direct message +--------------------------------------------------------- +This Conversations API method opens a multi-person direct message or just a 1:1 direct message. + +*Use conversations.create for public or private channels.* + +Provide 1 to 8 user IDs in the ``user`` parameter to open or resume a conversation. Providing only +1 ID will create a direct message. Providing more will create an ``mpim``. + +If there are no conversations already in progress including that exact set of members, a new +multi-person direct message conversation begins. + +Subsequent calls to ``conversations.open`` with the same set of users will return the already +existing conversation. + + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "conversations.open", + users=["W1234567890","U2345678901","U3456789012"] + ) + +See `conversations.open `_ additional info. + +-------- + +Creating a public or private channel +------------------------------------- +Initiates a public or private channel-based conversation + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "conversations.create", + name="myprivatechannel", + is_private=True + ) + +See `conversations.create `_ additional info. + +-------- + +Getting information about a conversation +----------------------------------------- +This Conversations API method returns information about a workspace conversation. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "conversations.info", + channel="C0XXXXXX", + ) + +See `conversations.info `_ for more info. + + +-------- + +Getting a list of conversations +-------------------------------- +This Conversations API method returns a list of all channel-like conversations in a workspace. +The "channels" returned depend on what the calling token has access to and the directives placed +in the ``types`` parameter. + + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call("conversations.list") + +Only public conversations are included by default. You may include additional conversations types +by passing ``types`` (as a string) into your list request. Additional conversation types include +``public_channel`` and ``private_channel``. + + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + # Note that `types` is a string + sc.api_call( + "conversations.list", + types="public_channel, private_channel" + ) + +See `conversations.list `_ for more info. + + +-------- + +Leaving a conversation +----------------------- +Maybe you've finished up all the business you had in a conversation, or maybe you +joined one by accident. This is how you leave a conversation. + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call( + "conversations.leave", + channel="C0XXXXXXX" + ) + +See `conversations.leave `_ for more info. + +-------- + +Get conversation members +------------------------------ +Get a list fo the members of a conversation + +.. code-block:: python + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.api_call("conversations.members", + channel="C0XXXXXXX" + ) + +See `users.list `_ for more info. + +.. include:: metadata.rst diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/faq.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/faq.rst new file mode 100644 index 0000000000..79621351c7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/faq.rst @@ -0,0 +1,63 @@ +============================================== +Frequently Asked Questions +============================================== + +What even is |product_name| and why should I care? +************************************************** + +|product_name| is a wrapper around commonly accessed parts of the Slack Platform. It provides basic mechanisms for +using the Slack Web API from within your Python app. + +On the other hand, |product_name| does not provide access to the Events bot-building API, but +[this adapter](https://github.com/slackapi/python-slack-events-api) does. + +OMG I found a bug! +****************** + +Well, poop. Take a deep breath, and then let us know on the `Issue Tracker`_. If you're feeling particularly ambitious, +why not submit a `pull request`_ with a bug fix? + +Hey, there's a feature missing! +******************************* + +There's always something more that could be added! You can let us know in the `Issue Tracker`_ to start a discussion +around the proposed feature, that's a good start. If you're feeling particularly ambitious, why not write the feature +yourself, and submit a `pull request`_! We love feedback and we love help and we don't bite. Much. + +I'd like to contribute...but how? +********************************* + +What an excellent question. First of all, please have a look at our general `contributing guidelines`_. We'll wait for +you here. + +All done? Great! While we're super excited to incorporate your new feature into |product_name|, there are a +couple of things we want to make sure you've given thought to. + +- Please write unit tests for your new code. But don't **just** aim to increase the test coverage, rather, we expect you + to have written **thoughtful** tests that ensure your new feature will continue to work as expected, and to help future + contributors to ensure they don't break it! + +- Please document your new feature. Think about **concrete use cases** for your feature, and add a section to the + appropriate document, including a **complete** sample program that demonstrates your feature. Don't forget to update + the changelog in ``changelog.rst``! + +Including these two items with your pull request will totally make our day—and, more importantly, your future users' days! + +On that note... + +How do I compile the documentation? +*********************************** + +This project's documentation is generated with `Sphinx `_. If you are editing one of the many +reStructuredText files in the ``docs-src`` folder, you'll need to rebuild the documentation. It is recommended to run +the following steps inside a ``virtualenv`` environment. + +.. code-block:: bash + + tox -e docs + + +Do be sure to add the ``docs`` folder and its contents to your pull request! + + +.. include:: metadata.rst diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/index.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/index.rst new file mode 100644 index 0000000000..30c9969730 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/index.rst @@ -0,0 +1,46 @@ +.. toctree:: + :hidden: + + self + auth + basic_usage + conversations + real_time_messaging + faq + changelog + about + +============================================== +|product_name| +============================================== + +Whether you're building a custom app for your team, or integrating a third party +service into your Slack workflows, |product_name| allows you to leverage the flexibility +of Python to get your project up and running as quickly as possible. + +Requirements and Installation +****************************** + +We recommend using `PyPI `_ to install |product_name| + +.. code-block:: bash + + pip install slackclient + +Of course, if you prefer doing things the hard way, you can always implement |product_name| +by pulling down the source code directly into your project: + +.. code-block:: bash + + git clone https://github.com/slackapi/python-slackclient.git + pip install -r requirements.txt + +Getting Help +************ + +If you get stuck, we’re here to help. The following are the best ways to get assistance working through your issue: + +- Use our `Github Issue Tracker `_ for reporting bugs or requesting features. +- Visit the `Bot Developer Hangout `_ for getting help using |product_name| or just generally bond with your fellow Slack developers. + +.. include:: metadata.rst diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/make.bat b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/make.bat new file mode 100644 index 0000000000..5a08728349 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-slackclient.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-slackclient.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/metadata.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/metadata.rst new file mode 100644 index 0000000000..75d613470c --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/metadata.rst @@ -0,0 +1,19 @@ +.. Site settings +.. |product_name| replace:: Slack Developer Kit for Python +.. |email| replace:: opensource@slack.com +.. |repo_name| replace:: python-slackclient +.. |github_username| replace:: SlackAPI +.. |twitter_username| replace:: SlackAPI + +.. _Bot Developer Hangout: https://dev4slack.slack.com/archives/sdk-python-slackclient +.. _Issue Tracker: http://github.com/SlackAPI/python-slackclient/issues +.. _pull request: http://github.com/SlackAPI/python-slackclient/pulls +.. _Python RTMBot: https://slackapi.github.io/python-rtmbot +.. _License: https://github.com/SlackAPI/python-slackclient/blob/master/LICENSE +.. _Code of Conduct: https://slackhq.github.io/code-of-conduct +.. _Contributing: https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md +.. _contributing guidelines: https://github.com/slackapi/python-slackclient/blob/master/.github/contributing.md +.. _Contributor License Agreement: https://docs.google.com/a/slack-corp.com/forms/d/e/1FAIpQLSfzjVoCM7ohBnjWf7eDYQxzti1EPpinsIJQA5RAUBwJKRUQHg/viewform +.. _Real Time Messaging (RTM) API: https://api.slack.com/rtm +.. _Web API: https://api.slack.com/web + diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/real_time_messaging.rst b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/real_time_messaging.rst new file mode 100644 index 0000000000..e651ea6875 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs-src/real_time_messaging.rst @@ -0,0 +1,130 @@ +.. _real-time-messaging: + +============================================== +Real Time Messaging (RTM) +============================================== +The `Real Time Messaging (RTM) API`_ is a WebSocket-based API that allows you to +receive events from Slack in real time and send messages as users. + +If you prefer events to be pushed to you instead, we recommend using the +HTTP-based `Events API `_ instead. +Most event types supported by the RTM API are also available +in `the Events API `_. + +See :ref:`Tokens & Authentication ` for API token handling best practices. + +Connecting to the RTM API +------------------------------------------ +:: + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + if sc.rtm_connect(): + while sc.server.connected is True: + print sc.rtm_read() + time.sleep(1) + else: + print "Connection Failed" + +If you connect successfully the first event received will be a hello: +:: + + { + u'type': u'hello' + } + +If there was a problem connecting an error will be returned, including a descriptive error message: +:: + + { + u'type': u'error', + u'error': { + u'code': 1, + u'msg': u'Socket URL has expired' + } + } + +rtm.start vs rtm.connect +--------------------------- + +If you expect your app to be used on large teams, we recommend starting the RTM client with `rtm.connect` rather than the default connection method for this client, `rtm.start`. +`rtm.connect` provides a lighter initial connection payload, without the team's channel and user information included. You'll need to request channel and user info via +the Web API separately. + +To do this, simply pass `with_team_state=False` into the `rtm_connect` call, like so: +:: + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + if sc.rtm_connect(with_team_state=False): + while True: + print sc.rtm_read() + time.sleep(1) + else: + print "Connection Failed" + + +Passing `auto_reconnect=True` will tell the websocket client to automatically reconnect if the connection gets dropped. + + +See the `rtm.start docs `_ and the `rtm.connect docs `_ +for more details. + + +RTM Events +------------- +:: + + { + u'type': u'message', + u'ts': u'1358878749.000002', + u'user': u'U023BECGF', + u'text': u'Hello' + } + +See `RTM Events `_ for a complete list of events. + +Sending messages via the RTM API +--------------------------------- +You can send a message to Slack by sending JSON over the websocket connection. + +:: + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.rtm_send_message("welcome-test", "test") + +You can send a message to a private group or direct message channel in the same +way, but using a Group ID (``C024BE91L``) or DM channel ID (``D024BE91L``). + +You can send a message in reply to a thread using the ``thread`` argument, and +optionally broadcast that message back to the channel by setting +``reply_broadcast`` to ``True``. + +:: + + from slackclient import SlackClient + + slack_token = os.environ["SLACK_API_TOKEN"] + sc = SlackClient(slack_token) + + sc.rtm_send_message("welcome-test", "test", "1482960137.003543", True) + +See `Threading messages `_ +for more details on using threads. + +The RTM API only supports posting messages with `basic formatting `_. +It does not support attachments or other message formatting modes. + + To post a more complex message as a user, see :ref:`Web API usage `. + +.. include:: metadata.rst diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs.sh b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs.sh new file mode 100644 index 0000000000..daf352aeb5 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +sphinx-build -c ./docs-src/_themes/slack/ -b html docs-src docs && touch ./docs/.nojekyll \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.buildinfo b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.buildinfo new file mode 100644 index 0000000000..1c0a8af899 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 6abbc33d255b00e789666fcb765fbf2d +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.nojekyll b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/ajax-loader.gif b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..61faf8cab23993bd3e1560bff0668bd628642330 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN literal 0 HcmV?d00001 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/basic.css b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/basic.css new file mode 100644 index 0000000000..104f076ae8 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/basic.css @@ -0,0 +1,676 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/classic.css b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/classic.css new file mode 100644 index 0000000000..6cfbfb9c3f --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/classic.css @@ -0,0 +1,261 @@ +/* + * classic.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- classic theme. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/comment-bright.png b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/comment-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..15e27edb12ac25701ac0ac21b97b52bb4e45415e GIT binary patch literal 756 zcmVgfIX78 z$8Pzv({A~p%??+>KickCb#0FM1rYN=mBmQ&Nwp<#JXUhU;{|)}%&s>suq6lXw*~s{ zvHx}3C%<;wE5CH!BR{p5@ml9ws}y)=QN-kL2?#`S5d*6j zk`h<}j1>tD$b?4D^N9w}-k)bxXxFg>+#kme^xx#qg6FI-%iv2U{0h(Y)cs%5a|m%Pn_K3X_bDJ>EH#(Fb73Z zfUt2Q3B>N+ot3qb*DqbTZpFIn4a!#_R-}{?-~Hs=xSS6p&$sZ-k1zDdtqU`Y@`#qL z&zv-~)Q#JCU(dI)Hf;$CEnK=6CK50}q7~wdbI->?E07bJ0R;!GSQTs5Am`#;*WHjvHRvY?&$Lm-vq1a_BzocI^ULXV!lbMd%|^B#fY;XX)n<&R^L z=84u1e_3ziq;Hz-*k5~zwY3*oDKt0;bM@M@@89;@m*4RFgvvM_4;5LB!@OB@^WbVT zjl{t;a8_>od-~P4 m{5|DvB&z#xT;*OnJqG}gk~_7HcNkCr0000W zanA~u9RIXo;n7c96&U)YLgs-FGlx~*_c{Jgvesu1E5(8YEf&5wF=YFPcRe@1=MJmi zag(L*xc2r0(slpcN!vC5CUju;vHJkHc*&70_n2OZsK%O~A=!+YIw z7zLLl7~Z+~RgWOQ=MI6$#0pvpu$Q43 zP@36QAmu6!_9NPM?o<1_!+stoVRRZbW9#SPe!n;#A_6m8f}|xN1;H{`0RoXQ2LM47 zt(g;iZ6|pCb@h2xk&(}S3=EVBUO0e90m2Lp5CB<(SPIaB;n4))3JB87Or#XPOPcum z?<^(g+m9}VNn4Y&B`g8h{t_$+RB1%HKRY6fjtd-<7&EsU;vs0GM(Lmbhi%Gwcfs0FTF}T zL{_M6Go&E0Eg8FuB*(Yn+Z*RVTBE@10eIOb3El^MhO`GabDll(V0&FlJi2k^;q8af zkENdk2}x2)_KVp`5OAwXZM;dG0?M-S)xE1IKDi6BY@5%Or?#aZ9$gcX)dPZ&wA1a< z$rFXHPn|TBf`e?>Are8sKtKrKcjF$i^lp!zkL?C|y^vlHr1HXeVJd;1I~g&Ob-q)& z(fn7s-KI}G{wnKzg_U5G(V%bX6uk zIa+<@>rdmZYd!9Y=C0cuchrbIjuRB_Wq{-RXlic?flu1*_ux}x%(HDH&nT`k^xCeC ziHi1!ChH*sQ6|UqJpTTzX$aw8e(UfcS^f;6yBWd+(1-70zU(rtxtqR%j z-lsH|CKQJXqD{+F7V0OTv8@{~(wp(`oIP^ZykMWgR>&|RsklFMCnOo&Bd{le} zV5F6424Qzl;o2G%oVvmHgRDP9!=rK8fy^!yV8y*4p=??uIRrrr0?>O!(z*g5AvL2!4z0{sq%vhG*Po}`a<6%kTK5TNhtC8}rXNu&h^QH4A&Sk~Autm*s~45(H7+0bi^MraaRVzr05hQ3iK?j` zR#U@^i0WhkIHTg29u~|ypU?sXCQEQgXfObPW;+0YAF;|5XyaMAEM0sQ@4-xCZe=0e z7r$ofiAxn@O5#RodD8rh5D@nKQ;?lcf@tg4o+Wp44aMl~c47azN_(im0N)7OqdPBC zGw;353_o$DqGRDhuhU$Eaj!@m000000NkvXXu0mjfjZ7Z_ literal 0 HcmV?d00001 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/default.css b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/default.css new file mode 100644 index 0000000000..d614fd2cf4 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/default.css @@ -0,0 +1,74 @@ +a.headerlink { + display: none !important; +} + +.section-title { + font-size: 2rem; + line-height: 2.5rem; + letter-spacing: -1px; + font-weight: 700; + margin: 0 0 1rem; +} + +nav#api_nav .toctree-l1 { + margin-bottom: 1.5rem; +} + +nav#api_nav #api_sections ul { + list-style: none; + margin: 0; + padding: 0; +} + +nav#api_nav #api_sections ul li.toctree-l1>a { + color: #1264a3; + letter-spacing: 0; + font-size: .8rem; + font-weight: 800; + text-transform: uppercase; + border: none; + padding: 0; +} + +nav#api_nav #api_sections ul li.toctree-l2 { + margin: 0; + padding: 0; +} + +nav#api_nav #api_sections ul li.toctree-l2 a { + color: #1d1c1d; + text-transform: none; + font-weight: inherit; + padding: 0; + display: block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-size: 15px!important; + line-height:15px; + padding: 4px 8px; + border: 1px solid transparent; + border-radius: 4px; +} + +nav#api_nav #api_sections ul li.toctree-l2 a:hover { + cursor: pointer; + text-decoration: none; + background-color:#e8f5fa; + border-color:#dcf0fb; +} + +nav#api_nav #footer #footer_nav { + font-size: .9375rem; +} + +nav#api_nav #footer #footer_nav a { + border: none; + padding: 0; + color: #616061; +} + +nav#api_nav #footer #footer_nav a:hover { + text-decoration: none; + color: #1c1c1c; +} \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/docs.css b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/docs.css new file mode 100644 index 0000000000..7f360ac666 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/docs.css @@ -0,0 +1,34 @@ +/* Updates body font */ +body { + font-family: Slack-Lato,appleLogo,sans-serif; +} + +/* Replaces old sidebar styled links */ +.sidebar_menu h5 { + font-size: 0.8rem; + font-weight: 800; + margin-bottom: 3px; +} + +/* Aligns footer navigation to the left of the sidebar */ +.footer_nav { + margin: 0 !important; +} + +/* Styles the signature all nice and pretty <3 */ +#footer_signature { + color:#e01e5a; + font-size:.9rem; + margin-top: 10px; +} + +/* Fixes link hover state */ +a:hover { + text-decoration: underline; +} + +/* Makes footer consistent */ +footer { + background-color: transparent; + border: 0; +} \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/doctools.js b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/doctools.js new file mode 100644 index 0000000000..ffadbec11f --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/doctools.js @@ -0,0 +1,315 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var bbox = span.getBBox(); + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + var parentOfText = node.parentNode.parentNode; + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/documentation_options.js b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/documentation_options.js new file mode 100644 index 0000000000..ccedf6692d --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/documentation_options.js @@ -0,0 +1,10 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.0.1', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, +}; \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/down-pressed.png b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/down-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..5756c8cad8854722893dc70b9eb4bb0400343a39 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`OFdm2Ln;`PZ^+1>KjR?B@S0W7 z%OS_REiHONoJ6{+Ks@6k3590|7k9F+ddB6!zw3#&!aw#S`x}3V3&=A(a#84O-&F7T z^k3tZB;&iR9siw0|F|E|DAL<8r-F4!1H-;1{e*~yAKZN5f0|Ei6yUmR#Is)EM(Po_ zi`qJR6|P<~+)N+kSDgL7AjdIC_!O7Q?eGb+L+qOjm{~LLinM4NHn7U%HcK%uoMYO5 VJ~8zD2B3o(JYD@<);T3K0RV0%P>BEl literal 0 HcmV?d00001 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/down.png b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/down.png new file mode 100644 index 0000000000000000000000000000000000000000..1b3bdad2ceffae91cee61b32f3295f9bbe646e48 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6CVIL!hEy=F?b*7pIY7kW{q%Rg zx!yQ<9v8bmJwa`TQk7YSw}WVQ()mRdQ;TC;* literal 0 HcmV?d00001 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/file.png b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/jquery-3.2.1.js b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/jquery-3.2.1.js new file mode 100644 index 0000000000..d2d8ca4790 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/_static/jquery-3.2.1.js @@ -0,0 +1,10253 @@ +/*! + * jQuery JavaScript Library v3.2.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2017-03-20T18:59Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.2.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && Array.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + !compilerCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Simple selector that can be filtered directly, removing non-Elements + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + // Complex selector, compare the two sets, removing non-Elements + qualifier = jQuery.filter( qualifier, elements ); + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; + } ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( nodeName( elem, "iframe" ) ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( jQuery.isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( jQuery.isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ jQuery.camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ jQuery.camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( jQuery.camelCase ); + } else { + key = jQuery.camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + jQuery.contains( elem.ownerDocument, elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, + scale = 1, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + do { + + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + initialInUnit = initialInUnit / scale; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // Break the loop if scale is unchanged or perfect, or if we've just had enough. + } while ( + scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations + ); + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); + +var rscriptType = ( /^$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); +var documentElement = document.documentElement; + + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 only +// See #13393 for more info +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: jQuery.isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( ">tbody", elem )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rmargin = ( /^margin/ ); + +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + div.style.cssText = + "box-sizing:border-box;" + + "position:relative;display:block;" + + "margin:auto;border:1px;padding:1px;" + + "top:1%;width:50%"; + div.innerHTML = ""; + documentElement.appendChild( container ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = divStyle.marginLeft === "2px"; + boxSizingReliableVal = divStyle.width === "4px"; + + // Support: Android 4.0 - 4.3 only + // Some styles come back with percentage values, even though they shouldn't + div.style.marginRight = "50%"; + pixelMarginRightVal = divStyle.marginRight === "4px"; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + + "padding:0;margin-top:1px;position:absolute"; + container.appendChild( div ); + + jQuery.extend( support, { + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelMarginRight: function() { + computeStyleTests(); + return pixelMarginRightVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + + cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style; + +// Return a css property mapped to a potentially vendor prefixed property +function vendorPropName( name ) { + + // Shortcut for names that are not vendor prefixed + if ( name in emptyStyle ) { + return name; + } + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a property mapped along what jQuery.cssProps suggests or to +// a vendor prefixed property. +function finalPropName( name ) { + var ret = jQuery.cssProps[ name ]; + if ( !ret ) { + ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; + } + return ret; +} + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i, + val = 0; + + // If we already have the right measurement, avoid augmentation + if ( extra === ( isBorderBox ? "border" : "content" ) ) { + i = 4; + + // Otherwise initialize for horizontal or vertical properties + } else { + i = name === "width" ? 1 : 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // At this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + + // At this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // At this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with computed style + var valueIsBorderBox, + styles = getStyles( elem ), + val = curCSS( elem, name, styles ), + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test( val ) ) { + return val; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && + ( support.boxSizingReliable() || val === elem.style[ name ] ); + + // Fall back to offsetWidth/Height when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + if ( val === "auto" ) { + val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ]; + } + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + + // Use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + "float": "cssFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + } ) : + getWidthOrHeight( elem, name, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = extra && getStyles( elem ), + subtract = extra && augmentWidthOrHeight( + elem, + name, + extra, + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + styles + ); + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ name ] = value; + value = jQuery.css( elem, name ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && + ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || + jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = jQuery.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 13 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( jQuery.isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + jQuery.proxy( result.stop, result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( type === "string" ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = value.match( rnothtmlwhite ) || []; + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, isFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +} ); + +jQuery.fn.extend( { + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +} ); + + + + +support.focusin = "onfocusin" in window; + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = jQuery.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = jQuery.isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( jQuery.isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 13 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available, append data to url + if ( s.data ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( jQuery.isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
About
+
+
+

About¶

+
+

Slack Developer Kit for Python¶

+

Access the Slack Platform from your Python app. Slack Developer Kit for Python lets you build on the Slack Web APIs pythonically.

+

Slack Developer Kit for Python is proudly maintained with 💖 by the Slack Developer Tools team

+ +
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/auth.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/auth.html new file mode 100644 index 0000000000..e541ccb53c --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/auth.html @@ -0,0 +1,300 @@ + + + + + + + Tokens & Authentication — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Tokens & Authentication
+
+
+

Tokens & Authentication¶

+
+

Handling tokens and other sensitive data¶

+

âš ï¸ Slack tokens are the keys to your—or your customers’—data.Keep them secret. Keep them safe.

+

One way to do that is to never explicitly hardcode them.

+

Try to avoid this when possible:

+
token = 'xoxb-abc-1232'
+
+
+

âš ï¸ Never share test tokens with other users or applications. Do not publish test tokens in public code repositories.

+

We recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as:

+
SLACK_BOT_TOKEN="xoxb-abc-1232" python myapp.py
+
+
+

Then retrieve the key with:

+
import os
+SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
+
+
+

You can use the same technique for other kinds of sensitive data that ne’er-do-wells could use in nefarious ways, including

+
    +
  • Incoming webhook URLs
  • +
  • Slash command verification tokens
  • +
  • App client ids and client secrets
  • +
+

For additional information, please see our Safely Storing Credentials page.

+
+
+

Single-Workspace Apps¶

+

If you’re building an application for a single Slack workspace, there’s no need to build out the entire OAuth flow.

+

Once you’ve setup your features, click on the Install App to Team button found on the Install App page. +If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to +your workspace for changes to take effect.

+

For additional information, see the Installing Apps of our Building Slack apps page.

+
+
+

The OAuth flow¶

+

Authentication for Slack’s APIs is done using OAuth, so you’ll want to read up on OAuth.

+

In order to implement OAuth in your app, you will need to include a web server. In this example, we’ll use Flask.

+

As mentioned above, we’re setting the app tokens and other configs in environment variables and pulling them into global variables.

+

Depending on what actions your app will need to perform, you’ll need different OAuth permission scopes. Review the available scopes here.

+
import os
+from flask import Flask, request
+from slackclient import SlackClient
+
+client_id = os.environ["SLACK_CLIENT_ID"]
+client_secret = os.environ["SLACK_CLIENT_SECRET"]
+oauth_scope = os.environ["SLACK_BOT_SCOPE"]
+
+app = Flask(__name__)
+
+
+

The OAuth initiation link:

+

To begin the OAuth flow, you’ll need to provide the user with a link to Slack’s OAuth page. +This directs the user to Slack’s OAuth acceptance page, where the user will review and accept +or refuse the permissions your app is requesting as defined by the requested scope(s).

+

For the best user experience, use the Add to Slack button to direct users to approve your application for access or Sign in with Slack to log users in.

+
@app.route("/begin_auth", methods=["GET"])
+def pre_install():
+  return '''
+      <a href="https://slack.com/oauth/authorize?scope={0}&client_id={1}">
+          Add to Slack
+      </a>
+  '''.format(oauth_scope, client_id)
+
+
+

The OAuth completion page

+

Once the user has agreed to the permissions you’ve requested on Slack’s OAuth +page, Slack will redirect the user to your auth completion page. Included in this +redirect is a code query string param which you’ll use to request access +tokens from the oauth.access endpoint.

+

Generally, Web API requests require a valid OAuth token, but there are a few endpoints +which do not require a token. oauth.access is one example of this. Since this +is the endpoint you’ll use to retrieve the tokens for later API requests, +an empty string "" is acceptable for this request.

+
@app.route("/finish_auth", methods=["GET", "POST"])
+def post_install():
+
+  # Retrieve the auth code from the request params
+  auth_code = request.args['code']
+
+  # An empty string is a valid token for this request
+  sc = SlackClient("")
+
+  # Request the auth tokens from Slack
+  auth_response = sc.api_call(
+    "oauth.access",
+    client_id=client_id,
+    client_secret=client_secret,
+    code=auth_code
+  )
+
+
+

A successful request to oauth.access will yield two tokens: A user +token and a bot token. The user token auth_response['access_token'] +is used to make requests on behalf of the authorizing user and the bot +token auth_response['bot']['bot_access_token'] is for making requests +on behalf of your app’s bot user.

+

If your Slack app includes a bot user, upon approval the JSON response will contain +an additional node containing an access token to be specifically used for your bot +user, within the context of the approving team.

+

When you use Web API methods on behalf of your bot user, you should use this bot +user access token instead of the top-level access token granted to your application.

+
# Save the bot token to an environmental variable or to your data store
+# for later use
+os.environ["SLACK_USER_TOKEN"] = auth_response['access_token']
+os.environ["SLACK_BOT_TOKEN"] = auth_response['bot']['bot_access_token']
+
+# Don't forget to let the user know that auth has succeeded!
+return "Auth complete!"
+
+
+

Once your user has completed the OAuth flow, you’ll be able to use the provided +tokens to make a variety of Web API calls on behalf of the user and your app’s bot user.

+

See the Web API usage section of this documentation for usage examples.

+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/basic_usage.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/basic_usage.html new file mode 100644 index 0000000000..63f6305762 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/basic_usage.html @@ -0,0 +1,545 @@ + + + + + + + Basic Usage — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Basic Usage
+
+
+

Basic Usage¶

+

The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations +we provide out of the box.

+

This package is a modular wrapper designed to make Slack Web API calls simpler and easier for your +app. Provided below are examples of how to interact with commonly used API endpoints, but this is by no means +a complete list. Review the full list of available methods here.

+

See Tokens & Authentication for API token handling best practices.

+
+
+

Sending a message¶

+

The primary use of Slack is sending messages. Whether you’re sending a message +to a user or to a channel, this method handles both.

+

To send a message to a channel, use the channel’s ID. For IMs, use the user’s ID.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "chat.postMessage",
+  channel="C0XXXXXX",
+  text="Hello from Python! :tada:"
+)
+
+
+

There are some unique options specific to sending IMs, so be sure to read the channels +section of the chat.postMessage +page for a full list of formatting and authorship options.

+

Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same +as sending a regular message, but with an additional user parameter.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "chat.postEphemeral",
+  channel="C0XXXXXX",
+  text="Hello from Python! :tada:",
+  user="U0XXXXXXX"
+)
+
+
+

See chat.postEphemeral for more info.

+
+
+
+

Customizing a message’s layout¶

+

The chat.postMessage method takes an optional blocks argument that allows you to customize the layout of a message. +Blocks for Web API methods are all specified in a single object literal, so just add additional keys for any optional argument.

+

To send a message to a channel, use the channel’s ID. For IMs, use the user’s ID.

+
sc.api_call(
+  "chat.postMessage",
+  channel="C0XXXXXX",
+  blocks=[
+    {
+        "type": "section",
+        "text": {
+            "type": "mrkdwn",
+            "text": "Danny Torrence left the following review for your property:"
+        }
+    },
+    {
+        "type": "section",
+        "text": {
+            "type": "mrkdwn",
+            "text": "<https://example.com|Overlook Hotel> \n :star: \n Doors had too many axe holes, guest in room " +
+            "237 was far too rowdy, whole place felt stuck in the 1920s."
+        },
+        "accessory": {
+            "type": "image",
+            "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg",
+            "alt_text": "Haunted hotel image"
+        }
+    },
+    {
+        "type": "section",
+        "fields": [
+            {
+                "type": "mrkdwn",
+                "text": "*Average Rating*\n1.0"
+            }
+        ]
+    }
+  ]
+)
+
+
+

Note: You can use the `Block Kit Builder <https://api.slack.com/tools/block-kit-builder>`for a playground where you can prototype your message’s look and feel.

+
+
+
+

Replying to messages and creating threads¶

+

Threaded messages are just like regular messages, except thread replies are grouped together to provide greater context +to the user. You can reply to a thread or start a new threaded conversation by simply passing the original message’s ts +ID in the thread_ts attribute when posting a message. If you’re replying to a threaded message, you’ll pass the thread_ts +ID of the message you’re replying to.

+

A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. +When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not +appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "chat.postMessage",
+  channel="C0XXXXXX",
+  text="Hello from Python! :tada:",
+  thread_ts="1476746830.000003"
+)
+
+
+

By default, reply_broadcast is set to False. To indicate your reply is germane to all members of a channel, +set the reply_broadcast boolean parameter to True.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "chat.postMessage",
+  channel="C0XXXXXX",
+  text="Hello from Python! :tada:",
+  thread_ts="1476746830.000003",
+  reply_broadcast=True
+)
+
+
+

Note: While threaded messages may contain attachments and message buttons, when your reply is broadcast to the +channel, it’ll actually be a reference to your reply, not the reply itself. +So, when appearing in the channel, it won’t contain any attachments or message buttons. Also note that updates and +deletion of threaded replies works the same as regular messages.

+

See the Threading messages together +article for more information.

+
+
+
+

Updating the content of a message¶

+

Let’s say you have a bot which posts the status of a request. When that request +is updated, you’ll want to update the message to reflect it’s state. Or your user +might want to fix a typo or change some wording. This is how you’ll make those changes.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "chat.update",
+  ts="1476746830.000003",
+  channel="C0XXXXXX",
+  text="Hello from Python! :tada:"
+)
+
+
+

See chat.update for formatting options +and some special considerations when calling this with a bot user.

+
+
+
+

Deleting a message¶

+

Sometimes you need to delete things.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "chat.delete",
+  channel="C0XXXXXX",
+  ts="1476745373.000002"
+)
+
+
+

See chat.delete for more info.

+
+
+
+

Adding or removing an emoji reaction¶

+

You can quickly respond to any message on Slack with an emoji reaction. Reactions +can be used for any purpose: voting, checking off to-do items, showing excitement — and just for fun.

+

This method adds a reaction (emoji) to an item (file, file comment, channel message, group message, or direct message). One of file, file_comment, or the combination of channel and timestamp must be specified.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "reactions.add",
+  channel="C0XXXXXXX",
+  name="thumbsup",
+  timestamp="1234567890.123456"
+)
+
+
+

Removing an emoji reaction is basically the same format, but you’ll use reactions.remove instead of reactions.add

+
sc.api_call(
+  "reactions.remove",
+  channel="C0XXXXXXX",
+  name="thumbsup",
+  timestamp="1234567890.123456"
+)
+
+
+

See reactions.add and reactions.remove for more info.

+
+
+
+

Getting a list of channels¶

+

At some point, you’ll want to find out what channels are available to your app. This is how you get that list.

+

Note: This call requires the channels:read scope.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call("channels.list")
+
+
+

Archived channels are included by default. You can exclude them by passing exclude_archived=1 to your request.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "channels.list",
+  exclude_archived=1
+)
+
+
+

See channels.list for more info.

+
+
+
+

Getting a channel’s info¶

+

Once you have the ID for a specific channel, you can fetch information about that channel.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "channels.info",
+  channel="C0XXXXXXX"
+)
+
+
+

See channels.info for more info.

+
+
+
+

Joining a channel¶

+

Channels are the social hub of most Slack teams. Here’s how you hop into one:

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "channels.join",
+  channel="C0XXXXXXY"
+)
+
+
+

If you are already in the channel, the response is slightly different. +already_in_channel will be true, and a limited channel object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user.

+

See channels.join for more info.

+
+
+
+

Leaving a channel¶

+

Maybe you’ve finished up all the business you had in a channel, or maybe you +joined one by accident. This is how you leave a channel.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "channels.leave",
+  channel="C0XXXXXXX"
+)
+
+
+

See channels.leave for more info.

+
+
+
+

Get a list of team members¶

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call("users.list")
+
+
+

See users.list for more info.

+
+
+
+

Uploading files¶

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+with open('thinking_very_much.png') as file_content:
+    sc.api_call(
+        "files.upload",
+        channels="C3UKJTQAC",
+        file=file_content,
+        title="Test upload"
+    )
+
+
+

See files.upload for more info.

+
+
+
+

Web API Rate Limits¶

+

Slack allows applications to send no more than one message per second. We allow bursts over that +limit for short periods. However, if your app continues to exceed the limit over a longer period +of time it will be rate limited.

+

Here’s a very basic example of how one might deal with rate limited requests.

+

If you go over these limits, Slack will start returning a HTTP 429 Too Many Requests error, +a JSON object containing the number of calls you have been making, and a Retry-After header +containing the number of seconds until you can retry.

+
from slackclient import SlackClient
+import time
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+# Simple wrapper for sending a Slack message
+def send_slack_message(channel, message):
+  return sc.api_call(
+    "chat.postMessage",
+    channel=channel,
+    text=message
+  )
+
+# Make the API call and save results to `response`
+response = send_slack_message("C0XXXXXX", "Hello, from Python!")
+
+# Check to see if the message sent successfully.
+# If the message succeeded, `response["ok"]`` will be `True`
+if response["ok"]:
+  print("Message posted successfully: " + response["message"]["ts"])
+  # If the message failed, check for rate limit headers in the response
+elif response["ok"] is False and response["headers"]["Retry-After"]:
+  # The `Retry-After` header will tell you how long to wait before retrying
+  delay = int(response["headers"]["Retry-After"])
+  print("Rate limited. Retrying in " + str(delay) + " seconds")
+  time.sleep(delay)
+  send_slack_message(message, channel)
+
+
+

See the documentation on Rate Limiting for more info.

+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/changelog.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/changelog.html new file mode 100644 index 0000000000..f2b1d919e0 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/changelog.html @@ -0,0 +1,365 @@ + + + + + + + Changelog — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Changelog
+
+
+

Changelog¶

+
+

v1.3.0 (2018-09-11)¶

+

## New Features +- Adds support for short lived tokens and automatic token refresh #347 (Thanks @roach!)

+

## Other +- update RTM rate limiting comment and error message #308 (Thanks @benoitlavigne!) +- Use logging instead of traceback #309 (Thanks @harlowja!) +- Remove Python 3.3 from test environments #346 (Thanks @roach!) +- Enforced linting when using VSCode. #347 (Thanks @roach!)

+
+
+

v1.2.1 (2018-03-26)¶

+
    +
  • Added rate limit handling for rtm connections (thanks @jayalane!)
  • +
+
+
+

v1.2.0 (2018-03-20)¶

+
    +
  • You can now tell the RTM client to automatically reconnect by passing auto_reconnect=True
  • +
+
+
+

v1.1.3 (2018-03-01)¶

+
    +
  • Fixed another API param encoding bug. It encodes things properly now.
  • +
+
+
+

v1.1.2 (2018-01-31)¶

+
    +
  • Fixed an encoding issue which was encoding some Web API params incorrectly (sorry)
  • +
+
+
+

v1.1.1 (2018-01-30)¶

+
+
    +
  • Adds HTTP response headers to api_call responses to expose things like rate limit info
  • +
  • Moves token into auth header rather than request params
  • +
+
+
+
+

v1.1.0 (2017-11-21)¶

+
+
    +
  • Aadds new SlackClientError and ResponseParseError types to describe errors - thanks @aoberoi!
  • +
  • Fix Build Error (#245) - thanks @stasfilin!
  • +
  • include email as user property (#173) - thanks @acaire!
  • +
  • Add http reply into slack login and slack connection error (#216) - thanks @harlowja!
  • +
  • Removed unused exception class (#233)
  • +
  • Fix rtm_send_message bug (#225) - thanks @kt5356!
  • +
  • Allow use of custom parameters on rtm_connect() (#210) - thanks @kamushadenes!
  • +
  • Fix link to rtm.connect docs (#223) - @sampart!
  • +
+
+
+
+

v1.0.9 (2017-08-31)¶

+
+
    +
  • Fixed rtm_send_message ID bug introduced in 1.0.8
  • +
+
+
+
+

v1.0.8 (2017-08-31)¶

+
+
    +
  • Added rtm.connect support
  • +
+
+
+
+

v1.0.7 (2017-08-02)¶

+
+
    +
  • Fixes an issue where connecting over RTM to large teams may result in “Websocket URL expired†errors
  • +
  • A load of packaging improvements
  • +
+
+
+
+

v1.0.6 (2017-06-12)¶

+
+
    +
  • Added proxy support (thanks @timfeirg!)
  • +
  • Tidied up docs (thanks @schlueter!)
  • +
  • Added tox settings for Python 3 testing (thanks @cclauss!)
  • +
+
+
+
+

v1.0.5 (2017-01-23)¶

+
+
    +
  • Allow RTM Channel.send_message to reply to a thread
  • +
  • Index users by ID instead of Name (non-breaking change)
  • +
  • Added timeout to api calls.
  • +
  • Fixed a typo about token access in auth.rst, thanks @kelvintaywl!
  • +
  • Added Message Threads to the docs
  • +
+
+
+
+

v1.0.4 (2016-12-15)¶

+
+
    +
  • fixed the ability to search for a user by ID
  • +
+
+
+
+

v1.0.3 (2016-12-13)¶

+
+
    +
  • fixed an issue causing RTM connections to fail for large teams
  • +
+
+
+
+

v1.0.2 (2016-09-22)¶

+
+
    +
  • removed unused ping counter
  • +
  • fixed contributor guidelines links
  • +
  • updated documentation
  • +
  • Fix bug preventing API calls requiring a file ID
  • +
  • Removes files from api_calls before JSON encoding, so the request is properly formatted
  • +
+
+
+
+

v1.0.1 (2016-03-25)¶

+
+
    +
  • fix for __eq__ comparison in channels using ‘#’ in channel name
  • +
  • added copyright info to the LICENSE file
  • +
+
+
+
+

v1.0.0 (2016-02-28)¶

+
+
    +
  • the api_call function now returns a decoded JSON object, rather than a JSON encoded string
  • +
  • some api_call calls now call actions on the parent server object: +- im.open +- mpim.open, groups.create, groups.createChild +- channels.create, channels.join`
  • +
+
+
+
+

v0.18.0 (2016-02-21)¶

+
+
    +
  • Moves to use semver for versioning
  • +
  • Adds support for private groups and MPDMs
  • +
  • Switches to use requests instead of urllib
  • +
  • Gets Travis CI integration working
  • +
  • Fixes some formatting issues so the code will work for python 2.6
  • +
  • Cleans up some unused imports, some PEP-8 fixes and a couple bad default args fixes
  • +
+
+
+
+

v0.17.0 (2016-02-15)¶

+
+
+
+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/conversations.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/conversations.html new file mode 100644 index 0000000000..574ef819e8 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/conversations.html @@ -0,0 +1,313 @@ + + + + + + + Conversations API — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Conversations API
+
+
+

Conversations API¶

+

The Slack Conversations API provides your app with a unified interface to work with all the +channel-like things encountered in Slack; public channels, private channels, direct messages, group +direct messages, and our newest channel type, Shared Channels.

+

See Conversations API docs for more info.

+
+
+

Creating a direct message or multi-person direct message¶

+

This Conversations API method opens a multi-person direct message or just a 1:1 direct message.

+

Use conversations.create for public or private channels.

+

Provide 1 to 8 user IDs in the user parameter to open or resume a conversation. Providing only +1 ID will create a direct message. Providing more will create an mpim.

+

If there are no conversations already in progress including that exact set of members, a new +multi-person direct message conversation begins.

+

Subsequent calls to conversations.open with the same set of users will return the already +existing conversation.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "conversations.open",
+  users=["W1234567890","U2345678901","U3456789012"]
+)
+
+
+

See conversations.open additional info.

+
+
+
+

Creating a public or private channel¶

+

Initiates a public or private channel-based conversation

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "conversations.create",
+  name="myprivatechannel",
+  is_private=True
+)
+
+
+

See conversations.create additional info.

+
+
+
+

Getting information about a conversation¶

+

This Conversations API method returns information about a workspace conversation.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "conversations.info",
+  channel="C0XXXXXX",
+)
+
+
+

See conversations.info for more info.

+
+
+
+

Getting a list of conversations¶

+

This Conversations API method returns a list of all channel-like conversations in a workspace. +The “channels†returned depend on what the calling token has access to and the directives placed +in the types parameter.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call("conversations.list")
+
+
+

Only public conversations are included by default. You may include additional conversations types +by passing types (as a string) into your list request. Additional conversation types include +public_channel and private_channel.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+# Note that `types` is a string
+sc.api_call(
+  "conversations.list",
+  types="public_channel, private_channel"
+)
+
+
+

See conversations.list for more info.

+
+
+
+

Leaving a conversation¶

+

Maybe you’ve finished up all the business you had in a conversation, or maybe you +joined one by accident. This is how you leave a conversation.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call(
+  "conversations.leave",
+  channel="C0XXXXXXX"
+)
+
+
+

See conversations.leave for more info.

+
+
+
+

Get conversation members¶

+

Get a list fo the members of a conversation

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.api_call("conversations.members",
+  channel="C0XXXXXXX"
+)
+
+
+

See users.list for more info.

+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/faq.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/faq.html new file mode 100644 index 0000000000..98d7916613 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/faq.html @@ -0,0 +1,230 @@ + + + + + + + Frequently Asked Questions — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Frequently Asked Questions
+
+
+

Frequently Asked Questions¶

+
+

What even is Slack Developer Kit for Python and why should I care?¶

+

Slack Developer Kit for Python is a wrapper around commonly accessed parts of the Slack Platform. It provides basic mechanisms for +using the Slack Web API from within your Python app.

+

On the other hand, Slack Developer Kit for Python does not provide access to the Events bot-building API, but +[this adapter](https://github.com/slackapi/python-slack-events-api) does.

+
+
+

OMG I found a bug!¶

+

Well, poop. Take a deep breath, and then let us know on the Issue Tracker. If you’re feeling particularly ambitious, +why not submit a pull request with a bug fix?

+
+
+

Hey, there’s a feature missing!¶

+

There’s always something more that could be added! You can let us know in the Issue Tracker to start a discussion +around the proposed feature, that’s a good start. If you’re feeling particularly ambitious, why not write the feature +yourself, and submit a pull request! We love feedback and we love help and we don’t bite. Much.

+
+
+

I’d like to contribute…but how?¶

+

What an excellent question. First of all, please have a look at our general contributing guidelines. We’ll wait for +you here.

+

All done? Great! While we’re super excited to incorporate your new feature into Slack Developer Kit for Python, there are a +couple of things we want to make sure you’ve given thought to.

+
    +
  • Please write unit tests for your new code. But don’t just aim to increase the test coverage, rather, we expect you +to have written thoughtful tests that ensure your new feature will continue to work as expected, and to help future +contributors to ensure they don’t break it!
  • +
  • Please document your new feature. Think about concrete use cases for your feature, and add a section to the +appropriate document, including a complete sample program that demonstrates your feature. Don’t forget to update +the changelog in changelog.rst!
  • +
+

Including these two items with your pull request will totally make our day—and, more importantly, your future users’ days!

+

On that note…

+
+
+

How do I compile the documentation?¶

+

This project’s documentation is generated with Sphinx. If you are editing one of the many +reStructuredText files in the docs-src folder, you’ll need to rebuild the documentation. It is recommended to run +the following steps inside a virtualenv environment.

+
tox -e docs
+
+
+

Do be sure to add the docs folder and its contents to your pull request!

+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/genindex.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/genindex.html new file mode 100644 index 0000000000..615d99ec64 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/genindex.html @@ -0,0 +1,189 @@ + + + + + + + + Index — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Index
+
+ +

Index

+ +
+ +
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/index.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/index.html new file mode 100644 index 0000000000..e88e84bf18 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/index.html @@ -0,0 +1,211 @@ + + + + + + + Slack Developer Kit for Python — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Slack Developer Kit for Python
+
+
+
+
+

Slack Developer Kit for Python¶

+

Whether you’re building a custom app for your team, or integrating a third party +service into your Slack workflows, Slack Developer Kit for Python allows you to leverage the flexibility +of Python to get your project up and running as quickly as possible.

+
+

Requirements and Installation¶

+

We recommend using PyPI to install Slack Developer Kit for Python

+
pip install slackclient
+
+
+

Of course, if you prefer doing things the hard way, you can always implement Slack Developer Kit for Python +by pulling down the source code directly into your project:

+
git clone https://github.com/slackapi/python-slackclient.git
+pip install -r requirements.txt
+
+
+
+
+

Getting Help¶

+

If you get stuck, we’re here to help. The following are the best ways to get assistance working through your issue:

+
    +
  • Use our Github Issue Tracker for reporting bugs or requesting features.
  • +
  • Visit the Bot Developer Hangout for getting help using Slack Developer Kit for Python or just generally bond with your fellow Slack developers.
  • +
+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/metadata.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/metadata.html new file mode 100644 index 0000000000..87e93ebaf6 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/metadata.html @@ -0,0 +1,182 @@ + + + + + + + <no title> — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
<no title>
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/objects.inv b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/objects.inv new file mode 100644 index 0000000000..6fa1db8dfa --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/objects.inv @@ -0,0 +1,7 @@ +# Sphinx inventory version 2 +# Project: Slack Developer Kit for Python +# Version: 1.0 +# The remainder of this file is compressed using zlib. +xÚ}“ÁNÃ0 †ïy +KCEâŠR!&4iŒq®¼Ök£¥Iפc{{’¥-ÉÜâßþìØNp¥:Úw…Ê!¹tÂMej©;2ìLXÛû—jCRÃ%¤V"ixކ+ÉV¨yžuK +Á@öü£àÓ ,¯P–$T£è㟓åJî¨ÕÇr:"BGOE±é|Ó6ü˜AàŠÄù“HJñ;ë·áM¬éë¿´´íìtÄR½¡Þ;ÒGŒ•$¹,h×TOÝ‘ÙÎ Áe™?ó72¹€×>ú@k‚²ÛiA[“¾#(Ð û)Ü_8¨ù!0ßÀ3íì°‹¾qkÕÂü`*»ßš º!=h>Á½T`¸ôÀjUœé±9$ƒÃ3Ut‚ún[B‘^SR“¶ÏÃõá. sÙ0 `a]°´.˜ìÕb9»fg °ƒ¿rþ“Q¶y_Íký½îø­ÜÃIhu#èd…§Ã6þŽoàM \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/real_time_messaging.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/real_time_messaging.html new file mode 100644 index 0000000000..6cc67a7386 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/real_time_messaging.html @@ -0,0 +1,290 @@ + + + + + + + Real Time Messaging (RTM) — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Real Time Messaging (RTM)
+
+
+

Real Time Messaging (RTM)¶

+

The Real Time Messaging (RTM) API is a WebSocket-based API that allows you to +receive events from Slack in real time and send messages as users.

+

If you prefer events to be pushed to you instead, we recommend using the +HTTP-based Events API instead. +Most event types supported by the RTM API are also available +in the Events API.

+

See Tokens & Authentication for API token handling best practices.

+
+

Connecting to the RTM API¶

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+if sc.rtm_connect():
+  while sc.server.connected is True:
+        print sc.rtm_read()
+        time.sleep(1)
+else:
+    print "Connection Failed"
+
+
+

If you connect successfully the first event received will be a hello:

+
{
+  u'type': u'hello'
+}
+
+
+

If there was a problem connecting an error will be returned, including a descriptive error message:

+
{
+  u'type': u'error',
+    u'error': {
+    u'code': 1,
+    u'msg': u'Socket URL has expired'
+  }
+}
+
+
+
+
+

rtm.start vs rtm.connect¶

+

If you expect your app to be used on large teams, we recommend starting the RTM client with rtm.connect rather than the default connection method for this client, rtm.start. +rtm.connect provides a lighter initial connection payload, without the team’s channel and user information included. You’ll need to request channel and user info via +the Web API separately.

+

To do this, simply pass with_team_state=False into the rtm_connect call, like so:

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+if sc.rtm_connect(with_team_state=False):
+    while True:
+        print sc.rtm_read()
+        time.sleep(1)
+else:
+    print "Connection Failed"
+
+
+

Passing auto_reconnect=True will tell the websocket client to automatically reconnect if the connection gets dropped.

+

See the rtm.start docs and the rtm.connect docs +for more details.

+
+
+

RTM Events¶

+
{
+  u'type': u'message',
+  u'ts': u'1358878749.000002',
+  u'user': u'U023BECGF',
+  u'text': u'Hello'
+}
+
+
+

See RTM Events for a complete list of events.

+
+
+

Sending messages via the RTM API¶

+

You can send a message to Slack by sending JSON over the websocket connection.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.rtm_send_message("welcome-test", "test")
+
+
+

You can send a message to a private group or direct message channel in the same +way, but using a Group ID (C024BE91L) or DM channel ID (D024BE91L).

+

You can send a message in reply to a thread using the thread argument, and +optionally broadcast that message back to the channel by setting +reply_broadcast to True.

+
from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+sc = SlackClient(slack_token)
+
+sc.rtm_send_message("welcome-test", "test", "1482960137.003543", True)
+
+
+

See Threading messages +for more details on using threads.

+

The RTM API only supports posting messages with basic formatting. +It does not support attachments or other message formatting modes.

+
+
To post a more complex message as a user, see Web API usage.
+
+
+ + +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/search.html b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/search.html new file mode 100644 index 0000000000..84d7cb9dc5 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/search.html @@ -0,0 +1,204 @@ + + + + + + + Search — Slack Developer Kit for Python + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Slack Developer Kit for Python + + +
+ + +
+
+ + + + + +
+
Search
+
+

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+ + +
+
+ +
+

© 2019 Slack Technologies, Inc. and contributors

+
+ + + + + + + \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/searchindex.js b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/searchindex.js new file mode 100644 index 0000000000..a6691c9095 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["about","auth","basic_usage","changelog","conversations","faq","index","metadata","real_time_messaging"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,sphinx:54},filenames:["about.rst","auth.rst","basic_usage.rst","changelog.rst","conversations.rst","faq.rst","index.rst","metadata.rst","real_time_messaging.rst"],objects:{},objnames:{},objtypes:{},terms:{"0cb4bcd6e887b428e27e8059b6278b86ee661aaa":3,"1046cc2375a85a22e94573e2aad954ba7287c886":3,"1920s":2,"7d01515cebc80918a29100b0e4793790eb83e7b9":3,"boolean":2,"break":[3,5],"case":5,"class":3,"default":[2,3,4,8],"function":3,"import":[1,2,3,4,8],"int":2,"long":2,"new":[1,2,3,4,5],"public":1,"return":[1,2,3,4,8],"short":[2,3],"super":5,"switch":3,"true":[2,3,4,8],"try":1,"while":[2,5,8],Added:3,But:5,For:[1,2],IDs:4,IMs:2,One:[1,2],The:[2,4,6,8],Then:1,There:[2,5],Use:[3,4,6],__eq__:3,__name__:1,aadd:3,abc:1,abil:3,abl:1,about:[2,3,5],abov:1,acair:3,accept:1,access:[0,1,3,4,5],access_token:1,accessori:2,accid:[2,4],action:[1,3],actual:2,adapt:5,add:[1,2,3,5],added:[3,5],addit:[1,2,4],after:[1,2],agre:1,agreement:0,aim:5,all:[2,4,5],allow:[2,3,6,8],alreadi:[2,4],already_in_channel:2,also:[2,8],alt_text:2,alwai:[5,6],ambiti:5,ani:2,anoth:[2,3],aoberoi:3,api:[0,1,3,5],api_cal:[1,2,3,4],app:[0,2,4,5,6,8],appear:2,applic:[1,2],appropri:5,approv:1,archiv:2,arg:[1,3],argument:[2,8],around:5,articl:2,assign:2,assist:6,attach:[2,8],attribut:2,auth:[1,3],auth_cod:1,auth_respons:1,authent:[2,3,8],author:1,authorship:2,auto_reconnect:[3,8],automat:[3,8],avail:[1,2,8],averag:2,avoid:1,axe:2,back:8,bad:3,base:[4,8],basic:[5,8],becom:2,been:[1,2],befor:[2,3],begin:[1,4],begin_auth:1,behalf:1,below:2,benoitlavign:3,best:[1,2,6,8],between:2,bite:5,block:2,bond:6,bot:[1,2,5,6],bot_access_token:1,both:2,box:2,breath:5,broadcast:[2,8],bug:[3,6],build:[0,1,2,3,5,6],builder:2,burst:2,busi:[2,4],button:[1,2],c024be91l:8,c0xxxxxx:[2,4],c0xxxxxxx:[2,4],c0xxxxxxy:2,c3ukjtqac:2,cach:3,call:[1,2,3,4,8],can:[1,2,3,5,6,8],cannot:2,caus:3,cclauss:3,chang:[1,2,3],changelog:5,channel:[3,8],channnel:3,chat:2,check:2,clean:3,click:1,client:[1,3,8],client_id:1,client_secret:1,clone:6,code:[0,1,3,5,6,8],com:[1,2,3,5,6],combin:2,command:1,comment:[2,3],commit:3,commonli:[2,5],comparison:3,compat:3,complet:[1,2,5,8],complex:[2,8],concret:5,conduct:0,config:1,configur:3,connect:3,consider:2,contain:[1,2],content:5,context:[1,2],continu:[2,5],contribut:0,contributor:[0,3,5],convers:2,copyright:3,could:[1,5],counter:3,coupl:[3,5],cours:6,coverag:5,creat:3,createchild:3,credenti:1,custom:[1,3,6],d024be91l:8,d45285d2f1025899dcd65e259624ee73771f94bb:3,dai:5,danni:2,databas:1,deal:2,decod:3,deep:5,def:[1,2],defin:1,delai:2,demonstr:5,depend:[1,4],descend:2,describ:3,descript:8,design:2,detail:8,differ:[1,2],direct:[1,2,8],directli:[2,6],discuss:5,doc:[3,4,5,8],document:[1,2,3],doe:[5,8],doesn:3,doing:6,don:[1,5],done:[1,5],door:2,down:6,drop:8,duplic:3,easier:2,edit:5,effect:1,elif:2,els:8,email:3,empti:1,encod:3,encount:4,endpoint:[1,2],enforc:3,ensur:5,entir:1,environ:[1,2,3,4,5,8],environment:1,ephemer:2,error:[2,3,8],event:5,exact:4,exampl:[1,2],exce:2,excel:5,except:[2,3],exchang:2,excit:[2,5],exclud:2,exclude_archiv:2,exist:4,expect:[5,8],experi:1,expir:[3,8],explicitli:1,expos:3,f7bb8889580cc34471ba1ddc05afc34d1a5efa23:3,fail:[2,3,8],fals:[2,8],far:2,featur:[1,3,6],feedback:5,feel:[2,5],fellow:6,felt:2,fetch:2,few:1,field:2,file:[3,5],file_com:2,file_cont:2,find:2,finish:[2,4],finish_auth:1,first:[5,8],fix:[2,3,5],flask:1,flexibl:6,folder:5,follow:[2,5,6],forget:[1,5],fork:2,format:[1,2,3,8],found:1,from:[0,1,2,3,4,5,8],full:2,fun:2,futur:5,gener:[1,5,6],german:2,get:[1,3,8],git:6,github:[3,5,6],given:5,global:1,good:5,grant:1,great:5,greater:2,group:[2,3,4,8],guest:2,guidelin:[3,5],had:[2,4],hand:5,handl:[2,3,8],hangout:6,hard:6,hardcod:1,harlowja:3,has:[1,4,8],haunt:2,have:[2,5],header:[2,3],hello:[2,8],help:5,here:[1,2,5,6],hole:2,hop:2,hotel:2,how:[2,4],howev:2,href:1,http:[1,2,3,5,6,8],hub:2,ids:1,imag:2,image_url:2,implement:[1,6],importantli:5,improv:3,includ:[1,2,3,4,5,8],incom:1,incorpor:5,incorrectli:3,increas:5,index:3,indic:2,info:[3,4,8],inform:[1,2,8],initi:[1,4,8],insid:5,instal:1,instead:[1,2,3,8],integr:[2,3,6],interact:2,interfac:4,intern:3,introduc:3,invit:2,is_priv:4,issu:[3,5,6],item:[2,5],its:[3,5],itself:2,jayalan:3,join:[3,4],jpeg:2,json:[1,2,3,8],just:[2,4,5,6],kamushaden:3,keep:1,kei:[1,2],kelvintaywl:3,kind:[1,2],kit:2,know:[1,5],kt5356:3,larg:[3,8],later:1,left:2,let:[0,1,2,5],level:1,leverag:6,licens:[0,3],lighter:8,like:[2,3,4,8],limit:3,linear:2,link:[1,3],lint:3,list:[3,8],liter:2,live:3,load:3,local:3,log:[1,3],login:3,longer:2,look:[2,5],love:5,mai:[2,3,4],maintain:0,make:[1,2,5],mani:[2,5],mayb:[2,4],mean:2,mechan:5,mention:1,messag:3,method:[1,2,4,8],might:2,mode:8,modular:2,more:[2,4,5,8],most:[2,8],move:3,mpdm:3,mpim:[3,4],mrkdwn:2,msg:8,much:5,must:[1,2],myapp:1,myprivatechannel:4,name:[2,3,4],nearli:2,need:[1,2,5,8],nefari:1,never:1,newest:4,node:1,non:3,note:[2,4,5],now:3,number:2,oauth_scop:1,object:[2,3],off:2,onc:[1,2],one:[1,2,4,5],onli:[2,4,8],open:[2,3,4],option:[2,8],order:1,origin:2,other:[3,5,8],our:[1,4,5,6],out:[1,2],over:[2,3,8],overlook:2,own:2,packag:[2,3],page:[1,2],param:[1,3],paramet:[2,3,4],parent:[2,3],part:5,parti:6,particularli:5,pass:[1,2,3,4,8],payload:8,peopl:2,pep:3,per:2,perform:1,period:2,permiss:1,persist:1,pexel:2,photo:2,ping:3,pip:6,place:[2,4],platform:[0,5],playground:2,pleas:[1,5],png:2,point:[2,3],poop:5,possibl:[1,6],post:[1,2,8],post_instal:1,postephemer:2,postmessag:2,practic:[2,8],pre_instal:1,prefer:[6,8],prevent:3,primari:2,print:[2,8],privat:[3,8],private_channel:4,problem:8,program:5,progress:4,project:[5,6],properli:3,properti:[2,3],propos:5,prototyp:2,proudli:0,provid:[1,2,4,5,8],proxi:3,public_channel:4,publish:1,pull:[1,5,6],purpos:2,push:8,pypi:6,python:[1,2,3],queri:1,quickli:[2,6],rate:3,rather:[3,5,8],read:[1,2],readm:3,rebuild:5,receiv:8,recommend:[1,5,6,8],reconnect:[3,8],redirect:1,refer:2,reflect:2,refresh:3,refus:1,regular:2,reinstal:1,releg:2,remov:3,repli:[3,8],reply_broadcast:[2,8],report:6,repositori:1,request:[1,2,3,4,5,6,8],requir:[1,2,3],respond:2,respons:[1,2,3],responseparseerror:3,restructuredtext:5,result:[2,3],resum:4,retri:2,retriev:1,review:[1,2],roach:3,room:2,rout:1,rowdi:2,rst:[3,5],rtm:3,rtm_connect:[3,8],rtm_read:8,rtm_send_messag:[3,8],run:[5,6],runtim:1,safe:1,sai:2,same:[1,2,4,8],sampart:3,sampl:5,save:[1,2],schlueter:3,scope:[1,2],search:3,second:2,secret:1,section:[1,2,5],see:[1,2,4,8],semver:3,send_messag:3,send_slack_messag:2,sent:2,separ:8,server:[1,3,8],servic:6,set:[1,2,3,4,8],setup:1,share:[1,4],should:1,show:2,sign:1,simpl:2,simpler:2,simpli:[2,8],sinc:1,singl:2,slack:[1,2,3,4,8],slack_api_token:[2,4,8],slack_bot_scop:1,slack_bot_token:1,slack_client_id:1,slack_client_secret:1,slack_token:[2,4,8],slack_user_token:1,slackapi:[3,5,6],slackclient:[1,2,3,4,6,8],slackclienterror:3,slash:1,sleep:[2,8],slightli:2,social:2,socket:8,some:[2,3],someth:5,sometim:2,sorri:3,sourc:6,special:2,specif:[1,2],specifi:2,sphinx:5,src:5,star:2,start:[1,2,5],stasfilin:3,state:2,statu:2,step:5,store:1,str:2,string:[1,3,4],stuck:[2,6],submit:5,subsequ:4,succeed:[1,2],success:1,successfulli:[2,8],support:[3,8],sure:[2,5],tada:2,take:[1,2,5],team:[0,1,3,6,8],techniqu:1,tell:[2,3,8],test:[1,2,3,5,8],text:[2,8],than:[2,3,8],thank:3,thei:[2,3,5],them:[1,2],thi:[1,2,4,5,8],thing:[2,3,4,5,6],think:5,thinking_very_much:2,third:6,those:2,thought:5,thread:[3,8],thread_t:2,through:6,thumbsup:2,tidi:3,time:2,timelin:2,timeout:3,timestamp:2,timfeirg:3,titl:2,togeth:2,token:[2,3,4,8],too:2,tool:[0,2],top:1,torrenc:2,total:5,tox:[3,5],traceback:3,tracker:[5,6],travi:3,two:[1,5],txt:6,type:[2,3,4,8],typo:[2,3],u023becgf:8,u0xxxxxxx:2,u2345678901:4,u3456789012:4,unifi:4,uniqu:2,unit:5,until:2,unus:3,updat:[3,5],upon:1,url:[1,3,8],urllib:3,usag:[1,8],use:[1,2,3,5],used:[1,2,8],user:[1,2,3,4,5,8],using:[1,3,5,6,8],valid:1,variabl:1,varieti:1,veri:2,verif:1,version:3,virtualenv:5,visibl:2,visit:6,vote:2,vscode:3,w1234567890:4,wai:[1,2,6,8],wait:[2,5],want:[1,2,5],web:[0,1,3,5,8],webhook:1,websocket:[3,8],welcom:8,well:[1,5],what:[1,2,4],when:[1,2,3],where:[1,2,3],whether:[2,6],which:[1,2,3],whole:2,with_team_st:8,within:[1,5],without:8,won:2,word:2,work:[2,3,4,5,6],workflow:6,workspac:4,wrapper:[2,5],write:5,written:5,xoxb:1,yield:1,you:[0,1,2,3,4,5,6,8],your:[0,1,2,4,5,6,8],yourself:5},titles:["About","Tokens & Authentication","Basic Usage","Changelog","Conversations API","Frequently Asked Questions","Slack Developer Kit for Python","<no title>","Real Time Messaging (RTM)"],titleterms:{"public":4,Adding:2,The:1,about:[0,4],api:[2,4,8],app:1,ask:5,authent:1,basic:2,bug:5,care:5,changelog:3,channel:[2,4],compil:5,connect:8,content:2,contribut:5,convers:4,creat:[2,4],custom:2,data:1,delet:2,develop:[0,5,6],direct:4,document:5,emoji:2,even:5,event:8,featur:5,file:2,flow:1,found:5,frequent:5,get:[2,4,6],handl:1,hei:5,help:6,how:5,info:2,inform:4,instal:6,join:2,kit:[0,5,6],layout:2,leav:[2,4],like:5,limit:2,list:[2,4],member:[2,4],messag:[2,4,8],miss:5,multi:4,oauth:1,omg:5,other:1,person:4,privat:4,python:[0,5,6],question:5,rate:2,reaction:2,real:8,remov:2,repli:2,requir:6,rtm:8,send:[2,8],sensit:1,should:5,singl:1,slack:[0,5,6],start:8,team:2,thread:2,time:8,token:1,updat:2,upload:2,usag:2,via:8,web:2,what:5,why:5,workspac:1}}) \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/requirements.txt b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/requirements.txt new file mode 100644 index 0000000000..cd8c47d3d7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/requirements.txt @@ -0,0 +1,3 @@ +--index-url https://pypi.python.org/simple/ + +-e . diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.cfg b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.cfg new file mode 100644 index 0000000000..24cf69da18 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.cfg @@ -0,0 +1,5 @@ +[metadata] +description-file = README.rst + +[bdist_wheel] +universal=1 \ No newline at end of file diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.py new file mode 100644 index 0000000000..c6e3402738 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/setup.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +from setuptools import setup, find_packages +import io +import os +import re + + +def read(*names, **kwargs): + with io.open( + os.path.join(os.path.dirname(__file__), *names), + encoding=kwargs.get("encoding", "utf8") + ) as fp: + return fp.read() + + +long_description = read('README.rst') + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +setup(name='slackclient', + version=find_version('slackclient', 'version.py'), + description='Slack API clients for Web API and RTM API', + long_description=long_description, + url='https://github.com/slackapi/python-slackclient', + author='Slack Technologies, Inc.', + author_email='opensource@slack.com', + license='MIT', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Topic :: Communications :: Chat', + 'Topic :: System :: Networking', + 'Topic :: Office/Business', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + keywords='slack slack-web slack-rtm chat chatbots bots chatops', + packages=find_packages(exclude=['docs', 'docs-src', 'tests']), + install_requires=[ + 'websocket-client >=0.35, <0.55.0', + 'requests >=2.11, <3.0a0', + 'six >=1.10, <2.0a0', + ]) diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/__init__.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/__init__.py new file mode 100644 index 0000000000..776c7ba3fe --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/__init__.py @@ -0,0 +1 @@ +from .client import SlackClient # noqa diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/channel.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/channel.py new file mode 100644 index 0000000000..ad6e8b9ce7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/channel.py @@ -0,0 +1,46 @@ +class Channel(object): + ''' + A Channel represents a public or private Slack Channel instance + ''' + def __init__(self, server, name, channel_id, members=None): + self.server = server + self.name = name + self.id = channel_id + self.members = [] if members is None else members + + def __eq__(self, compare_str): + if self.name == compare_str or "#" + self.name == compare_str or self.id == compare_str: + return True + else: + return False + + def __hash__(self): + return hash(self.id) + + def __str__(self): + data = "" + for key in list(self.__dict__.keys()): + data += "{0} : {1}\n".format(key, str(self.__dict__[key])[:40]) + return data + + def __repr__(self): + return self.__str__() + + def send_message(self, message, thread=None, reply_broadcast=False): + ''' + Sends a message to a this Channel. + + Include the parent message's thread_ts value in `thread` + to send to a thread. + + :Args: + message (message) - the string you'd like to send to the channel + thread (str or None) - the parent message ID, if sending to a + thread + reply_broadcast (bool) - if messaging a thread, whether to + also send the message back to the channel + + :Returns: + None + ''' + self.server.rtm_send_message(self.id, message, thread, reply_broadcast) diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/client.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/client.py new file mode 100644 index 0000000000..0e3dfde5e7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/client.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +# mostly a proxy object to abstract how some of this works + +import json +import logging +import time + +from .server import Server +from .exceptions import ParseResponseError, TokenRefreshError + + +LOG = logging.getLogger(__name__) + + +class SlackClient(object): + """ + The SlackClient makes API Calls to the `Slack Web API `_ as well as + managing connections to the `Real-time Messaging API via websocket `_ + + It also manages some of the Client state for Channels that the associated token (User or Bot) + is associated with. + + For more information, check out the `Slack API Docs `_ + """ + + def __init__( + self, + token=None, + refresh_token=None, + token_update_callback=None, + client_id=None, + client_secret=None, + proxies=None, + **kwargs + ): + """ + Init: + :Args: + token (str): Your Slack Authentication token. You can find or generate a test token + `here `_ + Note: Be `careful with your token `_ + proxies (dict): Proxies to use when create websocket or api calls, + declare http and websocket proxies using {'http': 'http://127.0.0.1'}, + and https proxy using {'https': 'https://127.0.0.1:443'} + refresh_token (str): Your Slack app's refresh token. This token is used to + update your app's OAuth access token + client_id (str): Your app's Client ID + client_secret (srt): Your app's Client Secret (Used for OAuth requests) + refresh_callback (function): Your application's function for updating Slack + OAuth tokens inside your data store + """ + + self.client_id = client_id + self.client_secret = client_secret + self.refresh_token = refresh_token + self.token_update_callback = token_update_callback + self.token = token + self.access_token_expires_at = 0 + + if refresh_token: + if callable(token_update_callback): + self.server = Server( + connect=False, + proxies=proxies, + refresh_token=refresh_token, + client_id=client_id, + client_secret=client_secret, + token_update_callback=token_update_callback, + ) + else: + raise TokenRefreshError( + "Token refresh callback function is required when using refresh token." + ) + else: + # Slack app configs + self.server = Server(token=token, connect=False, proxies=proxies) + + def refresh_access_token(self): + """ + Refresh the client's OAUth access tokens + https://api.slack.com/docs/rotating-and-refreshing-credentials + """ + post_data = { + "refresh_token": self.refresh_token, + "grant_type": "refresh_token", + "client_id": self.client_id, + "client_secret": self.client_secret, + } + response = self.server.api_requester.post_http_request( + self.refresh_token, api_method="oauth.access", post_data=post_data + ) + response_json = json.loads(response.text) + + # If Slack returned an updated access token, update the client, otherwise + # raise TokenRefreshError exception with the error returned from the API + if response_json["ok"]: + # Update the client's access token and expiration timestamp + self.team_id = response_json["team_id"] + # TODO: Minimize the numer of places token is stored. + self.token = response_json["access_token"] + self.server.token = response_json["access_token"] + + # Update the token expiration timestamp + current_ts = int(time.time()) + expires_at = int(current_ts + response_json["expires_in"]) + self.access_token_expires_at = expires_at + # Call the developer's token update callback + update_args = { + "enterprise_id": response_json["enterprise_id"], + "team_id": response_json["team_id"], + "access_token": response_json["access_token"], + "expires_in": response_json["expires_in"], + } + self.token_update_callback(update_args) + else: + raise TokenRefreshError("Token refresh failed") + + def append_user_agent(self, name, version): + self.server.append_user_agent(name, version) + + def rtm_connect(self, with_team_state=True, **kwargs): + """ + Connects to the RTM Websocket + + :Args: + with_team_state (bool): Connect via `rtm.start` to pull workspace state information. + `False` connects via `rtm.connect`, which is lighter weight and better for very large + teams. + + :Returns: + False on exceptions + """ + + if self.refresh_token: + raise TokenRefreshError( + "Workspace tokens may not be used to connect to the RTM API." + ) + + try: + self.server.rtm_connect(use_rtm_start=with_team_state, **kwargs) + return self.server.connected + except Exception: + LOG.warn("Failed RTM connect", exc_info=True) + return False + + def api_call(self, method, timeout=None, **kwargs): + """ + Call the Slack Web API as documented here: https://api.slack.com/web + + :Args: + method (str): The API Method to call. See + `the full list here `_ + :Kwargs: + (optional) kwargs: any arguments passed here will be bundled and sent to the api + requester as post_data and will be passed along to the API. + + Example:: + + sc.api_call( + "channels.setPurpose", + channel="CABC12345", + purpose="Writing some code!" + ) + + :Returns: + str -- returns the text of the HTTP response. + + Examples:: + + u'{"ok":true,"purpose":"Testing bots"}' + or + u'{"ok":false,"error":"channel_not_found"}' + + See here for more information on responses: https://api.slack.com/web + """ + # Check for missing or expired access token before submitting the request + if method != "oauth.access" and self.refresh_token: + current_ts = int(time.time()) + token_is_expired = current_ts > self.access_token_expires_at + if token_is_expired or self.token is None: + self.refresh_access_token() + + response_body = self.server.api_call( + self.token, request=method, timeout=timeout, **kwargs + ) + + # Attempt to parse the response as JSON + try: + result = json.loads(response_body) + except ValueError as json_decode_error: + raise ParseResponseError(response_body, json_decode_error) + response_json = json.loads(response_body) + + if result.get("ok", False): + if method == "im.open": + self.server.attach_channel(kwargs["user"], result["channel"]["id"]) + elif method in ("mpim.open", "groups.create", "groups.createchild"): + self.server.parse_channel_data([result["group"]]) + elif method in ("channels.create", "channels.join"): + self.server.parse_channel_data([result["channel"]]) + else: + # if the API request returns an invalid_auth error, refresh the token and try again + if ( + self.refresh_token + and "error" in response_json + and response_json["error"] == "invalid_auth" + ): + self.refresh_access_token() + # If token refresh was successful, retry the original API request + return self.api_call(method, timeout, **kwargs) + return result + + def rtm_read(self): + """ + Reads from the RTM Websocket stream then calls `self.process_changes(item)` for each line + in the returned data. + + Multiple events may be returned, always returns a list [], which is empty if there are no + incoming messages. + + :Args: + None + + :Returns: + data (json) - The server response. For example:: + + [{u'presence': u'active', u'type': u'presence_change', u'user': u'UABC1234'}] + + :Raises: + SlackNotConnected if self.server is not defined. + """ + # in the future, this should handle some events internally i.e. channel + # creation + if self.server: + json_data = self.server.websocket_safe_read() + data = [] + if json_data != "": + for d in json_data.split("\n"): + data.append(json.loads(d)) + for item in data: + self.process_changes(item) + return data + else: + raise SlackNotConnected + + def rtm_send_message(self, channel, message, thread=None, reply_broadcast=None): + """ + Sends a message to a given channel. + + :Args: + channel (str) - the string identifier for a channel or channel name (e.g. 'C1234ABC', + 'bot-test' or '#bot-test') + message (message) - the string you'd like to send to the channel + thread (str or None) - the parent message ID, if sending to a + thread + reply_broadcast (bool) - if messaging a thread, whether to + also send the message back to the channel + + :Returns: + None + + """ + # The `channel` argument can be a channel name or an ID. At first its assumed to be a + # name and an attempt is made to find the ID in the workspace state cache. + # If that lookup fails, the argument is used as the channel ID. + found_channel = self.server.channels.find(channel) + channel_id = found_channel.id if found_channel else channel + return self.server.rtm_send_message( + channel_id, message, thread, reply_broadcast + ) + + def process_changes(self, data): + """ + Internal method which processes RTM events and modifies the local data store + accordingly. + + Stores new channels when joining a group (Multi-party DM), IM (DM) or channel. + + Stores user data on a team join event. + """ + if "type" in data.keys(): + if data["type"] in ("channel_created", "group_joined"): + channel = data["channel"] + self.server.attach_channel(channel["name"], channel["id"], []) + if data["type"] == "im_created": + channel = data["channel"] + self.server.attach_channel(channel["user"], channel["id"], []) + if data["type"] == "team_join": + user = data["user"] + self.server.parse_user_data([user]) + pass + + +class SlackNotConnected(Exception): + pass diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/exceptions.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/exceptions.py new file mode 100644 index 0000000000..d8674567ce --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/exceptions.py @@ -0,0 +1,29 @@ +class SlackClientError(Exception): + """ + Base exception for all errors raised by the SlackClient library + """ + def __init__(self, msg=None): + if msg is None: + # default error message + msg = "An error occurred in the SlackClient library" + super(SlackClientError, self).__init__(msg) + + +class ParseResponseError(SlackClientError, ValueError): + """ + Error raised when responses to Web API methods cannot be parsed as valid JSON + """ + def __init__(self, response_body, original_exception): + super(ParseResponseError, self).__init__( + "Slack API response body could not be parsed: {0}. Original exception: {1}".format( + response_body, original_exception + ) + ) + self.response_body = response_body + self.original_exception = original_exception + + +class TokenRefreshError(SlackClientError): + """ + This exception is rasied when a token related error occurs within the client + """ diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/im.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/im.py new file mode 100644 index 0000000000..b615925354 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/im.py @@ -0,0 +1,40 @@ +class Im(object): + ''' + IMs represent direct message channels between two users on Slack. + ''' + def __init__(self, server, user, im_id): + self.server = server + self.user = user + self.id = im_id + + def __eq__(self, compare_str): + if self.id == compare_str or self.user == compare_str: + return True + else: + return False + + def __hash__(self): + return hash(self.id) + + def __str__(self): + data = "" + for key in list(self.__dict__.keys()): + if key != "server": + data += "{0} : {1}\n".format(key, str(self.__dict__[key])[:40]) + return data + + def __repr__(self): + return self.__str__() + + def send_message(self, message): + ''' + Sends a message to a this IM (or DM depending on your preferred terminology). + + :Args: + message (message) - the string you'd like to send to the IM + + :Returns: + None + ''' + message_json = {"type": "message", "channel": self.id, "text": message} + self.server.send_to_websocket(message_json) diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/server.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/server.py new file mode 100644 index 0000000000..04e6537da7 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/slackclient/server.py @@ -0,0 +1,377 @@ +from .channel import Channel +from .exceptions import SlackClientError +from .slackrequest import SlackRequest +from .user import User +from .util import SearchList, SearchDict + +import json +import logging +import time +import random + +from requests.packages.urllib3.util.url import parse_url +from ssl import SSLError +from websocket import create_connection +from websocket._exceptions import WebSocketConnectionClosedException + + +class Server(object): + """ + The Server object owns the websocket connection and all attached channel information. + """ + + def __init__(self, token=None, connect=True, proxies=None, **kwargs): + # Slack app configs + self.token = token + + # api configs + self.proxies = proxies + + # HTTP Request handler + self.api_requester = SlackRequest(proxies=proxies) + + # Workspace metadata + self.username = None + self.domain = None + self.login_data = None + self.users = SearchDict() + self.channels = SearchList() + + # RTM configs + self.websocket = None + self.ws_url = None + self.connected = False + self.auto_reconnect = False + self.last_connected_at = 0 + self.reconnect_count = 0 + self.rtm_connect_retries = 0 + + # Connect to RTM on load + if connect: + self.rtm_connect() + + def __eq__(self, compare_str): + if compare_str == self.domain or compare_str == self.token: + return True + else: + return False + + def __hash__(self): + return hash(self.token) + + def __str__(self): + """ + Example Output:: + + username : None + domain : None + websocket : None + users : [] + login_data : None + api_requester : 0: + # Back off after the the first attempt + backoff_offset_multiplier = random.randint(1, 4) + retry_timeout = ( + backoff_offset_multiplier * recon_count * recon_count + ) + logging.debug("Reconnecting in %d seconds", retry_timeout) + + time.sleep(retry_timeout) + self.reconnect_count += 1 + else: + self.reconnect_count = 0 + + reply = self.api_requester.do( + self.token, connect_method, post_data=kwargs, timeout=timeout + ) + + if reply.status_code != 200: + if self.rtm_connect_retries < 5 and reply.status_code == 429: + self.rtm_connect_retries += 1 + retry_after = int(reply.headers.get("retry-after", 120)) + logging.debug( + "HTTP 429: Rate limited. Retrying in %d seconds", retry_after + ) + time.sleep(retry_after) + self.rtm_connect( + reconnect=reconnect, + timeout=timeout, + use_rtm_start=use_rtm_start, + **kwargs + ) + else: + raise SlackConnectionError( + "RTM connection attempt was rate limited 5 times." + ) + else: + self.rtm_connect_retries = 0 + login_data = reply.json() + if login_data["ok"]: + self.ws_url = login_data["url"] + self.connect_slack_websocket(self.ws_url) + if not reconnect: + self.parse_slack_login_data(login_data, use_rtm_start) + else: + raise SlackLoginError(reply=reply) + + def parse_slack_login_data(self, login_data, use_rtm_start): + self.login_data = login_data + self.domain = self.login_data["team"]["domain"] + self.username = self.login_data["self"]["name"] + + # if the connection was made via rtm.start, update the server's state + if use_rtm_start: + self.parse_channel_data(login_data["channels"]) + self.parse_channel_data(login_data["groups"]) + self.parse_user_data(login_data["users"]) + self.parse_channel_data(login_data["ims"]) + + def connect_slack_websocket(self, ws_url): + """Uses http proxy if available""" + if self.proxies and "http" in self.proxies: + parts = parse_url(self.proxies["http"]) + proxy_host, proxy_port = parts.host, parts.port + auth = parts.auth + proxy_auth = auth and auth.split(":") + else: + proxy_auth, proxy_port, proxy_host = None, None, None + + try: + self.websocket = create_connection( + ws_url, + http_proxy_host=proxy_host, + http_proxy_port=proxy_port, + http_proxy_auth=proxy_auth, + ) + self.connected = True + self.last_connected_at = time.time() + logging.debug("RTM connected") + self.websocket.sock.setblocking(0) + except Exception as e: + self.connected = False + raise SlackConnectionError(message=str(e)) + + def parse_channel_data(self, channel_data): + for channel in channel_data: + if "name" not in channel: + channel["name"] = channel["id"] + if "members" not in channel: + channel["members"] = [] + self.attach_channel(channel["name"], channel["id"], channel["members"]) + + def parse_user_data(self, user_data): + for user in user_data: + if "tz" not in user: + user["tz"] = "unknown" + if "real_name" not in user: + user["real_name"] = user["name"] + if "email" not in user["profile"]: + user["profile"]["email"] = "" + self.attach_user( + user["name"], + user["id"], + user["real_name"], + user["tz"], + user["profile"]["email"], + ) + + def send_to_websocket(self, data): + """ + Send a JSON message directly to the websocket. See + `RTM documentation =2.4.0,<2.6 +pytest-mock +responses diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/conftest.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/conftest.py new file mode 100644 index 0000000000..9b2571ad75 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/conftest.py @@ -0,0 +1,39 @@ +import pytest +import requests +from slackclient.channel import Channel +from slackclient.server import Server +from slackclient.client import SlackClient + + +# This is so that tests work on Travis for python 2.6, it's really hacky, but expedient +def get_unverified_post(): + requests_post = requests.post + + def unverified_post(*args, **kwargs): + # don't throw SSL errors plz + kwargs['verify'] = False + return requests_post(*args, **kwargs) + + return unverified_post + + +requests.post = get_unverified_post() + + +@pytest.fixture +def server(monkeypatch): + my_server = Server(token='xoxp-1234123412341234-12341234-1234', connect=False) + return my_server + + +@pytest.fixture +def slackclient(): + my_slackclient = SlackClient('xoxp-1234123412341234-12341234-1234') + return my_slackclient + + +@pytest.fixture +def channel(server): + my_channel = Channel(server, "somechannel", "C12341234", ["user"]) + return my_channel + diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/channel.created.json b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/channel.created.json new file mode 100644 index 0000000000..9ccbffaebc --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/channel.created.json @@ -0,0 +1,9 @@ +{ + "type": "channel_created", + "channel": { + "id": "C024BE91L", + "name": "fun", + "created": 1360782804, + "creator": "U024BE7LH" + } +} diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/im.created.json b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/im.created.json new file mode 100644 index 0000000000..8f51519f1e --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/im.created.json @@ -0,0 +1,9 @@ +{ + "type": "im_created", + "user": "U024BE7LH", + "channel": { + "id": "D024BE91L", + "user": "U123BL234", + "created": 1360782804 + } +} diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/rtm.start.json b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/rtm.start.json new file mode 100644 index 0000000000..a0696eda93 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/rtm.start.json @@ -0,0 +1,321 @@ +{ + "ok": true, + "self": { + "id": "U10CX1234", + "name": "fakeuser", + "prefs": { + "highlight_words": "", + "user_colors": "", + "color_names_in_list": true, + "growls_enabled": true, + "tz": "America\/Los_Angeles", + "push_dm_alert": true, + "push_mention_alert": true, + "push_everything": true, + "push_idle_wait": 2, + "push_sound": "b2.mp3", + "push_loud_channels": "", + "push_mention_channels": "", + "push_loud_channels_set": "", + "email_alerts": "instant", + "email_alerts_sleep_until": 0, + "email_misc": true, + "email_weekly": true, + "welcome_message_hidden": false, + "all_channels_loud": true, + "loud_channels": "", + "never_channels": "", + "loud_channels_set": "", + "show_member_presence": true, + "search_sort": "timestamp", + "expand_inline_imgs": true, + "expand_internal_inline_imgs": true, + "expand_snippets": false, + "posts_formatting_guide": true, + "seen_welcome_2": true, + "seen_ssb_prompt": false, + "search_only_my_channels": false, + "emoji_mode": "default", + "has_invited": false, + "has_uploaded": false, + "has_created_channel": false, + "search_exclude_channels": "", + "messages_theme": "default", + "webapp_spellcheck": true, + "no_joined_overlays": false, + "no_created_overlays": false, + "dropbox_enabled": false, + "seen_user_menu_tip_card": true, + "seen_team_menu_tip_card": true, + "seen_channel_menu_tip_card": true, + "seen_message_input_tip_card": true, + "seen_channels_tip_card": true, + "seen_domain_invite_reminder": false, + "seen_member_invite_reminder": false, + "seen_flexpane_tip_card": true, + "seen_search_input_tip_card": true, + "mute_sounds": false, + "arrow_history": false, + "tab_ui_return_selects": true, + "obey_inline_img_limit": true, + "new_msg_snd": "knock_brush.mp3", + "collapsible": false, + "collapsible_by_click": true, + "require_at": false, + "mac_ssb_bounce": "", + "mac_ssb_bullet": true, + "expand_non_media_attachments": true, + "show_typing": true, + "pagekeys_handled": true, + "last_snippet_type": "", + "display_real_names_override": 0, + "time24": false, + "enter_is_special_in_tbt": false, + "graphic_emoticons": false, + "convert_emoticons": true, + "autoplay_chat_sounds": true, + "ss_emojis": true, + "sidebar_behavior": "", + "mark_msgs_read_immediately": true, + "start_scroll_at_oldest": true, + "snippet_editor_wrap_long_lines": false, + "ls_disabled": false, + "sidebar_theme": "default", + "sidebar_theme_custom_values": "", + "f_key_search": false, + "k_key_omnibox": true, + "speak_growls": false, + "mac_speak_voice": "com.apple.speech.synthesis.voice.Alex", + "mac_speak_speed": 250, + "comma_key_prefs": false, + "at_channel_suppressed_channels": "", + "push_at_channel_suppressed_channels": "", + "prompted_for_email_disabling": false, + "full_text_extracts": false, + "no_text_in_notifications": false, + "muted_channels": "", + "no_macssb1_banner": true, + "no_winssb1_banner": false, + "privacy_policy_seen": true, + "search_exclude_bots": false, + "fuzzy_matching": false, + "load_lato_2": false, + "fuller_timestamps": false, + "last_seen_at_channel_warning": 0, + "enable_flexpane_rework": false, + "flex_resize_window": false, + "msg_preview": false, + "msg_preview_displaces": true, + "msg_preview_persistent": true, + "emoji_autocomplete_big": false, + "winssb_run_from_tray": true + }, + "created": 1421456475, + "manual_presence": "active" + }, + "team": { + "id": "T03CX4S34", + "name": "TESTteam, INC", + "email_domain": "", + "domain": "testteaminc", + "msg_edit_window_mins": -1, + "prefs": { + "default_channels": [ + "C01CX1234", + "C05BX1234" + ], + "msg_edit_window_mins": -1, + "allow_message_deletion": true, + "hide_referers": true, + "display_real_names": false, + "who_can_at_everyone": "regular", + "who_can_at_channel": "ra", + "warn_before_at_channel": "always", + "who_can_create_channels": "regular", + "who_can_archive_channels": "regular", + "who_can_create_groups": "ra", + "who_can_post_general": "ra", + "who_can_kick_channels": "admin", + "who_can_kick_groups": "regular", + "retention_type": 0, + "retention_duration": 0, + "group_retention_type": 0, + "group_retention_duration": 0, + "dm_retention_type": 0, + "dm_retention_duration": 0, + "require_at_for_mention": 0, + "compliance_export_start": 0 + }, + "icon": { + "image_34": "https:\/\/slack.global.ssl.fastly.net\/b3b7\/img\/avatars-teams\/ava_0025-34.png", + "image_44": "https:\/\/slack.global.ssl.fastly.net\/b3b7\/img\/avatars-teams\/ava_0025-44.png", + "image_68": "https:\/\/slack.global.ssl.fastly.net\/b3b7\/img\/avatars-teams\/ava_0025-68.png", + "image_88": "https:\/\/slack.global.ssl.fastly.net\/b3b7\/img\/avatars-teams\/ava_0025-88.png", + "image_102": "https:\/\/slack.global.ssl.fastly.net\/b3b7\/img\/avatars-teams\/ava_0025-102.png", + "image_132": "https:\/\/slack.global.ssl.fastly.net\/b3b7\/img\/avatars-teams\/ava_0025-132.png", + "image_default": true + }, + "over_storage_limit": false + }, + "latest_event_ts": "1426103085.000000", + "channels": [ + { + "id": "C01CX1234", + "name": "general", + "is_channel": true, + "created": 1421456475, + "creator": "U03CX4S38", + "is_archived": false, + "is_general": true, + "is_member": true, + "last_read": "0000000000.000000", + "latest": { + "type": "message", + "user": "U03CX4S38", + "text": "a", + "ts": "1425499421.000004" + }, + "unread_count": 0, + "unread_count_display": 0, + "members": [ + "U03CX4S38" + ], + "topic": { + "value": "", + "creator": "", + "last_set": 0 + }, + "purpose": { + "value": "This channel is for team-wide communication and announcements. All team members are in this channel.", + "creator": "", + "last_set": 0 + } + }, + { + "id": "C05BX1234", + "name": "random", + "is_channel": true, + "created": 1421456475, + "creator": "U03CX4S38", + "is_archived": false, + "is_general": false, + "is_member": true, + "last_read": "0000000000.000000", + "latest": null, + "unread_count": 0, + "unread_count_display": 0, + "members": [ + "U03CX4S38" + ], + "topic": { + "value": "", + "creator": "", + "last_set": 0 + }, + "purpose": { + "value": "A place for non-work banter, links, articles of interest, humor or anything else which you'd like concentrated in some place other than work-related channels.", + "creator": "", + "last_set": 0 + } + } + ], + "groups": [], + "ims": [ + { + "id": "D03CX4S3E", + "is_im": true, + "user": "USLACKBOT", + "created": 1421456475, + "last_read": "1425318850.000003", + "latest": { + "type": "message", + "user": "USLACKBOT", + "text": "To start, what is your first name?", + "ts": "1425318850.000003" + }, + "unread_count": 0, + "unread_count_display": 0, + "is_open": true + } + ], + "users": [ + { + "id": "U10CX1234", + "name": "fakeuser", + "deleted": false, + "status": null, + "color": "9f69e7", + "profile": { + "email": "fakeuser@example.com", + "image_24": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=24&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002-24.png", + "image_32": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=32&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002-32.png", + "image_48": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=48&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F272a%2Fimg%2Favatars%2Fava_0002-48.png", + "image_72": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=72&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002-72.png", + "image_192": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=192&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002.png" + }, + "is_admin": true, + "is_owner": true, + "is_primary_owner": true, + "is_restricted": false, + "is_ultra_restricted": false, + "is_bot": false, + "has_files": false, + "presence": "away" + }, + { + "id": "U10CX1235", + "name": "userwithoutemail", + "deleted": false, + "status": null, + "color": "9f69e7", + "profile": { + "image_24": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=24&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002-24.png", + "image_32": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=32&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002-32.png", + "image_48": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=48&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F272a%2Fimg%2Favatars%2Fava_0002-48.png", + "image_72": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=72&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002-72.png", + "image_192": "https:\/\/secure.gravatar.com\/avatar\/4f1bd7fd71e645fa19620504b4c0e3ba.jpg?s=192&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0002.png" + }, + "is_admin": true, + "is_owner": true, + "is_primary_owner": true, + "is_restricted": false, + "is_ultra_restricted": false, + "is_bot": false, + "has_files": false, + "presence": "away" + }, + { + "id": "USLACKBOT", + "name": "slackbot", + "deleted": false, + "status": null, + "color": "757575", + "real_name": "Slack Bot", + "tz": null, + "tz_label": "Pacific Daylight Time", + "tz_offset": -25200, + "profile": { + "first_name": "Slack", + "last_name": "Bot", + "image_24": "https:\/\/slack-assets2.s3-us-west-2.amazonaws.com\/10068\/img\/slackbot_24.png", + "image_32": "https:\/\/slack-assets2.s3-us-west-2.amazonaws.com\/10068\/img\/slackbot_32.png", + "image_48": "https:\/\/slack-assets2.s3-us-west-2.amazonaws.com\/10068\/img\/slackbot_48.png", + "image_72": "https:\/\/slack-assets2.s3-us-west-2.amazonaws.com\/10068\/img\/slackbot_72.png", + "image_192": "https:\/\/slack-assets2.s3-us-west-2.amazonaws.com\/10068\/img\/slackbot_192.png", + "real_name": "Slack Bot", + "real_name_normalized": "Slack Bot", + "email": null + }, + "is_admin": false, + "is_owner": false, + "is_primary_owner": false, + "is_restricted": false, + "is_ultra_restricted": false, + "is_bot": false, + "presence": "active" + } + ], + "bots": [], + "cache_version": "v5-dog", + "url": "wss:\/\/cerberus-xxxx.lb.slack-msgs.com\/websocket\/ifkp3MKfNXd6ftbrEGllwcHn" +} diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/slack_logo.png b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/data/slack_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..232a00cf139ae920dcf1872302c78cfba0f4eeab GIT binary patch literal 64759 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

WADXx>%L{u)LbD#*6&pC--)-+jo5i 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 ayZf7wd43yA8njhMQ&H4>R3>ln;(q{OhCDg| literal 0 HcmV?d00001 diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_channel.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_channel.py new file mode 100644 index 0000000000..88ea5a8561 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_channel.py @@ -0,0 +1,47 @@ +from slackclient.channel import Channel + + +def test_channel(channel): + assert type(channel) == Channel + + +def test_channel_eq(channel): + channel = Channel( + 'test-server', + 'test-channel', + 'C12345678', + ) + assert channel == 'test-channel' + assert channel == '#test-channel' + assert channel == 'C12345678' + assert 'C12345678' in str(channel) + assert 'C12345678' in "%r" % channel + assert (channel == 'foo') is False + + +def test_channel_is_hashable(channel): + channel = Channel( + 'test-server', + 'test-channel', + 'C12345678', + ) + channel_map = {channel: channel.id} + assert channel_map[channel] == 'C12345678' + assert (channel_map[channel] == 'foo') is False + + +def test_channel_send_message(channel, mocker, monkeypatch): + mock_server = mocker.Mock() + monkeypatch.setattr(channel, 'server', mock_server) + channel.send_message('hi') + mock_server.rtm_send_message.assert_called_with(channel.id, 'hi', None, False) + + +def test_channel_send_message_to_thread(channel, mocker, monkeypatch): + mock_server = mocker.Mock() + monkeypatch.setattr(channel, 'server', mock_server) + channel.send_message('hi', thread='123456.789') + mock_server.rtm_send_message.assert_called_with(channel.id, 'hi', '123456.789', False) + + channel.send_message('hi', thread='123456.789', reply_broadcast=True) + mock_server.rtm_send_message.assert_called_with(channel.id, 'hi', '123456.789', True) diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_server.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_server.py new file mode 100644 index 0000000000..10e9183467 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_server.py @@ -0,0 +1,236 @@ +import json +import pytest +import responses +import time +import urllib3 + +from mock import patch + +from slackclient.user import User +from slackclient.server import Server, SlackConnectionError +from slackclient.channel import Channel + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +@pytest.fixture +def rtm_start_fixture(): + file_login_data = open('tests/data/rtm.start.json', 'r').read() + json_login_data = json.loads(file_login_data) + return json_login_data + + +def test_server(): + server = Server(token="valid_token", connect=False) + assert type(server) == Server + + # The server eqs to a string, either the token or workspace domain + assert server.token == "valid_token" + + +def test_server_connect(rtm_start_fixture): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.start", + status=200, + json=rtm_start_fixture + ) + + Server(token="token", connect=True) + + for call in rsps.calls: + assert call.request.url in [ + "https://slack.com/api/rtm.start" + ] + + +def test_api_call_for_empty_slack_responses(server): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/chat.postMessage", + status=429, + headers={"Retry-After": "1"}, + ) + + response_received = server.api_call("token", "chat.postMessage") + chat_postMessage_response = rsps.calls[0].response + + assert chat_postMessage_response.text == "" + expected_response = { + "headers": {"Content-Type": "text/plain", "Retry-After": "1"} + } + assert json.loads(response_received) == expected_response + + +def test_server_is_hashable(server): + server_map = {server: server.token} + assert server_map[server] == 'xoxp-1234123412341234-12341234-1234' + assert (server_map[server] == 'foo') is False + + +@patch('time.sleep', return_value=None) +def test_rate_limiting(patched_time_sleep, server): + # Testing for rate limit retry headers + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.start", + status=429, + json={"ok": False}, + headers={'Retry-After': "1"} + ) + + with pytest.raises(SlackConnectionError) as e: + server.rtm_connect() + for call in rsps.calls: + assert call.response.status_code == 429 + assert e.message == "RTM connection attempt was rate limited 10 times." + + +def test_custom_agent(server): + server.append_user_agent("test agent", 1.0) + assert server.api_requester.custom_user_agent[0] == ['test agent', 1.0] + + +def test_server_parse_channel_data(server, rtm_start_fixture): + server.parse_channel_data(rtm_start_fixture["channels"]) + assert type(server.channels.find('general')) == Channel + + +def test_server_parse_user_data(server, rtm_start_fixture): + server.parse_user_data(rtm_start_fixture["users"]) + # Find user by Name + user_by_name = server.users.find('fakeuser') + assert type(user_by_name) == User + assert user_by_name == "fakeuser" + assert user_by_name != "someotheruser" + # Find user by ID + user_by_id = server.users.find('U10CX1234') + assert type(user_by_id) == User + assert user_by_id == "fakeuser" + assert user_by_id.email == 'fakeuser@example.com' + # Don't find invalid user + user_by_id = server.users.find('invaliduser') + assert user_by_id is None + + +def test_server_cant_connect(server): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.start", + status=403, + json={"ok": False} + ) + + with pytest.raises(SlackConnectionError) as e: + server.rtm_connect() + + +def test_reconnect_flag(server, rtm_start_fixture): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.start", + status=200, + json=rtm_start_fixture + ) + + server.rtm_connect(auto_reconnect=True) + assert server.auto_reconnect is True + + for call in rsps.calls: + assert call.request.url in [ + "https://slack.com/api/rtm.start" + ] + + +def test_rtm_reconnect(server, rtm_start_fixture): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.connect", + status=200, + json=rtm_start_fixture + ) + + server.rtm_connect(auto_reconnect=True, reconnect=True, use_rtm_start=False) + + for call in rsps.calls: + assert call.request.url in [ + "https://slack.com/api/rtm.connect" + ] + + +@patch('time.sleep', return_value=None) +def test_rtm_max_reconnect_timeout(patched_time_sleep, server, rtm_start_fixture): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.connect", + status=200, + json=rtm_start_fixture + ) + + server.reconnect_count = 4 + server.last_connected_at = time.time() + server.rtm_connect(auto_reconnect=True, reconnect=True, use_rtm_start=False) + + assert server.reconnect_count == 5 + + +def test_rtm_reconnect_timeout_recently_connected(server, rtm_start_fixture): + # If reconnected recently, server must wait to reconnect and increment the counter + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.connect", + status=200, + json=rtm_start_fixture + ) + + server.reconnect_count = 0 + server.last_connected_at = time.time() + server.rtm_connect(auto_reconnect=True, reconnect=True, use_rtm_start=False) + + assert server.reconnect_count == 1 + for call in rsps.calls: + assert call.request.url in [ + "https://slack.com/api/rtm.connect" + ] + + +def test_rtm_reconnect_timeout_not_recently_connected(server, rtm_start_fixture): + # If reconnecting after 3 minutes since last reconnect, reset counter and connect without wait + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/rtm.connect", + status=200, + json=rtm_start_fixture + ) + + server.reconnect_count = 1 + server.last_connected_at = time.time() - 180 + server.rtm_connect(auto_reconnect=True, reconnect=True, use_rtm_start=False) + + assert server.reconnect_count == 0 + for call in rsps.calls: + assert call.request.url in [ + "https://slack.com/api/rtm.connect" + ] + + +def test_max_rtm_reconnects(server, monkeypatch): + monkeypatch.setattr("time.sleep", None) + with pytest.raises(SlackConnectionError) as e: + server.reconnect_count = 5 + server.rtm_connect(auto_reconnect=True, reconnect=True, use_rtm_start=False) + assert e.message == "RTM connection failed, reached max reconnects." + + +@pytest.mark.xfail +def test_server_ping(server, monkeypatch): + monkeypatch.setattr("websocket.create_connection", lambda: True) + reply = server.ping() diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackclient.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackclient.py new file mode 100644 index 0000000000..7eaeacad56 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackclient.py @@ -0,0 +1,295 @@ +import json +import pytest +from requests.exceptions import ProxyError +import responses +from slackclient.exceptions import TokenRefreshError +from slackclient.channel import Channel +from slackclient.client import SlackClient +from slackclient.server import SlackConnectionError + + +@pytest.fixture +def channel_created_fixture(): + file_channel_created_data = open("tests/data/channel.created.json", "r").read() + json_channel_created_data = json.loads(file_channel_created_data) + return json_channel_created_data + + +@pytest.fixture +def im_created_fixture(): + file_channel_created_data = open("tests/data/im.created.json", "r").read() + json_channel_created_data = json.loads(file_channel_created_data) + return json_channel_created_data + + +def test_proxy(): + proxies = {"http": "some-bad-proxy", "https": "some-bad-proxy"} + client = SlackClient("xoxp-1234123412341234-12341234-1234", proxies=proxies) + server = client.server + + assert server.proxies == proxies + + with pytest.raises(ProxyError): + server.rtm_connect() + + with pytest.raises(SlackConnectionError): + server.connect_slack_websocket( + "wss://mpmulti-xw58.slack-msgs.com/websocket/bad-token" + ) + + api_requester = server.api_requester + assert api_requester.proxies == proxies + with pytest.raises(ProxyError): + api_requester.do("xoxp-1234123412341234-12341234-1234", request="channels.list") + + +def test_SlackClient(slackclient): + assert type(slackclient) == SlackClient + + +def test_custom_user_agent(slackclient): + slackclient.append_user_agent("customua", "1.0.0") + assert "customua" in slackclient.server.api_requester.get_user_agent() + + +def test_SlackClient_process_changes( + slackclient, channel_created_fixture, im_created_fixture +): + slackclient.process_changes(channel_created_fixture) + assert type(slackclient.server.channels.find("fun")) == Channel + slackclient.process_changes(im_created_fixture) + assert type(slackclient.server.channels.find("U123BL234")) == Channel + + +def test_api_not_ok(slackclient): + # Testing for rate limit retry headers + client = SlackClient("xoxp-1234123412341234-12341234-1234") + + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/im.open", + status=200, + json={"ok": False, "error": "invalid_auth"}, + headers={}, + ) + + client.api_call("im.open", user="UXXXX") + + for call in rsps.calls: + assert call.response.status_code == 200 + assert call.request.url in ["https://slack.com/api/im.open"] + + +def test_im_open(slackclient): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/im.open", + status=200, + json={"ok": True, "channel": {"id": "CXXXXXX"}}, + headers={}, + ) + + slackclient.api_call("im.open", user="UXXXX") + + for call in rsps.calls: + assert call.response.status_code == 200 + assert call.request.url in ["https://slack.com/api/im.open"] + + +def test_channel_join(slackclient): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/channels.join", + status=200, + json={ + "ok": True, + "channel": { + "id": "CXXXX", + "name": "test", + "members": ("U0G9QF9C6", "U1QNSQB9U"), + }, + }, + ) + + slackclient.api_call("channels.join", channel="CXXXX") + + for call in rsps.calls: + assert call.response.status_code == 200 + assert call.request.url in ["https://slack.com/api/channels.join"] + response_json = call.response.json() + assert response_json["ok"] is True + + +def test_noncallable_refresh_callback(): + with pytest.raises(TokenRefreshError): + SlackClient( + client_id="12345", + client_secret="12345", + refresh_token="refresh_token", + token_update_callback="THIS IS A STRING, NOT A CALLABLE METHOD", + ) + + +def test_no_RTM_with_workspace_tokens(): + def token_update_callback(update_data): + return update_data + + with pytest.raises(TokenRefreshError): + sc = SlackClient( + client_id="12345", + client_secret="12345", + refresh_token="refresh_token", + token_update_callback=token_update_callback, + ) + + sc.rtm_connect() + + +def test_token_refresh_on_initial_api_request(): + # Client should fetch and append an access token on the first API request + + # When the token is refreshed, the client will call this callback + access_token = "xoxa-2-abcdef" + client_args = {} + + def token_update_callback(update_data): + client_args[update_data["team_id"]] = update_data + + sc = SlackClient( + client_id="12345", + client_secret="12345", + refresh_token="refresh_token", + token_update_callback=token_update_callback, + ) + + # The client starts out with an empty token + assert sc.token is None + + # Mock both the main API request and the token refresh request + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/auth.test", + status=200, + json={"ok": True}, + ) + + rsps.add( + responses.POST, + "https://slack.com/api/oauth.access", + status=200, + json={ + "ok": True, + "access_token": access_token, + "token_type": "app", + "expires_in": 3600, + "team_id": "T2U81E2FP", + "enterprise_id": "T2U81ELK", + }, + ) + + # Calling the API for the first time will trigger a token refresh + sc.api_call("auth.test") + + # Store the calls in order + calls = {} + for index, call in enumerate(rsps.calls): + calls[index] = {"url": call.request.url} + + # After the initial call, the refresh method will update the client's token, + # then the callback will update client_args + assert sc.token == access_token + assert client_args["T2U81E2FP"]["access_token"] == access_token + + # Verify that the client first tried to call the API, refreshed the token, then retried + assert calls[0]["url"] == "https://slack.com/api/oauth.access" + assert calls[1]["url"] == "https://slack.com/api/auth.test" + + +def test_token_refresh_failed(): + # Client should raise TokenRefreshError is token refresh returns error + def token_update_callback(update_data): + return update_data + + sc = SlackClient( + client_id="12345", + client_secret="12345", + refresh_token="refresh_token", + token_update_callback=token_update_callback, + ) + + with pytest.raises(TokenRefreshError): + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/channels.list", + status=200, + json={"ok": False, "error": "invalid_auth"}, + ) + + rsps.add( + responses.POST, + "https://slack.com/api/oauth.access", + status=200, + json={"ok": False, "error": "invalid_auth"}, + ) + + sc.api_call("channels.list") + + +def test_token_refresh_on_expired_token(): + # Client should fetch and append an access token on the first API request + + # When the token is refreshed, the client will call this callback + client_args = {} + + def token_update_callback(update_data): + client_args[update_data["team_id"]] = update_data + + sc = SlackClient( + client_id="12345", + client_secret="12345", + refresh_token="refresh_token", + token_update_callback=token_update_callback, + ) + + # Set the token TTL to some time in the past + sc.access_token_expires_at = 0 + + # Mock both the main API request and the token refresh request + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + "https://slack.com/api/auth.test", + status=200, + json={"ok": True}, + ) + + rsps.add( + responses.POST, + "https://slack.com/api/oauth.access", + status=200, + json={ + "ok": True, + "access_token": "xoxa-2-abcdef", + "token_type": "app", + "expires_in": 3600, + "team_id": "T2U81E2FP", + "enterprise_id": "T2U81ELK", + }, + ) + + # Calling the API for the first time will trigger a token refresh + sc.api_call("auth.test") + + # Store the calls in order + calls = {} + for index, call in enumerate(rsps.calls): + calls[index] = {"url": call.request.url} + + # Verify that the client first fetches the token, then submits the request + assert calls[0]["url"] == "https://slack.com/api/oauth.access" + assert calls[1]["url"] == "https://slack.com/api/auth.test" diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackrequest.py b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackrequest.py new file mode 100644 index 0000000000..0c49915f88 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tests/test_slackrequest.py @@ -0,0 +1,114 @@ +from slackclient.slackrequest import SlackRequest +from slackclient.version import __version__ +import os + + +def test_http_headers(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', 'chat.postMessage', {'text': 'test', 'channel': '#general'}) + args, kwargs = requests.post.call_args + + assert kwargs['headers']['user-agent'] is not None + + +def test_custom_user_agent(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.append_user_agent("fooagent1", "0.1") + request.append_user_agent("baragent/2", "0.2") + + request.do('xoxb-123', 'chat.postMessage', {'text': 'test', 'channel': '#general'}) + args, kwargs = requests.post.call_args + + # Verify user-agent includes both default and custom agent info + assert "slackclient/{}".format(__version__) in kwargs['headers']['user-agent'] + assert "fooagent1/0.1" in kwargs['headers']['user-agent'] + + # verify escaping of slashes in custom agent name + assert "baragent:2/0.2" in kwargs['headers']['user-agent'] + + +def test_auth_header(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', 'chat.postMessage', {'text': 'test', 'channel': '#general'}) + args, kwargs = requests.post.call_args + + assert "Bearer xoxb-123" in kwargs['headers']['Authorization'] + + +def test_token_override(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', 'chat.postMessage', + { + 'token': "newtoken", + 'text': 'test', + 'channel': '#general' + }) + args, kwargs = requests.post.call_args + + assert "Bearer newtoken" in kwargs['headers']['Authorization'] + + +def test_plural_field(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', 'conversations.open', {'users': ['U123', 'U234', 'U345']}) + args, kwargs = requests.post.call_args + + assert kwargs['data'] == {'users': 'U123,U234,U345'} + + request.do('xoxb-123', 'conversations.open', {'users': "U123,U234,U345"}) + args2, kwargs2 = requests.post.call_args + + assert kwargs2['data'] == {'users': 'U123,U234,U345'} + + +def test_post_file(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', + 'files.upload', + {'file': open(os.path.join('.', 'tests', 'data', 'slack_logo.png'), 'rb'), + 'filename': 'slack_logo.png'}) + args, kwargs = requests.post.call_args + + assert requests.post.call_count == 1 + assert 'https://slack.com/api/files.upload' == args[0] + assert {'filename': 'slack_logo.png'} == kwargs['data'] + assert kwargs['files'] is not None + + +def test_get_file(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', 'files.info', {'file': 'myFavoriteFileID'}) + args, kwargs = requests.post.call_args + + assert requests.post.call_count == 1 + assert 'https://slack.com/api/files.info' == args[0] + assert {'file': "myFavoriteFileID"} == kwargs['data'] + assert kwargs['files'] is None + + +def test_post_attachements(mocker): + requests = mocker.patch('slackclient.slackrequest.requests') + request = SlackRequest() + + request.do('xoxb-123', + 'chat.postMessage', + {'attachments': [{'title': 'hello'}]}) + args, kwargs = requests.post.call_args + + assert requests.post.call_count == 1 + assert 'https://slack.com/api/chat.postMessage' == args[0] + assert isinstance(kwargs["data"]["attachments"], str) diff --git a/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tox.ini b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tox.ini new file mode 100644 index 0000000000..488623a078 --- /dev/null +++ b/openpype/modules/slack/python2_vendor/python-slack-sdk-1/tox.ini @@ -0,0 +1,35 @@ +[tox] +; you probably don't have all of these python versions on your machine. when you invoke tox, you should pick an +; environment that you have (e.g. `tox -e py27,py36,flake8`). +; for quality analysis, use `tox -e flake8` or just `flake8 slackclient` +; to build the docs, use `tox -e docs` +envlist= + py{27,34,35,36}, + flake8, + docs + +[testenv] +deps = -rtest_requirements.txt +commands = + ; `--cov-report=html:cov_html`: suppress terminal output, html report in `cov_html/`, populate `.coverage/` + ; `--cov=slackclient`: name project + ; `{posargs:tests}`: tests located in `tests` by default unless otherwise overriden by tox positional args + py.test --cov-report=html:cov_html --cov=slackclient {posargs:tests} + ; `codecov` will run the `coverage` utility and then upload results in xml format + ; `coverage` utility has configuration in `.coveragerc` + ; CI systems use their own build matricies and virtualenvs and don't need tox. therefore tox shouldn't be used + ; to upload coverage to codecov + ; codecov -e TOXENV + +[testenv:flake8] +basepython = python +deps = flake8 +commands = flake8 slackclient + +[testenv:docs] +basepython = python +whitelist_externals = /bin/bash +deps = + Sphinx + sphinx_rtd_theme +commands = bash ./docs.sh From d7097aa338b4c3eec0f4a169fe733404c15510ba Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 16:18:21 +0200 Subject: [PATCH 22/41] client/#75 - small fixes for Settings --- openpype/modules/slack/README.md | 3 +- .../plugins/publish/integrate_slack_api.py | 53 +++++++++---------- .../defaults/project_settings/slack.json | 14 +++-- .../defaults/system_settings/modules.json | 2 +- .../projects_schema/schema_project_slack.json | 10 ++-- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/openpype/modules/slack/README.md b/openpype/modules/slack/README.md index a189fdf978..baf0f9a1ec 100644 --- a/openpype/modules/slack/README.md +++ b/openpype/modules/slack/README.md @@ -46,4 +46,5 @@ Example of message content: ```{SUBSET} for {Asset} was published.``` Integration can upload 'thumbnail' file (if present in instance), for that bot must be -manually added to target channel by Slack admin! \ No newline at end of file +manually added to target channel by Slack admin! +(In target channel write: ```/invite @OpenPypeNotifier``) \ No newline at end of file diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index a2a1ce4d55..371e14214f 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -1,28 +1,19 @@ import os -try: - from slackclient import SlackClient - python2 = True -except ImportError: - python2 = False - from slack_sdk import WebClient - from slack_sdk.errors import SlackApiError - +import six import pyblish.api +import copy + from openpype.lib.plugin_tools import prepare_template_data class IntegrateSlackAPI(pyblish.api.InstancePlugin): """ Send message notification to a channel. - Triggers on instances with "slack" family, filled by 'collect_slack_family'. - Expects configured profile in Project settings > Slack > Publish plugins > Notification to Slack. - If instance contains 'thumbnail' it uploads it. Bot must be present in the target channel. - Message template can contain {} placeholders from anatomyData. """ order = pyblish.api.IntegratorOrder + 0.499 @@ -34,32 +25,34 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def process(self, instance): message_templ = instance.data["slack_message"] + fill_data = copy.deepcopy(instance.context.data["anatomyData"]) + self.log.debug("fill_data {}".format(fill_data)) fill_pairs = ( - ("project_name", instance.data["anatomyData"]["project"]["name"]), - ("project_code", instance.data["anatomyData"]["project"]["code"]), - ("asset", instance.data["anatomyData"]["asset"]), - ("subset", instance.data["anatomyData"]["subset"]), - ("task", instance.data["anatomyData"]["task"]), - ("username", instance.data["anatomyData"]["username"]), - ("app", instance.data["anatomyData"]["app"]), - ("family", instance.data["anatomyData"]["family"]), - ("version", str(instance.data["anatomyData"]["version"])), + ("asset", fill_data["asset"]), + ("subset", fill_data.get("subset", instance.data["subset"])), + ("task", fill_data.get("task")), + ("username", fill_data.get("username")), + ("app", fill_data.get("app")), + ("family", fill_data.get("family", instance.data["family"])), + ("version", str(fill_data.get("version"))), ) - message = None + self.log.debug("fill_pairs {}".format(fill_pairs)) + multiple_case_variants = prepare_template_data(fill_pairs) + fill_data.update(multiple_case_variants) + self.log.debug("fill_data upd {}".format(fill_data)) + try: - message = message_templ.format( - **prepare_template_data(fill_pairs)) + message = message_templ.format(**fill_data) except Exception: self.log.warning( "Some keys are missing in {}".format(message_templ), exc_info=True) - - self.log.debug("message:: {}".format(message)) + return published_path = self._get_thumbnail_path(instance) for channel in instance.data["slack_channel"]: - if python2: + if six.PY2: self._python2_call(instance.data["slack_token"], channel, message, @@ -74,8 +67,8 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): """Returns abs url for thumbnail if present in instance repres""" published_path = None for repre in instance.data['representations']: + self.log.debug("repre ::{}".format(repre)) if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_files = repre["files"] if isinstance(repre_files, (tuple, list, set)): filename = repre_files[0] @@ -89,6 +82,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): return published_path def _python2_call(self, token, channel, message, published_path): + from slackclient import SlackClient try: client = SlackClient(token) if not published_path: @@ -114,6 +108,8 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): self.log.warning("Error happened: {}".format(error_str)) def _python3_call(self, token, channel, message, published_path): + from slack_sdk import WebClient + from slack_sdk.errors import SlackApiError try: client = WebClient(token=token) if not published_path: @@ -139,5 +135,4 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): # the channel msg = " - application must added to channel '{}'.".format(channel) error_str += msg + " Ask Slack admin." - return error_str diff --git a/openpype/settings/defaults/project_settings/slack.json b/openpype/settings/defaults/project_settings/slack.json index 8453945a5e..853ef4bed7 100644 --- a/openpype/settings/defaults/project_settings/slack.json +++ b/openpype/settings/defaults/project_settings/slack.json @@ -1,16 +1,20 @@ { - "token": "", + "token": "xoxb-2134660737317-2122995982087-cALVXgj3mpBhdf2nO36cdWok", "publish": { "CollectSlackFamilies": { "enabled": true, "optional": true, "profiles": [ { - "families": [], - "hosts": [], + "families": [ + "workfile" + ], "tasks": [], - "channel": [], - "message": "" + "hosts": [], + "channel": [ + "things" + ], + "message": "{Asset} was publishet {subset} with {thumbnail}" } ] } diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index f759546dca..1b74b4695c 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -169,6 +169,6 @@ "enabled": true }, "slack": { - "enabled": true + "enabled": false } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json index 7479924d36..83c8ab0812 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -16,16 +16,17 @@ "key": "publish", "label": "Publish plugins", "children": [ + { + "type": "label", + "label": "Fill combination of families, task names and hosts when to send notification" + }, { "type": "dict", "key": "CollectSlackFamilies", "label": "Notification to Slack", "use_label_wrap": true, + "checkbox_key": "enabled", "children": [ - { - "type": "label", - "label": "" - }, { "type": "boolean", "key": "enabled", @@ -41,6 +42,7 @@ "collapsible": true, "key": "profiles", "label": "Profiles", + "use_label_wrap": true, "object_type": { "type": "dict", "children": [ From 1bc6c070459bcc775e3fdb78225a9bcc474791a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 16:18:39 +0200 Subject: [PATCH 23/41] define name column width and use interactive stretch to be able resize it and avoid strange behavior --- .../tools/project_manager/project_manager/view.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 4d75af3405..74f5a06b71 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -92,19 +92,16 @@ class HierarchyView(QtWidgets.QTreeView): "stretch": QtWidgets.QHeaderView.ResizeToContents }, "name": { - "stretch": QtWidgets.QHeaderView.Stretch + "stretch": QtWidgets.QHeaderView.Interactive, + "width": 260 }, "type": { "stretch": QtWidgets.QHeaderView.Interactive, - "width": 100 + "width": 140 }, "tools_env": { "stretch": QtWidgets.QHeaderView.Interactive, - "width": 140 - }, - "pixelAspect": { - "stretch": QtWidgets.QHeaderView.Interactive, - "width": 80 + "width": 200 } } persistent_columns = { @@ -180,13 +177,14 @@ class HierarchyView(QtWidgets.QTreeView): def header_init(self): header = self.header() - header.setStretchLastSection(False) default_behavior = self.columns_sizes["default"] widths_by_idx = {} for idx in range(header.count()): key = self._source_model.columns[idx] behavior = self.columns_sizes.get(key, default_behavior) + if behavior is None: + continue logical_index = header.logicalIndex(idx) stretch = behavior["stretch"] header.setSectionResizeMode(logical_index, stretch) From 444d824a4968fb2a17d63d6e66e76edeb3542211 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 16:19:01 +0200 Subject: [PATCH 24/41] client/#75 - added invitation to channel --- website/docs/module_slack.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index de7014c203..86c7baaa83 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -55,4 +55,5 @@ to send messages to 'public' channels (eg. bot doesn't need to join the channel ![Configure module](assets/slack_system.png) Integration can upload 'thumbnail' file (if present in instance), for that bot must be -manually added to target channel by Slack admin! \ No newline at end of file +manually added to target channel by Slack admin! +(In target channel write: ```/invite @OpenPypeNotifier``) \ No newline at end of file From 617ed02ef5a4d77fc81a6906450bfbef22e26356 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Jun 2021 16:27:38 +0200 Subject: [PATCH 25/41] Update README.md remove requirements badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 566e226538..6b4495c9b6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ OpenPype ==== -[![documentation](https://github.com/pypeclub/pype/actions/workflows/documentation.yml/badge.svg)](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) ![GitHub Requirements](https://img.shields.io/requires/github/pypeclub/pype?labelColor=303846) ![GitHub VFX Platform](https://img.shields.io/badge/vfx%20platform-2021-lightgrey?labelColor=303846) +[![documentation](https://github.com/pypeclub/pype/actions/workflows/documentation.yml/badge.svg)](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) ![GitHub VFX Platform](https://img.shields.io/badge/vfx%20platform-2021-lightgrey?labelColor=303846) From a082da2f42132e8eda708b9207fc7a87a02e9da5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 16:28:40 +0200 Subject: [PATCH 26/41] client/#75 - added documentation about templates --- website/docs/module_slack.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 86c7baaa83..e993f36965 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -56,4 +56,19 @@ to send messages to 'public' channels (eg. bot doesn't need to join the channel Integration can upload 'thumbnail' file (if present in instance), for that bot must be manually added to target channel by Slack admin! -(In target channel write: ```/invite @OpenPypeNotifier``) \ No newline at end of file +(In target channel write: ```/invite @OpenPypeNotifier``) + +### Message +Message content can use Templating (see https://openpype.io/docs/admin_settings_project_anatomy/#available-template-keys). + +Pre selected set of keys could be used in lowercase, Capitalized or UPPERCASE format, values will be modified accordingly. +({Asset} >> "Asset", {FAMILY} >> "RENDER") + +**Available keys:** +- asset +- subset +- task +- username +- app +- family +- version From 53a0579cda8b0113980e9cdc029281be985f6545 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 16:29:15 +0200 Subject: [PATCH 27/41] client/#75 - removing unnecessary debugs --- .../modules/slack/plugins/publish/integrate_slack_api.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 371e14214f..ce1511d8e3 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -26,7 +26,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): message_templ = instance.data["slack_message"] fill_data = copy.deepcopy(instance.context.data["anatomyData"]) - self.log.debug("fill_data {}".format(fill_data)) + fill_pairs = ( ("asset", fill_data["asset"]), ("subset", fill_data.get("subset", instance.data["subset"])), @@ -36,10 +36,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): ("family", fill_data.get("family", instance.data["family"])), ("version", str(fill_data.get("version"))), ) - self.log.debug("fill_pairs {}".format(fill_pairs)) + multiple_case_variants = prepare_template_data(fill_pairs) fill_data.update(multiple_case_variants) - self.log.debug("fill_data upd {}".format(fill_data)) try: message = message_templ.format(**fill_data) @@ -67,7 +66,6 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): """Returns abs url for thumbnail if present in instance repres""" published_path = None for repre in instance.data['representations']: - self.log.debug("repre ::{}".format(repre)) if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): repre_files = repre["files"] if isinstance(repre_files, (tuple, list, set)): From f9ab24c539d85992f9a12f01522997a40eadc3db Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Jun 2021 16:41:31 +0200 Subject: [PATCH 28/41] rename workflow --- .github/workflows/nightly_merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml index 98d8cd41f3..8b8792cb62 100644 --- a/.github/workflows/nightly_merge.yml +++ b/.github/workflows/nightly_merge.yml @@ -1,4 +1,4 @@ -name: Merge Develop to Main +name: Dev -> Main on: schedule: From 80833c7b7918eecb721c9e141fa4d56883917a1f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 18:03:09 +0200 Subject: [PATCH 29/41] client/#75 - fixed thumbnail upload in Python2 --- .../plugins/publish/integrate_slack_api.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index ce1511d8e3..d3f0f36140 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -83,19 +83,22 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): from slackclient import SlackClient try: client = SlackClient(token) - if not published_path: + if published_path and os.path.exists(published_path): + with open(published_path, 'rb') as pf: + response = client.api_call( + "files.upload", + channels=channel, + initial_comment=message, + file=pf, + title=os.path.basename(published_path) + ) + else: response = client.api_call( "chat.postMessage", channel=channel, text=message ) - else: - response = client.api_call( - "files.upload", - channels=channel, - initial_comment=message, - file=published_path, - ) + if response.get("error"): error_str = self._enrich_error(str(response.get("error")), channel) @@ -110,17 +113,17 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): from slack_sdk.errors import SlackApiError try: client = WebClient(token=token) - if not published_path: - _ = client.chat_postMessage( - channel=channel, - text=message - ) - else: + if published_path and os.path.exists(published_path): _ = client.files_upload( channels=channel, initial_comment=message, file=published_path, ) + else: + _ = client.chat_postMessage( + channel=channel, + text=message + ) except SlackApiError as e: # You will get a SlackApiError if "ok" is False error_str = self._enrich_error(str(e.response["error"]), channel) From ede5013dbd44fe5b66c3efd779e348a733921bed Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 18:10:34 +0200 Subject: [PATCH 30/41] trigger set_entity_value on add_row after new item is part of input_fields --- openpype/tools/settings/settings/list_item_widget.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index e1990d0bf6..82ca541132 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -100,7 +100,6 @@ class ListItem(QtWidgets.QWidget): self.input_field = self.create_ui_for_entity( self.category_widget, self.entity, self ) - self.input_field.set_entity_value() spacer_widget = QtWidgets.QWidget(self) spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) @@ -337,6 +336,12 @@ class ListWidget(InputWidget): self.content_layout.insertWidget(row + 1, item_widget) self.input_fields.insert(row, item_widget) + # Change to entity value after item is added to `input_fields` + # - may cause recursion error as setting a value may cause input field + # change which will trigger this validation if entity is already + # added as widget here which won't because is not in input_fields + item_widget.input_field.set_entity_value() + if previous_field: previous_field.order_changed() From d199fad0e66d1982ba2e889e80c79e49a7b59fd1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 18:15:08 +0200 Subject: [PATCH 31/41] fix another unrelated issue in DictMutableKeysEntity --- openpype/settings/entities/dict_mutable_keys_entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index 4b221720c3..3c2645e3e5 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -467,7 +467,7 @@ class DictMutableKeysEntity(EndpointEntity): if self.store_as_list: output = [] for key, child_entity in self.children_by_key.items(): - output.append(key, child_entity.value) + output.append([key, child_entity.value]) return output output = {} From 069ba7b3afc2a7785c1fd8e927393834e5aca4b0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 18:52:01 +0200 Subject: [PATCH 32/41] client/#75 - added functionality to send different message to different channel(s) --- .../plugins/publish/collect_slack_family.py | 5 +- .../plugins/publish/integrate_slack_api.py | 52 +++++++++++------- .../defaults/project_settings/slack.json | 11 +--- .../projects_schema/schema_project_slack.json | 34 +++++++++--- website/docs/assets/slack_project.png | Bin 34468 -> 48648 bytes website/docs/module_slack.md | 11 ++-- 6 files changed, 70 insertions(+), 43 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index fefc0c8f56..2110c0703b 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -30,7 +30,6 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): profile = filter_profiles(self.profiles, key_values, logger=self.log) - self.log.debug("profile ::{}".format(profile)) # make slack publishable if profile: if instance.data.get('families'): @@ -38,8 +37,8 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): else: instance.data['families'] = ['slack'] - instance.data["slack_channel"] = profile["channel"] - instance.data["slack_message"] = profile["message"] + instance.data["slack_channel_message_profiles"] = \ + profile["channel_messages"] slack_token = (instance.context.data["project_settings"] ["slack"] diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index d3f0f36140..ccd00dc1c8 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -23,8 +23,30 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): optional = True def process(self, instance): - message_templ = instance.data["slack_message"] + published_path = self._get_thumbnail_path(instance) + for message_profile in instance.data["slack_channel_message_profiles"]: + message = self._get_filled_message(message_profile["message"], + instance) + if not message: + return + + for channel in message_profile["channels"]: + if six.PY2: + self._python2_call(instance.data["slack_token"], + channel, + message, + published_path, + message_profile["upload_thumbnail"]) + else: + self._python3_call(instance.data["slack_token"], + channel, + message, + published_path, + message_profile["upload_thumbnail"]) + + def _get_filled_message(self, message_templ, instance): + """Use message_templ and data from instance to get message content.""" fill_data = copy.deepcopy(instance.context.data["anatomyData"]) fill_pairs = ( @@ -40,27 +62,15 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): multiple_case_variants = prepare_template_data(fill_pairs) fill_data.update(multiple_case_variants) + message = None try: message = message_templ.format(**fill_data) except Exception: self.log.warning( "Some keys are missing in {}".format(message_templ), exc_info=True) - return - published_path = self._get_thumbnail_path(instance) - - for channel in instance.data["slack_channel"]: - if six.PY2: - self._python2_call(instance.data["slack_token"], - channel, - message, - published_path) - else: - self._python3_call(instance.data["slack_token"], - channel, - message, - published_path) + return message def _get_thumbnail_path(self, instance): """Returns abs url for thumbnail if present in instance repres""" @@ -79,11 +89,13 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): break return published_path - def _python2_call(self, token, channel, message, published_path): + def _python2_call(self, token, channel, message, + published_path, upload_thumbnail): from slackclient import SlackClient try: client = SlackClient(token) - if published_path and os.path.exists(published_path): + if upload_thumbnail and \ + published_path and os.path.exists(published_path): with open(published_path, 'rb') as pf: response = client.api_call( "files.upload", @@ -108,12 +120,14 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): error_str = self._enrich_error(str(e), channel) self.log.warning("Error happened: {}".format(error_str)) - def _python3_call(self, token, channel, message, published_path): + def _python3_call(self, token, channel, message, + published_path, upload_thumbnail): from slack_sdk import WebClient from slack_sdk.errors import SlackApiError try: client = WebClient(token=token) - if published_path and os.path.exists(published_path): + if upload_thumbnail and \ + published_path and os.path.exists(published_path): _ = client.files_upload( channels=channel, initial_comment=message, diff --git a/openpype/settings/defaults/project_settings/slack.json b/openpype/settings/defaults/project_settings/slack.json index 853ef4bed7..e70ef77fd2 100644 --- a/openpype/settings/defaults/project_settings/slack.json +++ b/openpype/settings/defaults/project_settings/slack.json @@ -1,20 +1,15 @@ { - "token": "xoxb-2134660737317-2122995982087-cALVXgj3mpBhdf2nO36cdWok", + "token": "", "publish": { "CollectSlackFamilies": { "enabled": true, "optional": true, "profiles": [ { - "families": [ - "workfile" - ], + "families": [], "tasks": [], "hosts": [], - "channel": [ - "things" - ], - "message": "{Asset} was publishet {subset} with {thumbnail}" + "channel_messages": [] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json index 83c8ab0812..532aa083b3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -68,16 +68,32 @@ "type": "separator" }, { + "key": "channel_messages", + "label": "Messages to channels", "type": "list", - "object_type": "text", - "key": "channel", - "label": "Channel" - }, - { - "type": "text", - "multiline": true, - "key": "message", - "label": "Message" + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "channels", + "label": "Channels" + }, + { + "type": "boolean", + "key": "upload_thumbnail", + "label": "Upload thumbnail" + }, + { + "type": "text", + "multiline": false, + "key": "message", + "label": "Message" + } + ] + } } ] } diff --git a/website/docs/assets/slack_project.png b/website/docs/assets/slack_project.png index 496013800f8eebbf03828a3018095becd61320b1..417cdab2de4dfb70f985fadeb49fddf3c55e61eb 100644 GIT binary patch literal 48648 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 PD9kQf{f;)ee(!$)16z81 literal 34468 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 Dx4{?{ diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index e993f36965..0a3d542393 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -48,20 +48,23 @@ Eg. If I want to be notified when render is published from Maya, setting is: - family: 'render' - host: 'Maya' -### Channel -Message could be delivered to one or multiple channels, by default app allows Slack bot +### Messages to channels + +#### Channels +Multiple messages could be delivered to one or multiple channels, by default app allows Slack bot to send messages to 'public' channels (eg. bot doesn't need to join the channel first). ![Configure module](assets/slack_system.png) +#### Upload thumbnail Integration can upload 'thumbnail' file (if present in instance), for that bot must be manually added to target channel by Slack admin! (In target channel write: ```/invite @OpenPypeNotifier``) -### Message +#### Message Message content can use Templating (see https://openpype.io/docs/admin_settings_project_anatomy/#available-template-keys). -Pre selected set of keys could be used in lowercase, Capitalized or UPPERCASE format, values will be modified accordingly. +Pre-selected set of keys could be used in lowercase, Capitalized or UPPERCASE format, values will be modified accordingly. ({Asset} >> "Asset", {FAMILY} >> "RENDER") **Available keys:** From f6998523ed201efe3ae5e6304692007311fc0237 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Jun 2021 19:03:51 +0200 Subject: [PATCH 33/41] client/#75 - added back multiline to message #1671 PR fixed issue that limited of usage of multiline previously --- .../entities/schemas/projects_schema/schema_project_slack.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json index 532aa083b3..58708776ca 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -88,7 +88,7 @@ }, { "type": "text", - "multiline": false, + "multiline": true, "key": "message", "label": "Message" } From b4d081b7ee61e16702a0f7c707ae8d9dffb7a0a6 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Jun 2021 20:31:04 +0200 Subject: [PATCH 34/41] bump igniter version due to dependency updates --- igniter/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igniter/version.py b/igniter/version.py index 3c627aaa1a..56d58f7f60 100644 --- a/igniter/version.py +++ b/igniter/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- """Definition of Igniter version.""" -__version__ = "1.0.0" +__version__ = "1.0.1" From a139bb0738bb6b16480f3e9fcec305f44d1ae5a6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Jun 2021 10:19:37 +0200 Subject: [PATCH 35/41] client/#75 - fixed documentation Site Sync was showed instead of Slack --- website/docs/module_slack.md | 8 ++++---- website/sidebars.js | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 0a3d542393..dfdc46f8a4 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -1,5 +1,5 @@ --- -id: module_site_sync +id: module_slack title: Slack Integration Administration sidebar_label: Slack --- @@ -41,7 +41,7 @@ It is possible to create multiple tokens and configure different scopes for them ### Profiles Profiles are used to select when to trigger notification. One or multiple profiles -could be configured, `Families`, `Task names` (regex available), `Host names` and host combination is needed. +could be configured, `Families`, `Task names` (regex available), `Host names` combination is needed. Eg. If I want to be notified when render is published from Maya, setting is: @@ -54,10 +54,10 @@ Eg. If I want to be notified when render is published from Maya, setting is: Multiple messages could be delivered to one or multiple channels, by default app allows Slack bot to send messages to 'public' channels (eg. bot doesn't need to join the channel first). -![Configure module](assets/slack_system.png) +![Configure module](assets/slack_project.png) #### Upload thumbnail -Integration can upload 'thumbnail' file (if present in instance), for that bot must be +Integration can upload 'thumbnail' file (if present in an instance), for that bot must be manually added to target channel by Slack admin! (In target channel write: ```/invite @OpenPypeNotifier``) diff --git a/website/sidebars.js b/website/sidebars.js index 0b831bccb3..59071ec34f 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -78,7 +78,8 @@ module.exports = { "module_site_sync", "module_deadline", "module_muster", - "module_clockify" + "module_clockify", + "module_slack" ], }, { From 35c3f5004c71da1631ade7d995e0dd67a405f119 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 10 Jun 2021 10:44:28 +0200 Subject: [PATCH 36/41] fix workflow step name --- .github/workflows/nightly_merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml index 8b8792cb62..1d36c89cc7 100644 --- a/.github/workflows/nightly_merge.yml +++ b/.github/workflows/nightly_merge.yml @@ -14,7 +14,7 @@ jobs: - name: 🚛 Checkout Code uses: actions/checkout@v2 - - name: 🔨 Merge main back to develop + - name: 🔨 Merge develop to main uses: everlytic/branch-merge@1.1.0 with: github_token: ${{ secrets.ADMIN_TOKEN }} From 14d8a92b1beb149cc08a14b1341b6d44afef2ad3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Jun 2021 10:52:30 +0200 Subject: [PATCH 37/41] client/#75 - fixed documentation --- website/docs/module_slack.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index dfdc46f8a4..45e1520056 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -62,10 +62,9 @@ manually added to target channel by Slack admin! (In target channel write: ```/invite @OpenPypeNotifier``) #### Message -Message content can use Templating (see https://openpype.io/docs/admin_settings_project_anatomy/#available-template-keys). +Message content can use Templating (see [Available template keys](admin_settings_project_anatomy#available-template-keys)). -Pre-selected set of keys could be used in lowercase, Capitalized or UPPERCASE format, values will be modified accordingly. -({Asset} >> "Asset", {FAMILY} >> "RENDER") +Few keys also have Capitalized and UPPERCASE format. Values will be modified accordingly ({Asset} >> "Asset", {FAMILY} >> "RENDER"). **Available keys:** - asset From 311bad31b24686cd3e7a3fbb8c367399038d49da Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Jun 2021 12:51:46 +0200 Subject: [PATCH 38/41] client/#75 - fixed documentation - added slack icon --- website/docs/module_slack.md | 2 +- website/src/pages/index.js | 5 +++++ website/static/img/app_slack.png | Bin 0 -> 4778 bytes 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 website/static/img/app_slack.png diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 45e1520056..f71fcc2bb7 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -18,7 +18,7 @@ whenever configured asset type is published. Slack application must be installed to company's Slack first. Please locate `openpype/modules/slack/manifest.yml` file in deployed OpenPype installation and follow instruction at -https://api.slack.com/reference/manifests#using +https://api.slack.com/reference/manifests#using and follow "Creating apps with manifests". ## System Settings diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 8eb6e84c24..6a233ddb66 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -351,6 +351,11 @@ function Home() { DaVinci Resolve (Beta) + + + Slack (Beta) + +

In development by us or a community of avalon core developers.

diff --git a/website/static/img/app_slack.png b/website/static/img/app_slack.png new file mode 100644 index 0000000000000000000000000000000000000000..20c9a465b8457c88211eaf02bf91f6df815c1f8b GIT binary patch literal 4778 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 YUl*&GKDcS%A3n&^)W+nkkq6;_01ILn Date: Thu, 10 Jun 2021 13:10:46 +0200 Subject: [PATCH 39/41] client/#75 - fixed precedence of selection instance data should have precedence --- .../slack/plugins/publish/integrate_slack_api.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index ccd00dc1c8..7b81d3c364 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -50,13 +50,15 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): fill_data = copy.deepcopy(instance.context.data["anatomyData"]) fill_pairs = ( - ("asset", fill_data["asset"]), - ("subset", fill_data.get("subset", instance.data["subset"])), - ("task", fill_data.get("task")), - ("username", fill_data.get("username")), - ("app", fill_data.get("app")), - ("family", fill_data.get("family", instance.data["family"])), - ("version", str(fill_data.get("version"))), + ("asset", instance.data.get("asset", fill_data.get("asset"))), + ("subset", instance.data.get("subset", fill_data.get("subset"))), + ("task", instance.data.get("task", fill_data.get("task"))), + ("username", instance.data.get("username", + fill_data.get("username"))), + ("app", instance.data.get("app", fill_data.get("app"))), + ("family", instance.data.get("family", fill_data.get("family"))), + ("version", str(instance.data.get("version", + fill_data.get("version")))) ) multiple_case_variants = prepare_template_data(fill_pairs) From 3b348f2af3342a33d615097232f74f9b4e8c2bec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Jun 2021 14:08:31 +0200 Subject: [PATCH 40/41] use group name as default value for RenderLayer --- .../plugins/create/create_render_layer.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 585f0c87d7..3eb2f5871d 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -1,4 +1,8 @@ -from avalon.tvpaint import pipeline, lib +from avalon.tvpaint import ( + pipeline, + lib, + CommunicationWrapper +) from openpype.hosts.tvpaint.api import plugin @@ -18,6 +22,40 @@ class CreateRenderlayer(plugin.Creator): " {clip_id} {group_id} {r} {g} {b} \"{name}\"" ) + @classmethod + def get_default_variant(cls): + """Default value for variant in Creator tool. + + Method checks if TVPaint implementation is running and tries to find + selected layers from TVPaint. If only one is selected it's name is + returned. + + Returns: + str: Default variant name for Creator tool. + """ + # Validate that communication is initialized + if CommunicationWrapper.communicator: + # Get currently selected layers + layers_data = lib.layers_data() + + group_ids = set() + for layer in layers_data: + if layer["selected"]: + group_ids.add(layer["group_id"]) + + # Return layer name if only one is selected + if len(group_ids) == 1: + group_id = list(group_ids)[0] + groups_data = lib.groups_data() + for group in groups_data: + if group["group_id"] == group_id: + return group["name"] + + # Use defaults + if cls.defaults: + return cls.defaults[0] + return None + def process(self): self.log.debug("Query data from workfile.") instances = pipeline.list_instances() From 1bbca47a20e181c54efe9a408188254f7cb362a3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 10 Jun 2021 14:42:35 +0200 Subject: [PATCH 41/41] #683 - Validate Frame Range in Standalone Added settings and defaults --- .../plugins/publish/validate_frame_ranges.py | 53 +++++++++++++++++++ .../project_settings/standalonepublisher.json | 40 +++++++++++++- .../schema_project_standalonepublisher.json | 46 ++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py new file mode 100644 index 0000000000..e3086fb638 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py @@ -0,0 +1,53 @@ +import re + +import pyblish.api +import openpype.api +from openpype import lib + + +class ValidateFrameRange(pyblish.api.InstancePlugin): + """Validating frame range of rendered files against state in DB.""" + + label = "Validate Frame Range" + hosts = ["standalonepublisher"] + families = ["render"] + order = openpype.api.ValidateContentsOrder + + optional = True + # published data might be sequence (.mov, .mp4) in that counting files + # doesnt make sense + check_extensions = ["exr", "dpx", "jpg", "jpeg", "png", "tiff", "tga", + "gif", "svg"] + skip_timelines_check = [] # skip for specific task names (regex) + + def process(self, instance): + if any(re.search(pattern, instance.data["task"]) + for pattern in self.skip_timelines_check): + self.log.info("Skipping for {} task".format(instance.data["task"])) + + asset_data = lib.get_asset(instance.data["asset"])["data"] + frame_start = asset_data["frameStart"] + frame_end = asset_data["frameEnd"] + handle_start = asset_data["handleStart"] + handle_end = asset_data["handleEnd"] + duration = (frame_end - frame_start + 1) + handle_start + handle_end + + repre = instance.data.get("representations", [None]) + if not repre: + self.log.info("No representations, skipping.") + return + + ext = repre[0]['ext'].replace(".", '') + + if not ext or ext.lower() not in self.check_extensions: + self.log.warning("Cannot check for extension {}".format(ext)) + return + + frames = len(instance.data.get("representations", [None])[0]["files"]) + + err_msg = "Frame duration from DB:'{}' ". format(int(duration)) +\ + " doesn't match number of files:'{}'".format(frames) +\ + " Please change frame range for Asset or limit no. of files" + assert frames == duration, err_msg + + self.log.debug("Valid ranges {} - {}".format(int(duration), frames)) diff --git a/openpype/settings/defaults/project_settings/standalonepublisher.json b/openpype/settings/defaults/project_settings/standalonepublisher.json index 9d40d2ded6..979f5285d3 100644 --- a/openpype/settings/defaults/project_settings/standalonepublisher.json +++ b/openpype/settings/defaults/project_settings/standalonepublisher.json @@ -100,6 +100,22 @@ ], "help": "Script exported from matchmoving application" }, + "create_render": { + "name": "render", + "label": "Render", + "family": "render", + "icon": "image", + "defaults": ["Animation", "Lighting", "Lookdev", "Compositing"], + "help": "Rendered images or video files" + }, + "create_mov_batch": { + "name": "mov_batch", + "label": "Batch Mov", + "family": "render_mov_batch", + "icon": "image", + "defaults": ["Main"], + "help": "Process multiple Mov files and publish them for layout and comp." + }, "__dynamic_keys_labels__": { "create_workfile": "Workfile", "create_model": "Model", @@ -109,10 +125,32 @@ "create_camera": "Camera", "create_editorial": "Editorial", "create_image": "Image", - "create_matchmove": "Matchmove" + "create_matchmove": "Matchmove", + "create_render": "Render", + "create_mov_batch": "Batch Mov" } }, "publish": { + "ValidateSceneSettings": { + "enabled": true, + "optional": true, + "active": true, + "check_extensions": [ + "exr", + "dpx", + "jpg", + "jpeg", + "png", + "tiff", + "tga", + "gif", + "svg" + ], + "families": [ + "render" + ], + "skip_timelines_check": [] + }, "ExtractThumbnailSP": { "ffmpeg_args": { "input": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json index 28755ad268..0ef7612805 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json @@ -56,6 +56,52 @@ "key": "publish", "label": "Publish plugins", "children": [ + { + "type": "dict", + "collapsible": true, + "key": "ValidateSceneSettings", + "label": "Validate Scene Settings", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "label", + "label": "Validate if frame range in DB matches number of published files" + }, + { + "type": "list", + "key": "check_extensions", + "object_type": "text", + "label": "Check Frame Range for Extensions" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "list", + "key": "skip_timelines_check", + "object_type": "text", + "label": "Skip Frame Range check for Tasks" + } + ] + }, { "type": "dict", "collapsible": true,
+ + + + + + + {{ project }} + + +