├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── docs ├── docs │ ├── cards.md │ ├── index.md │ ├── messages.md │ └── rooms.md ├── mkdocs.yml └── site │ ├── 404.html │ ├── cards │ └── index.html │ ├── css │ ├── fonts │ │ ├── Roboto-Slab-Bold.woff │ │ ├── Roboto-Slab-Bold.woff2 │ │ ├── Roboto-Slab-Regular.woff │ │ ├── Roboto-Slab-Regular.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── lato-bold-italic.woff │ │ ├── lato-bold-italic.woff2 │ │ ├── lato-bold.woff │ │ ├── lato-bold.woff2 │ │ ├── lato-normal-italic.woff │ │ ├── lato-normal-italic.woff2 │ │ ├── lato-normal.woff │ │ └── lato-normal.woff2 │ ├── theme.css │ └── theme_extra.css │ ├── img │ └── favicon.ico │ ├── index.html │ ├── js │ ├── html5shiv.min.js │ ├── jquery-3.6.0.min.js │ ├── theme.js │ └── theme_extra.js │ ├── messages │ └── index.html │ ├── rooms │ └── index.html │ ├── search.html │ ├── search │ ├── lunr.js │ ├── main.js │ ├── search_index.json │ └── worker.js │ ├── sitemap.xml │ └── sitemap.xml.gz ├── python_webex ├── __init__.py ├── v1 │ ├── Bot.py │ ├── Card.py │ ├── Message.py │ ├── People.py │ ├── Room.py │ ├── Webhook.py │ └── __init__.py └── webhook │ ├── __init__.py │ └── handlers.py ├── python_webex_bot ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | Procfile 2 | runtime.txt 3 | */__pycache__/* 4 | /python_webex_bot.egg-info/ 5 | /__pycache__/* 6 | /venv/* 7 | .vscode 8 | .pytest_cache/ 9 | */python_webex_bot.egg-info/* 10 | /dist/* 11 | /build/* 12 | test_script.py 13 | .gitignore 14 | *.pyc 15 | upgrade_version.sh 16 | .env 17 | test_script.sh 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![published](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-published.svg)](https://developer.cisco.com/codeexchange/github/repo/Paul-weqe/python_webex_bot) 2 | [![PyPI version](https://badge.fury.io/py/python-webex-bot.svg)](https://badge.fury.io/py/python-webex-bot) 3 | 4 | [Official Documentation](https://python-webex-bot.readthedocs.io/en/latest/) 5 | 6 | [Installation and Quickstart](https://python-webex-bot.readthedocs.io/en/latest/)
7 | [Rooms](https://python-webex-bot.readthedocs.io/en/latest/docs/rooms/)
8 | [Messages](https://python-webex-bot.readthedocs.io/en/latest/docs/messages/)
9 | [Cards](https://python-webex-bot.readthedocs.io/en/latest/docs/cards/)
10 | 11 | # python_webex_bot 12 | 13 | A python3 library meant to help you create a cisco webex teams bot and take advantage of some of the features available to these bots. 14 | Most of the python libraries setup for webex have been lacking in terms of connecting you to a webhook and this aims at solving that 15 | 16 | ## Installation and setup 17 | 18 | The following are items this documentation assumes you already have installed 19 | - virtualenv 20 | - python3 21 | - ngrok 22 | 23 | ### Step 1: setup the virtual environment 24 | 25 | to initialize the virtual environment, run the following command in your Command Line or Command Prompt 26 | ``` 27 | virtualenv venv 28 | ``` 29 | 30 | then we activate it: 31 | 32 | Windows 33 | ``` 34 | venv\Scripts\activate 35 | ``` 36 | 37 | Linux 38 | ``` 39 | source venv/bin/activate 40 | ``` 41 | 42 | and there, you have your virtual environment setup and ready for action 43 | 44 | ### Step 2: install python_webex_bot 45 | 46 | while still in your activated virtual environment, run the following command to install python_webex_bot via pip: 47 | 48 | ``` 49 | pip install python_webex_bot 50 | ``` 51 | 52 | then download ngrok which will be used in the concurrent steps 53 | 54 | ## Quickstart 55 | 56 | Lets get a simple bot up, running and responsive on our local machine. 57 | 58 | ### Step 1: Create the bot on Cisco Webex 59 | 60 | If you haven't already, create your Webex account. 61 | Then head on to create your bot 62 | 63 | You should be provided with an access token for the bot. 64 | 65 | Take this access token and place it in your environment variable as auth_token. 66 | 67 | this can be done via your Command prompt or Command Line as: 68 | ``` 69 | set auth_token=my_auth_token 70 | ``` 71 | 72 | replace my_auth_token with your bots access token 73 | 74 | This is a crutial part of running your bot as the python_webex_bot library uses this to identify your bot 75 | 76 | If you still have some questions on environment variables, why we need them and how to use them, this may be a good start 77 | 78 | ### Step 2: setup ngrok 79 | 80 | in a different terminal from the one used in steps 1 and 2, navigate to the folder where you have the ngrok placed. 81 | 82 | Then run the following command: 83 | ``` 84 | ngrok http 5000 85 | ``` 86 | 87 | This should produce an output similar to the one shown below: 88 | ``` 89 | Session Status online 90 | Session Expires 7 hours, 59 minutes 91 | Update update available (version 2.3.25, Ctrl-U to update) 92 | Version 2.3.18 93 | Region United States (us) 94 | Web Interface http://127.0.0.1:4040 95 | Forwarding http://87a942a1.ngrok.io -> http://localhost:5000 96 | Forwarding https://87a942a1.ngrok.io -> http://localhost:5000 97 | 98 | Connections ttl opn rt1 rt5 p50 p90 99 | 0 0 0.00 0.00 0.00 0.00 100 | ``` 101 | 102 | Now you are ready for the quest 103 | 104 | ### Step 3: create the python file and run it 105 | 106 | Create a python file where you intend to run the bot. In my case, I will name my file `run.py` 107 | 108 | copy and paste the following code: 109 | 110 | ```python 111 | from python_webex.v1.Bot import Bot 112 | from python_webex import webhook 113 | 114 | bot = Bot() # the program will automatically know the bot being referred to y the auth_token 115 | 116 | # create a webhook to expose it to the internet 117 | # rememer that url we got from step 2, this is where we use it. In my case it was http://87a942a1.ngrok.io. 118 | # We will be creating a webhook that will be listening when messages are sent 119 | bot.create_webhook( 120 | name="quickstart_webhook", target_url="http://87a942a1.ngrok.io", resource="messages", event="created" 121 | ) 122 | 123 | # we create a function that responds when someone says hi 124 | # the room_id will automatically be filled with the webhook. Do not forget it 125 | @bot.on_hears("hi") 126 | def greet_back(room_id=None): 127 | return bot.send_message(room_id=room_id, text="Hi, how are you doing?") 128 | 129 | # We create a default response in case anyone types anything else that we have not set a response for 130 | # this is done using * [ don't ask me what happend when someone sends '*' as the message, that's on my TODO] 131 | @bot.on_hears("*") 132 | def default_response(room_id=None): 133 | return bot.send_message(room_id=room_id, text="Sorry, could not understand that") 134 | 135 | 136 | # make the webhook know the bot to be listening for, and we are done 137 | webhook.bot = bot 138 | 139 | if __name__ == "__main__": 140 | webhook.app.run(debug=True) # don't keep debug=True in production 141 | ``` 142 | 143 | Now, when we text our bot "hi", it will respond with "Hi, how are you doing?" 144 | 145 | And when we text anything else, like "When can we meet up?" it will respond with "Sorry, I could not understand that" 146 | 147 | 148 | FIND MORE DOCUMENTATION here 149 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/__init__.py -------------------------------------------------------------------------------- /docs/docs/cards.md: -------------------------------------------------------------------------------- 1 | 2 | # Cards 3 | 4 | ## Create & Send Blank Card 5 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 6 | 7 | Cards are meant to increase interactivity during the chat. They can be used, for example to send a form that the bot would like an end user to respond to. In this instance, we are sending a blank card to the user, which is pretty much useless. This can 8 | 9 | 10 | ```python 11 | from python.webex.v1.Card import Card 12 | from python.webex.v1.Bot import Bot 13 | 14 | bot = Bot() 15 | 16 | card = Card() 17 | 18 | bot.send_card(card=card, room_id="room-id") 19 | ``` 20 | 21 | ## Add text items on the card 22 | 23 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 24 | 25 | The card we just sent above is pretty much useless. If we are to send a card, the user needs to be able to interact with the card and the bot should be able to read whatever has been input on the user side. 26 | 27 | ```python 28 | from python_webex.v1.Card import Card 29 | from python_webex.v1.Bot import Bot 30 | 31 | bot = Bot() 32 | 33 | bot.create_webhook( 34 | name='attachment-response-2', target_url="[your-bot-url]/attachment-response", resource="attachmentActions", event="created" 35 | ) 36 | 37 | Card = Card() 38 | bot.send_card(card=card, room_id='room-id') 39 | ``` 40 | 41 | ## Create Card Webhook 42 | 43 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 44 | 45 | Here, we create a webhook for the card responses. For instance, if one fills a form that has been sent on a card, the response will be sent to the specific webhook. 46 | 47 | ```python 48 | from python_webex.v1.Card import Card 49 | from python_webex.v1.Bot import Bot 50 | 51 | bot = Bot() 52 | 53 | bot.create_webhook( 54 | name='attachment-response-2', target_url="[your-bot-url]/attachment-response", resource="attachmentActions", event="created" 55 | ) 56 | 57 | ``` 58 | 59 | Note: always make sure to setup this webhook to be whatever link you will be using and append /attachment-response to it. For example, if you are using 'https://abc.com', your value on target_url will be 'https://abc.com/attachment-response' 60 | 61 | ## Listen for response on card 62 | 63 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 64 | 65 | Now, what happens when the user has filled a card form and the response has been sent to the webhook, how do we get the information about the card that has been filled from our end? 66 | 67 | Here is how: 68 | 69 | ```python 70 | from python_webex.v1.Card import Card 71 | from python_webex.v1.Bot imporrt Bot 72 | from pprint import pprint 73 | from python_webex import webhook 74 | 75 | bot = Bot() 76 | card = Card() 77 | 78 | 79 | card.add_input_text( 80 | input_id="first-name-input", input_placeholder="First Name" 81 | ) 82 | 83 | card.add_input_text( 84 | input_id="last-name-input", input_placeholder="Last Name" 85 | ) 86 | 87 | card.add_submit_action_btn( 88 | title="Submit" 89 | ) 90 | 91 | message = bot.send_card(card=card, room_id="room-id") 92 | message_id = message.json()['id'] 93 | 94 | @bot.attachment_response(message_id=message_id) 95 | def respond_to_card(msg): 96 | pprint(msg) 97 | 98 | webhook.bot = bot 99 | 100 | if __name__ == "__main__": 101 | webhook.app.run(debug=True) 102 | 103 | ``` 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | # python_webex_bot 3 | 4 | A python3 library meant to help you create a cisco webex teams bot and take advantage of some of the features available to these bots. 5 | Most of the python libraries setup for webex have been lacking in terms of connecting you to a webhook and this aims at solving that 6 | 7 | ## Installation and setup 8 | 9 | The following are items this documentation assumes you already have installed: 10 | - virtualenv 11 | - python3 12 | - ngrok 13 | 14 | ### Step 1: setup the virtual environment 15 | 16 | to initialize the virtual environment, run the following command in your Command Line or Command Prompt 17 | ``` 18 | virtualenv venv 19 | ``` 20 | 21 | then we activate it: 22 | 23 | Windows 24 | ``` 25 | venv\Scripts\activate 26 | ``` 27 | 28 | Linux 29 | ``` 30 | source venv/bin/activate 31 | ``` 32 | 33 | and there, you have your virtual environment setup and ready for action 34 | 35 | ### Step 2: install python_webex_bot 36 | 37 | while still in your activated virtual environment, run the following command to install python_webex_bot via pip: 38 | 39 | ``` 40 | pip install python_webex_bot 41 | ``` 42 | 43 | then download ngrok which will be used in the concurrent steps 44 | 45 | ## Quickstart 46 | 47 | Lets get a simple bot up, running and responsive on our local machine. 48 | 49 | ### Step 1: Create the bot on Cisco Webex 50 | 51 | If you haven't already, create your Webex account. 52 | Then head on to create your bot 53 | 54 | You should be provided with an access token for the bot. 55 | 56 | Take this access token and place it in your environment variable as auth_token. 57 | 58 | this can be done via your Command prompt or Command Line as: 59 | ``` 60 | export auth_token=my_auth_token 61 | ``` 62 | 63 | If you're on Windows, run the following on the command prompt: 64 | ``` 65 | set auth_token=my_auth_token 66 | ``` 67 | 68 | replace my_auth_token with your bots access token 69 | 70 | This is a crutial part of running your bot as the python_webex_bot library uses this to identify your bot 71 | 72 | If you still have some questions on environment variables, why we need them and how to use them, this may be a good start 73 | 74 | ### Step 2: setup ngrok 75 | 76 | in a different terminal from the one used in steps 1 and 2, navigate to the folder where you have the ngrok placed. 77 | 78 | Then run the following command: 79 | ``` 80 | ngrok http 5000 81 | ``` 82 | 83 | This should produce an output similar to the one shown below: 84 | ``` 85 | Session Status online 86 | Session Expires 7 hours, 59 minutes 87 | Update update available (version 2.3.25, Ctrl-U to update) 88 | Version 2.3.18 89 | Region United States (us) 90 | Web Interface http://127.0.0.1:4040 91 | Forwarding http://87a942a1.ngrok.io -> http://localhost:5000 92 | Forwarding https://87a942a1.ngrok.io -> http://localhost:5000 93 | 94 | Connections ttl opn rt1 rt5 p50 p90 95 | 0 0 0.00 0.00 0.00 0.00 96 | ``` 97 | 98 | Now you are ready for the quest 99 | 100 | ### Step 3: create the python file and run it 101 | 102 | Create a python file where you intend to run the bot. In my case, I will name my file `run.py` 103 | 104 | copy and paste the following code: 105 | 106 | ``` 107 | from python_webex.v1.Bot import Bot 108 | from python_webex import webhook 109 | 110 | bot = Bot() # the program will automatically know the bot being referred to y the auth_token 111 | 112 | # create a webhook to expose it to the internet 113 | # rememer that url we got from step 2, this is where we use it. In my case it was http://87a942a1.ngrok.io. 114 | # We will be creating a webhook that will be listening when messages are sent 115 | bot.create_webhook( 116 | name="quickstart_webhook", target_url="http://87a942a1.ngrok.io", resource="messages", event="created" 117 | ) 118 | 119 | # we create a function that responds when someone says hi 120 | # the room_id will automatically be filled with the webhook. Do not forget it 121 | @bot.on_hears("hi") 122 | def greet_back(room_id=None): 123 | return bot.send_message(room_id=room_id, text="Hi, how are you doing?") 124 | 125 | # We create a default response in case anyone types anything else that we have not set a response for 126 | # this is done using * [ don't ask me what happend when someone sends '*' as the message, that's on my TODO] 127 | @bot.on_hears("*") 128 | def default_response(room_id=None): 129 | return bot.send_message(room_id=room_id, text="Sorry, could not understand that") 130 | 131 | 132 | # make the webhook know the bot to be listening for, and we are done 133 | webhook.bot = bot 134 | 135 | if __name__ == "__main__": 136 | webhook.app.run(debug=True) # don't keep debug=True in production 137 | ``` 138 | 139 | Now, when we text our bot "hi", it will respond with "Hi, how are you doing?" 140 | 141 | And when we text anything else, like "When can we meet up?" it will respond with "Sorry, I could not understand that" 142 | -------------------------------------------------------------------------------- /docs/docs/messages.md: -------------------------------------------------------------------------------- 1 | 2 | # Messages 3 | 4 | ## Create Message 5 | 6 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 7 | 8 | Want to send a message from your bot to a specific room? No worries, here is how: 9 | 10 | ```python 11 | from python_webex.v1.Bot import Bot 12 | 13 | bot = Bot() 14 | 15 | bot.send_message(to_person_email='person-email@gmail.com', text='This is some text') 16 | # or 17 | bot.send_message(room_id='someroomid', text='This is the text') 18 | 19 | # you can use either `room_id` will be given priority over `to_person_email` when both are used at the same time. 20 | ``` 21 | 22 | 23 | ## Attach files with message 24 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 25 | 26 | To attach a files when sending a message, do this: 27 | ```python 28 | from python_webex.v1.Bot import Bot 29 | 30 | bot = Bot() 31 | 32 | bot.send_message(room_id='room-id', text='I am sending a file', files=['https://image.shutterstock.com/image-photo/white-transparent-leaf-on-mirror-260nw-1029171697.jpg']) 33 | ``` 34 | 35 | Note the files parameter may be a list, but only one field is allowed. Why Exactly? No Idea, ask Webex. And also you can only keep URI's on the files field and not a path to a file in a local directory. 36 | 37 | 38 | ## Send message with Markdown 39 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 40 | 41 | When we want to style our messages e.g using bold, italivs and so on, we can use markups. Have a look here to read more about how markups work. 42 | 43 | So, this is how we can send a markup: 44 | 45 | ```python 46 | from python_webex.v1.Bot import Bot 47 | 48 | bot = Bot() 49 | 50 | markdown = """ 51 | *This is italic*\n 52 | **This is bold**\n 53 | `This looks like code` 54 | """ 55 | bot.send_markdown( 56 | to_person_email="some-email@gmail.com", markdown = markdown 57 | ) 58 | ``` 59 | 60 | 61 | ## Get messages 62 | 63 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 64 | 65 | This lists all the messages that have been received by the bot on a specific room. 66 | 67 | This is how we can get these details: 68 | 69 | ```python 70 | from python_webex.v1.Bot import Bot 71 | from pprint import pprint 72 | 73 | bot = Bot() 74 | 75 | pprint(bot.get_messages(room_id="room-id").json()) 76 | ``` 77 | 78 | ## Get Direct Messages 79 | 80 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 81 | 82 | Gets a list of all messages sent in 1:1 rooms. This is basically a list of all the bot's DMs with a particular individual, done by providing the person's ID. 83 | 84 | This is how this is done: 85 | 86 | ```python 87 | from python_webex.v1.Bot import Bot 88 | from pprint import pprint 89 | 90 | bot = Bot() 91 | 92 | pprint(bot.get_direct_messages(person_id="person-id").json()) 93 | ``` 94 | 95 | 96 | ## Get Message Details 97 | 98 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 99 | 100 | Gives you details about a specific message with ID message-id 101 | 102 | ```python 103 | from python_webex.v1.Bot import Bot 104 | from pprint import pprint 105 | 106 | bot = Bot() 107 | 108 | pprin(bot.get_message_details(message_id="message-id").json()) 109 | ``` 110 | 111 | ## Delete Message 112 | 113 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 114 | 115 | Deletes a specific message with ID message-id 116 | 117 | ```python 118 | from python_webex.v1.Bot import Bot 119 | from pprint import pprint 120 | 121 | bot = Bot() 122 | 123 | bot.delete_message(message_id='message-id') 124 | ``` 125 | 126 | ## Setup Webhook for incoming messages 127 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 128 | 129 | If your bot need to be constantly listening for incoming messages, you need to set up a webhook. To set up a webhook, you need an address that is available via the public internet. You can use ngrok for this. 130 | 131 | If you have ngrok downloaded, type `./ngrok http 5000` if you are on Linux. Otherwise, type `ngrok.exe http 5000` if you are on Windows. This will give you an output as such: 132 | 133 | ``` 134 | Session Expires 7 hours, 6 minutes 135 | Version 2.3.35 136 | Region United States (us) 137 | Web Interface http://127.0.0.1:4040 138 | Forwarding http://cff51342.ngrok.io -> http://localhost:5000 139 | Forwarding https://cff51342.ngrok.io -> http://localhost:5000 140 | ``` 141 | 142 | Now, you can open up your text editor and type in the following: 143 | 144 | ```python 145 | from python_webex.v1.Bot import Bot 146 | from python_webex import webhook 147 | 148 | bot = Bot() 149 | bot.create_webhook( 150 | name='some-normal-webhook', target_url='https://cff51342.ngrok.io', resource='messages', event='created' 151 | ) 152 | 153 | webhook.bot = bot 154 | 155 | if __name__ == "__main__": 156 | webhook.app.run(debug=True) 157 | ``` 158 | 159 | You willl now be constantly listening for any incoming message via the message that has been sent to the bot. 160 | 161 | ## Set default action for incoming message 162 | 163 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples 164 | on this tutorial.* 165 | 166 | When you receive a message, it normally comes with text. The following is how to set a default response whatever text is sent to the bot: 167 | 168 | ```python 169 | from python_webex.v1.Bot import Bot 170 | 171 | bot = Bot() 172 | 173 | @bot.on_hears("*") 174 | def default_on_hears_function(room_id=None): 175 | bot.send_message(room_id=room_id, text="This is the default response for the message that has been sent") 176 | ``` 177 | 178 | ## Set default listener for incoming files 179 | 180 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples 181 | on this tutorial.* 182 | 183 | You want your bot to receive files? So do many other people. 184 | 185 | We need to have a way to handle how these files are received and handled. So here is how we set the default function for handling incoming files: 186 | 187 | ```python 188 | from python_webex.v1.Bot import Bot 189 | from python_webex import webhook 190 | 191 | bot = Bot() 192 | 193 | @bot.set_default_file_response() 194 | def default_file_response(files, room_id=None): 195 | bot.send_message(room_id=room_id, text='this is the default response for an attached file') 196 | 197 | webhook.bot = bot 198 | 199 | if __name__ == "__main__": 200 | webhook.app.run(debug=True) 201 | 202 | ``` 203 | 204 | If you need to attach anything in the response, refer to the previous tutorial about attaching files with messages 205 | 206 | ## Set listener for specific text attached with file 207 | 208 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples 209 | on this tutorial.* 210 | 211 | What do you do when someone send you a file attached with a specific set of text, and you do know what you need your bot to do at this point: 212 | 213 | ```python 214 | from python_webex.v1.Bot import Bot 215 | from python_webex import webhook 216 | 217 | bot = Bot() 218 | 219 | @bot.set_file_action("This is me") 220 | def custom_response(room_id=None, files=None): 221 | print(files) 222 | bot.send_message(room_id=room_id, text="You look amazing") 223 | 224 | webhook.bot = bot 225 | 226 | if __name__ == "__main__": 227 | webhook.app.run(debug=True) 228 | 229 | ``` 230 | 231 | In `def custom_response(room_id=None, files=None):`, files represent a list of the files being sent to the bot. -------------------------------------------------------------------------------- /docs/docs/rooms.md: -------------------------------------------------------------------------------- 1 | # Rooms 2 | 3 | ## Get all rooms 4 | 5 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on here.* 6 | 7 | What we are aiming to do here is to get all the rooms that the bot is currently in. All from group rooms to individual rooms, we get all the details. Let us look at what we have to do: 8 | 9 | ``` 10 | from python_webex.v1.Bot import Bot 11 | 12 | bot = Bot() 13 | 14 | all_rooms_response = bot.get_all_rooms() 15 | 16 | all_rooms = all_rooms_response.json() 17 | 18 | print(all_rooms) 19 | 20 | ``` 21 | 22 | If everything works out fine you should see the following output: 23 | 24 | ``` 25 | { 26 | 'items': [ 27 | { 28 | 'title': 'room-title', 29 | 'ownerId': 'consumer', 30 | 'id': 'room-id', 31 | 'teamId': 'team-id', # this will show if it is a group room 32 | 'lastActivity': '2019-03-29T07:36:12.214Z', 33 | 'created': '2019-03-29T07:34:21.521Z', 34 | 'isLocked': False, 35 | 'creatorId': 'creator-id', 36 | 'type': 'group' 37 | } 38 | ] 39 | } 40 | ``` 41 | 42 | ## Get room details 43 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 44 | 45 | This gets the details of a specific room, we can use the output from here and get a single rooms ID. We will call the room ID room_id 46 | 47 | We will use this room_id to get the details of that specific room, here is how: 48 | 49 | ``` 50 | from python_webex.v1.Bot import Bot 51 | 52 | bot = Bot() 53 | 54 | room_id = 'someroomid' 55 | 56 | room_details_response = bot.get_room_details(room_id=room_id) 57 | 58 | room_details = room_details_response.json() 59 | 60 | print(room_details) 61 | 62 | ``` 63 | 64 | You should see an output similar to this: 65 | 66 | ``` 67 | { 68 | 'creatorId': 'creator-id', 69 | 'lastActivity': '2019-03-29T07:36:12.214Z', 70 | 'id': 'room-id', 71 | 'title': 'Discussion', 72 | 'created': '2019-03-29T07:34:21.521Z', 73 | 'type': 'group', 74 | 'ownerId': 'consumer', 75 | 'isLocked': False, 76 | 'teamId': 'team-id' # if the room is a team 77 | } 78 | 79 | ``` 80 | 81 | Use this information wisely. 82 | 83 | ## Create Room 84 | 85 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 86 | 87 | Some of the functionality for creating a room is still being worked on, bear with us. 88 | 89 | The following should work for creating a room: 90 | 91 | ``` 92 | from python_webex.v1.Bot import Bot 93 | 94 | bot = Bot() 95 | 96 | bot.create_room(title="Bot's room with best friend", team_id="team-id", room_type="something either 'direct' or 'group'") 97 | ``` 98 | 99 | ## Update Room Details 100 | 101 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 102 | 103 | Currently, we can only edit the title of a room. To do so, run the following script: 104 | 105 | ``` 106 | from python_webex.v1.Bot import Bot 107 | 108 | bot = Bot() 109 | 110 | room_id = 'room-id' 111 | 112 | bot.update_room_details(room_id=room_id, title='New Title') 113 | ``` 114 | 115 | ## Delete a room 116 | 117 | *Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.* 118 | 119 | Let us wreck some havock and delete a room. 120 | 121 | This can be done through: 122 | 123 | ``` 124 | from python_webex.v1.Bot import Bot 125 | 126 | bot = Bot() 127 | 128 | room_id = 'room-id' 129 | 130 | bot.delete_room(room_id=room_id) 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Python-Webex-Bot 2 | 3 | 4 | nav: 5 | - Rooms: rooms.md 6 | - Messages: messages.md 7 | - Cards: cards.md 8 | 9 | theme: 10 | include_sidebar: true 11 | name: readthedocs 12 | -------------------------------------------------------------------------------- /docs/site/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Python-Webex-Bot 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 51 | 52 |
53 | 58 |
59 |
60 |
    61 |
  • »
  • 62 |
  • 63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 |

404

72 | 73 |

Page not found

74 | 75 | 76 |
77 |
87 | 88 |
89 |
90 | 91 |
92 | 93 |
94 | 95 |
96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 106 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/site/cards/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Cards - Python-Webex-Bot 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 68 | 69 |
70 | 75 |
76 |
77 |
    78 |
  • »
  • 79 |
  • Cards
  • 80 |
  • 81 |
  • 82 |
83 |
84 |
85 |
86 |
87 | 88 |

Cards

89 |

Create & Send Blank Card

90 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

91 |

Cards are meant to increase interactivity during the chat. They can be used, for example to send a form that the bot would like an end user to respond to. In this instance, we are sending a blank card to the user, which is pretty much useless. This can

92 |
from python.webex.v1.Card import Card
 93 | from python.webex.v1.Bot import Bot
 94 | 
 95 | bot = Bot()
 96 | 
 97 | card = Card()
 98 | 
 99 | bot.send_card(card=card, room_id="room-id")
100 | 
101 |

Add text items on the card

102 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

103 |

The card we just sent above is pretty much useless. If we are to send a card, the user needs to be able to interact with the card and the bot should be able to read whatever has been input on the user side.

104 |
from python_webex.v1.Card import Card
105 | from python_webex.v1.Bot import Bot
106 | 
107 | bot = Bot()
108 | 
109 | bot.create_webhook(
110 |     name='attachment-response-2', target_url="[your-bot-url]/attachment-response", resource="attachmentActions", event="created"
111 | )
112 | 
113 | Card = Card()
114 | bot.send_card(card=card, room_id='room-id')
115 | 
116 |

Create Card Webhook

117 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

118 |

Here, we create a webhook for the card responses. For instance, if one fills a form that has been sent on a card, the response will be sent to the specific webhook.

119 |
from python_webex.v1.Card import Card
120 | from python_webex.v1.Bot import Bot
121 | 
122 | bot = Bot()
123 | 
124 | bot.create_webhook(
125 |     name='attachment-response-2', target_url="[your-bot-url]/attachment-response", resource="attachmentActions", event="created"
126 | )
127 | 
128 | 
129 |

Note: always make sure to setup this webhook to be whatever link you will be using and append /attachment-response to it. For example, if you are using 'https://abc.com', your value on target_url will be 'https://abc.com/attachment-response'

130 |

Listen for response on card

131 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

132 |

Now, what happens when the user has filled a card form and the response has been sent to the webhook, how do we get the information about the card that has been filled from our end?

133 |

Here is how:

134 |
from python_webex.v1.Card import Card
135 | from python_webex.v1.Bot imporrt Bot
136 | from pprint import pprint
137 | from python_webex import webhook
138 | 
139 | bot = Bot()
140 | card = Card()
141 | 
142 | 
143 | card.add_input_text(
144 |     input_id="first-name-input", input_placeholder="First Name"
145 | )
146 | 
147 | card.add_input_text(
148 |     input_id="last-name-input", input_placeholder="Last Name"
149 | )
150 | 
151 | card.add_submit_action_btn(
152 |     title="Submit"
153 | )
154 | 
155 | message = bot.send_card(card=card, room_id="room-id")
156 | message_id = message.json()['id']
157 | 
158 | @bot.attachment_response(message_id=message_id)
159 | def respond_to_card(msg):
160 |     pprint(msg)
161 | 
162 | webhook.bot = bot
163 | 
164 | if __name__ == "__main__":
165 |     webhook.app.run(debug=True)
166 | 
167 | 
168 | 169 |
170 |
183 | 184 |
185 |
186 | 187 |
188 | 189 |
190 | 191 |
192 | 193 | 194 | 195 | « Previous 196 | 197 | 198 | 199 |
200 | 201 | 202 | 203 | 204 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /docs/site/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/site/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/site/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/site/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/site/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/site/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/site/css/theme_extra.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Wrap inline code samples otherwise they shoot of the side and 3 | * can't be read at all. 4 | * 5 | * https://github.com/mkdocs/mkdocs/issues/313 6 | * https://github.com/mkdocs/mkdocs/issues/233 7 | * https://github.com/mkdocs/mkdocs/issues/834 8 | */ 9 | .rst-content code { 10 | white-space: pre-wrap; 11 | word-wrap: break-word; 12 | padding: 2px 5px; 13 | } 14 | 15 | /** 16 | * Make code blocks display as blocks and give them the appropriate 17 | * font size and padding. 18 | * 19 | * https://github.com/mkdocs/mkdocs/issues/855 20 | * https://github.com/mkdocs/mkdocs/issues/834 21 | * https://github.com/mkdocs/mkdocs/issues/233 22 | */ 23 | .rst-content pre code { 24 | white-space: pre; 25 | word-wrap: normal; 26 | display: block; 27 | padding: 12px; 28 | font-size: 12px; 29 | } 30 | 31 | /** 32 | * Fix code colors 33 | * 34 | * https://github.com/mkdocs/mkdocs/issues/2027 35 | */ 36 | .rst-content code { 37 | color: #E74C3C; 38 | } 39 | 40 | .rst-content pre code { 41 | color: #000; 42 | background: #f8f8f8; 43 | } 44 | 45 | /* 46 | * Fix link colors when the link text is inline code. 47 | * 48 | * https://github.com/mkdocs/mkdocs/issues/718 49 | */ 50 | a code { 51 | color: #2980B9; 52 | } 53 | a:hover code { 54 | color: #3091d1; 55 | } 56 | a:visited code { 57 | color: #9B59B6; 58 | } 59 | 60 | /* 61 | * The CSS classes from highlight.js seem to clash with the 62 | * ReadTheDocs theme causing some code to be incorrectly made 63 | * bold and italic. 64 | * 65 | * https://github.com/mkdocs/mkdocs/issues/411 66 | */ 67 | pre .cs, pre .c { 68 | font-weight: inherit; 69 | font-style: inherit; 70 | } 71 | 72 | /* 73 | * Fix some issues with the theme and non-highlighted code 74 | * samples. Without and highlighting styles attached the 75 | * formatting is broken. 76 | * 77 | * https://github.com/mkdocs/mkdocs/issues/319 78 | */ 79 | .rst-content .no-highlight { 80 | display: block; 81 | padding: 0.5em; 82 | color: #333; 83 | } 84 | 85 | 86 | /* 87 | * Additions specific to the search functionality provided by MkDocs 88 | */ 89 | 90 | .search-results { 91 | margin-top: 23px; 92 | } 93 | 94 | .search-results article { 95 | border-top: 1px solid #E1E4E5; 96 | padding-top: 24px; 97 | } 98 | 99 | .search-results article:first-child { 100 | border-top: none; 101 | } 102 | 103 | form .search-query { 104 | width: 100%; 105 | border-radius: 50px; 106 | padding: 6px 12px; /* csslint allow: box-model */ 107 | border-color: #D1D4D5; 108 | } 109 | 110 | /* 111 | * Improve inline code blocks within admonitions. 112 | * 113 | * https://github.com/mkdocs/mkdocs/issues/656 114 | */ 115 | .rst-content .admonition code { 116 | color: #404040; 117 | border: 1px solid #c7c9cb; 118 | border: 1px solid rgba(0, 0, 0, 0.2); 119 | background: #f8fbfd; 120 | background: rgba(255, 255, 255, 0.7); 121 | } 122 | 123 | /* 124 | * Account for wide tables which go off the side. 125 | * Override borders to avoid weirdness on narrow tables. 126 | * 127 | * https://github.com/mkdocs/mkdocs/issues/834 128 | * https://github.com/mkdocs/mkdocs/pull/1034 129 | */ 130 | .rst-content .section .docutils { 131 | width: 100%; 132 | overflow: auto; 133 | display: block; 134 | border: none; 135 | } 136 | 137 | td, th { 138 | border: 1px solid #e1e4e5 !important; /* csslint allow: important */ 139 | border-collapse: collapse; 140 | } 141 | 142 | /* 143 | * Without the following amendments, the navigation in the theme will be 144 | * slightly cut off. This is due to the fact that the .wy-nav-side has a 145 | * padding-bottom of 2em, which must not necessarily align with the font-size of 146 | * 90 % on the .rst-current-version container, combined with the padding of 12px 147 | * above and below. These amendments fix this in two steps: First, make sure the 148 | * .rst-current-version container has a fixed height of 40px, achieved using 149 | * line-height, and then applying a padding-bottom of 40px to this container. In 150 | * a second step, the items within that container are re-aligned using flexbox. 151 | * 152 | * https://github.com/mkdocs/mkdocs/issues/2012 153 | */ 154 | .wy-nav-side { 155 | padding-bottom: 40px; 156 | } 157 | 158 | /* 159 | * The second step of above amendment: Here we make sure the items are aligned 160 | * correctly within the .rst-current-version container. Using flexbox, we 161 | * achieve it in such a way that it will look like the following: 162 | * 163 | * [No repo_name] 164 | * Next >> // On the first page 165 | * << Previous Next >> // On all subsequent pages 166 | * 167 | * [With repo_name] 168 | * Next >> // On the first page 169 | * << Previous Next >> // On all subsequent pages 170 | * 171 | * https://github.com/mkdocs/mkdocs/issues/2012 172 | */ 173 | .rst-versions .rst-current-version { 174 | padding: 0 12px; 175 | display: flex; 176 | font-size: initial; 177 | justify-content: space-between; 178 | align-items: center; 179 | line-height: 40px; 180 | } 181 | 182 | /* 183 | * Please note that this amendment also involves removing certain inline-styles 184 | * from the file ./mkdocs/themes/readthedocs/versions.html. 185 | * 186 | * https://github.com/mkdocs/mkdocs/issues/2012 187 | */ 188 | .rst-current-version span { 189 | flex: 1; 190 | text-align: center; 191 | } 192 | -------------------------------------------------------------------------------- /docs/site/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/img/favicon.ico -------------------------------------------------------------------------------- /docs/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Python-Webex-Bot 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 58 | 59 |
60 | 65 |
66 |
67 |
    68 |
  • »
  • 69 |
  • python_webex_bot
  • 70 |
  • 71 |
  • 72 |
73 |
74 |
75 |
76 |
77 | 78 |

python_webex_bot

79 |

A python3 library meant to help you create a cisco webex teams bot and take advantage of some of the features available to these bots. 80 | Most of the python libraries setup for webex have been lacking in terms of connecting you to a webhook and this aims at solving that

81 |

Installation and setup

82 |

The following are items this documentation assumes you already have installed: 83 | - virtualenv 84 | - python3 85 | - ngrok

86 |

Step 1: setup the virtual environment

87 |

to initialize the virtual environment, run the following command in your Command Line or Command Prompt

88 |
virtualenv venv
 89 | 
90 |

then we activate it:

91 |

Windows

92 |
venv\Scripts\activate
 93 | 
94 |

Linux

95 |
source venv/bin/activate
 96 | 
97 |

and there, you have your virtual environment setup and ready for action

98 |

Step 2: install python_webex_bot

99 |

while still in your activated virtual environment, run the following command to install python_webex_bot via pip:

100 |
pip install python_webex_bot
101 | 
102 |

then download ngrok which will be used in the concurrent steps

103 |

Quickstart

104 |

Lets get a simple bot up, running and responsive on our local machine.

105 |

Step 1: Create the bot on Cisco Webex

106 |

If you haven't already, create your Webex account. 107 | Then head on to create your bot

108 |

You should be provided with an access token for the bot.

109 |

Take this access token and place it in your environment variable as auth_token.

110 |

this can be done via your Command prompt or Command Line as:

111 |
export auth_token=my_auth_token
112 | 
113 |

If you're on Windows, run the following on the command prompt:

114 |
set auth_token=my_auth_token
115 | 
116 |

replace my_auth_token with your bots access token

117 |

This is a crutial part of running your bot as the python_webex_bot library uses this to identify your bot

118 |

If you still have some questions on environment variables, why we need them and how to use them, this may be a good start

119 |

Step 2: setup ngrok

120 |

in a different terminal from the one used in steps 1 and 2, navigate to the folder where you have the ngrok placed.

121 |

Then run the following command:

122 |
ngrok http 5000
123 | 
124 |

This should produce an output similar to the one shown below:

125 |
Session Status                online
126 | Session Expires               7 hours, 59 minutes
127 | Update                        update available (version 2.3.25, Ctrl-U to update)
128 | Version                       2.3.18
129 | Region                        United States (us)
130 | Web Interface                 http://127.0.0.1:4040
131 | Forwarding                    http://87a942a1.ngrok.io -> http://localhost:5000
132 | Forwarding                    https://87a942a1.ngrok.io -> http://localhost:5000
133 | 
134 | Connections                   ttl     opn     rt1     rt5     p50     p90
135 |                               0       0       0.00    0.00    0.00    0.00
136 | 
137 |

Now you are ready for the quest

138 |

Step 3: create the python file and run it

139 |

Create a python file where you intend to run the bot. In my case, I will name my file run.py

140 |

copy and paste the following code:

141 |
from python_webex.v1.Bot import Bot
142 | from python_webex import webhook
143 | 
144 | bot = Bot()         # the program will automatically know the bot being referred to y the auth_token
145 | 
146 | # create a webhook to expose it to the internet
147 | # rememer that url we got from step 2, this is where we use it. In my case it was http://87a942a1.ngrok.io. 
148 | # We will be creating a webhook that will be listening when messages are sent
149 | bot.create_webhook(
150 |     name="quickstart_webhook", target_url="http://87a942a1.ngrok.io", resource="messages", event="created"
151 | )
152 | 
153 | # we create a function that responds when someone says hi
154 | # the room_id will automatically be filled with the webhook. Do not forget it
155 | @bot.on_hears("hi")
156 | def greet_back(room_id=None):
157 |     return bot.send_message(room_id=room_id, text="Hi, how are you doing?")
158 | 
159 | # We create a default response in case anyone types anything else that we have not set a response for
160 | # this is done using * [ don't ask me what happend when someone sends '*' as the message, that's on my TODO]
161 | @bot.on_hears("*")
162 | def default_response(room_id=None):
163 |     return bot.send_message(room_id=room_id, text="Sorry, could not understand that")
164 | 
165 | 
166 | # make the webhook know the bot to be listening for, and we are done
167 | webhook.bot = bot
168 | 
169 | if __name__ == "__main__":
170 |     webhook.app.run(debug=True)         # don't keep debug=True in production
171 | 
172 |

Now, when we text our bot "hi", it will respond with "Hi, how are you doing?"

173 |

And when we text anything else, like "When can we meet up?" it will respond with "Sorry, I could not understand that"

174 | 175 |
176 |
186 | 187 |
188 |
189 | 190 |
191 | 192 |
193 | 194 |
195 | 196 | 197 | 198 | 199 | 200 |
201 | 202 | 203 | 204 | 205 | 210 | 211 | 212 | 213 | 214 | 218 | -------------------------------------------------------------------------------- /docs/site/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); 5 | -------------------------------------------------------------------------------- /docs/site/js/theme.js: -------------------------------------------------------------------------------- 1 | /* sphinx_rtd_theme version 1.0.0 | MIT license */ 2 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t 2 | 3 | 4 | 5 | 6 | 7 | 8 | Messages - Python-Webex-Bot 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 82 | 83 |
84 | 89 |
90 |
91 |
    92 |
  • »
  • 93 |
  • Messages
  • 94 |
  • 95 |
  • 96 |
97 |
98 |
99 |
100 |
101 | 102 |

Messages

103 |

Create Message

104 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

105 |

Want to send a message from your bot to a specific room? No worries, here is how:

106 |
from python_webex.v1.Bot import Bot
107 | 
108 | bot = Bot()
109 | 
110 | bot.send_message(to_person_email='person-email@gmail.com', text='This is some text')
111 | # or
112 | bot.send_message(room_id='someroomid', text='This is the text')
113 | 
114 | # you can use either `room_id` will be given priority over `to_person_email` when both are used at the same time. 
115 | 
116 |

Attach files with message

117 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

118 |

To attach a files when sending a message, do this:

119 |
from python_webex.v1.Bot import Bot
120 | 
121 | bot = Bot()
122 | 
123 | bot.send_message(room_id='room-id', text='I am sending a file', files=['https://image.shutterstock.com/image-photo/white-transparent-leaf-on-mirror-260nw-1029171697.jpg'])
124 | 
125 |

Note the files parameter may be a list, but only one field is allowed. Why Exactly? No Idea, ask Webex. And also you can only keep URI's on the files field and not a path to a file in a local directory.

126 |

Send message with Markdown

127 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

128 |

When we want to style our messages e.g using bold, italivs and so on, we can use markups. Have a look here to read more about how markups work.

129 |

So, this is how we can send a markup:

130 |
from python_webex.v1.Bot import Bot
131 | 
132 | bot = Bot()
133 | 
134 | markdown = """
135 | *This is italic*\n
136 | **This is bold**\n
137 | `This looks like code`
138 | """
139 | bot.send_markdown(
140 |     to_person_email="some-email@gmail.com", markdown = markdown
141 | )
142 | 
143 |

Get messages

144 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

145 |

This lists all the messages that have been received by the bot on a specific room.

146 |

This is how we can get these details:

147 |
from python_webex.v1.Bot import Bot
148 | from pprint import pprint
149 | 
150 | bot = Bot()
151 | 
152 | pprint(bot.get_messages(room_id="room-id").json())
153 | 
154 |

Get Direct Messages

155 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

156 |

Gets a list of all messages sent in 1:1 rooms. This is basically a list of all the bot's DMs with a particular individual, done by providing the person's ID.

157 |

This is how this is done:

158 |
from python_webex.v1.Bot import Bot
159 | from pprint import pprint
160 | 
161 | bot = Bot()
162 | 
163 | pprint(bot.get_direct_messages(person_id="person-id").json())
164 | 
165 |

Get Message Details

166 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

167 |

Gives you details about a specific message with ID message-id

168 |
from python_webex.v1.Bot import Bot
169 | from pprint import pprint
170 | 
171 | bot = Bot()
172 | 
173 | pprin(bot.get_message_details(message_id="message-id").json())
174 | 
175 |

Delete Message

176 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

177 |

Deletes a specific message with ID message-id

178 |
from python_webex.v1.Bot import Bot
179 | from pprint import pprint
180 | 
181 | bot = Bot()
182 | 
183 | bot.delete_message(message_id='message-id')
184 | 
185 |

Setup Webhook for incoming messages

186 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

187 |

If your bot need to be constantly listening for incoming messages, you need to set up a webhook. To set up a webhook, you need an address that is available via the public internet. You can use ngrok for this.

188 |

If you have ngrok downloaded, type ./ngrok http 5000 if you are on Linux. Otherwise, type ngrok.exe http 5000 if you are on Windows. This will give you an output as such:

189 |
Session Expires               7 hours, 6 minutes                                
190 | Version                       2.3.35                                            
191 | Region                        United States (us)                                
192 | Web Interface                 http://127.0.0.1:4040                             
193 | Forwarding                    http://cff51342.ngrok.io -> http://localhost:5000 
194 | Forwarding                    https://cff51342.ngrok.io -> http://localhost:5000
195 | 
196 |

Now, you can open up your text editor and type in the following:

197 |
from python_webex.v1.Bot import Bot
198 | from python_webex import webhook
199 | 
200 | bot = Bot()
201 | bot.create_webhook(
202 |     name='some-normal-webhook', target_url='https://cff51342.ngrok.io', resource='messages', event='created'
203 | )
204 | 
205 | webhook.bot = bot
206 | 
207 | if __name__ == "__main__":
208 |     webhook.app.run(debug=True)
209 | 
210 |

You willl now be constantly listening for any incoming message via the message that has been sent to the bot.

211 |

Set default action for incoming message

212 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples 213 | on this tutorial.

214 |

When you receive a message, it normally comes with text. The following is how to set a default response whatever text is sent to the bot:

215 |
from python_webex.v1.Bot import Bot
216 | 
217 | bot = Bot()
218 | 
219 | @bot.on_hears("*")
220 | def default_on_hears_function(room_id=None):
221 |     bot.send_message(room_id=room_id, text="This is the default response for the message that has been sent")
222 | 
223 |

Set default listener for incoming files

224 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples 225 | on this tutorial.

226 |

You want your bot to receive files? So do many other people.

227 |

We need to have a way to handle how these files are received and handled. So here is how we set the default function for handling incoming files:

228 |
from python_webex.v1.Bot import Bot
229 | from python_webex import webhook
230 | 
231 | bot = Bot()
232 | 
233 | @bot.set_default_file_response()
234 | def default_file_response(files, room_id=None):
235 |     bot.send_message(room_id=room_id, text='this is the default response for an attached file')
236 | 
237 | webhook.bot = bot
238 | 
239 | if __name__ == "__main__":
240 |     webhook.app.run(debug=True)
241 | 
242 | 
243 |

If you need to attach anything in the response, refer to the previous tutorial about attaching files with messages

244 |

Set listener for specific text attached with file

245 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples 246 | on this tutorial.

247 |

What do you do when someone send you a file attached with a specific set of text, and you do know what you need your bot to do at this point:

248 |
from python_webex.v1.Bot import Bot
249 | from python_webex import webhook
250 | 
251 | bot = Bot()
252 | 
253 | @bot.set_file_action("This is me")
254 | def custom_response(room_id=None, files=None):
255 |     print(files)
256 |     bot.send_message(room_id=room_id, text="You look amazing")
257 | 
258 | webhook.bot = bot
259 | 
260 | if __name__ == "__main__":
261 |     webhook.app.run(debug=True)
262 | 
263 | 
264 |

In def custom_response(room_id=None, files=None):, files represent a list of the files being sent to the bot.

265 | 266 |
267 |
281 | 282 |
283 |
284 | 285 |
286 | 287 |
288 | 289 |
290 | 291 | 292 | 293 | « Previous 294 | 295 | 296 | Next » 297 | 298 | 299 |
300 | 301 | 302 | 303 | 304 | 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /docs/site/rooms/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Rooms - Python-Webex-Bot 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 70 | 71 |
72 | 77 |
78 |
79 |
    80 |
  • »
  • 81 |
  • Rooms
  • 82 |
  • 83 |
  • 84 |
85 |
86 |
87 |
88 |
89 | 90 |

Rooms

91 |

Get all rooms

92 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on here.

93 |

What we are aiming to do here is to get all the rooms that the bot is currently in. All from group rooms to individual rooms, we get all the details. Let us look at what we have to do:

94 |
from python_webex.v1.Bot import Bot
 95 | 
 96 | bot = Bot()
 97 | 
 98 | all_rooms_response = bot.get_all_rooms()
 99 | 
100 | all_rooms = all_rooms_response.json()
101 | 
102 | print(all_rooms)
103 | 
104 | 
105 |

If everything works out fine you should see the following output:

106 |
{
107 |     'items': [
108 |         {
109 |             'title': 'room-title', 
110 |             'ownerId': 'consumer', 
111 |             'id': 'room-id', 
112 |             'teamId': 'team-id', # this will show if it is a group room
113 |             'lastActivity': '2019-03-29T07:36:12.214Z', 
114 |             'created': '2019-03-29T07:34:21.521Z', 
115 |             'isLocked': False, 
116 |             'creatorId': 'creator-id', 
117 |             'type': 'group'
118 |         }
119 |     ]
120 | }
121 | 
122 |

Get room details

123 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

124 |

This gets the details of a specific room, we can use the output from here and get a single rooms ID. We will call the room ID room_id

125 |

We will use this room_id to get the details of that specific room, here is how:

126 |
from python_webex.v1.Bot import Bot
127 | 
128 | bot = Bot()
129 | 
130 | room_id = 'someroomid'
131 | 
132 | room_details_response = bot.get_room_details(room_id=room_id)
133 | 
134 | room_details = room_details_response.json()
135 | 
136 | print(room_details)
137 | 
138 | 
139 |

You should see an output similar to this:

140 |
{
141 |     'creatorId': 'creator-id', 
142 |     'lastActivity': '2019-03-29T07:36:12.214Z', 
143 |     'id': 'room-id', 
144 |     'title': 'Discussion', 
145 |     'created': '2019-03-29T07:34:21.521Z', 
146 |     'type': 'group', 
147 |     'ownerId': 'consumer', 
148 |     'isLocked': False, 
149 |     'teamId': 'team-id' # if the room is a team
150 | }
151 | 
152 | 
153 |

Use this information wisely.

154 |

Create Room

155 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

156 |

Some of the functionality for creating a room is still being worked on, bear with us.

157 |

The following should work for creating a room:

158 |
from python_webex.v1.Bot import Bot
159 | 
160 | bot = Bot()
161 | 
162 | bot.create_room(title="Bot's room with best friend", team_id="team-id", room_type="something either 'direct' or 'group'")
163 | 
164 |

Update Room Details

165 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

166 |

Currently, we can only edit the title of a room. To do so, run the following script:

167 |
from python_webex.v1.Bot import Bot
168 | 
169 | bot = Bot()
170 | 
171 | room_id = 'room-id'
172 | 
173 | bot.update_room_details(room_id=room_id, title='New Title')
174 | 
175 |

Delete a room

176 |

Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial.

177 |

Let us wreck some havock and delete a room.

178 |

This can be done through:

179 |
from python_webex.v1.Bot import Bot
180 | 
181 | bot = Bot()
182 | 
183 | room_id = 'room-id'
184 | 
185 | bot.delete_room(room_id=room_id)
186 | 
187 | 188 |
189 |
202 | 203 |
204 |
205 | 206 |
207 | 208 |
209 | 210 |
211 | 212 | 213 | 214 | 215 | Next » 216 | 217 | 218 |
219 | 220 | 221 | 222 | 223 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /docs/site/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Python-Webex-Bot 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 51 | 52 |
53 | 58 |
59 |
60 |
    61 |
  • »
  • 62 |
  • 63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 |

Search Results

72 | 73 | 77 | 78 |
79 | Searching... 80 |
81 | 82 | 83 |
84 |
94 | 95 |
96 |
97 | 98 |
99 | 100 |
101 | 102 |
103 | 104 | 105 | 106 | 107 | 108 |
109 | 110 | 111 | 112 | 113 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/site/search/main.js: -------------------------------------------------------------------------------- 1 | function getSearchTermFromLocation() { 2 | var sPageURL = window.location.search.substring(1); 3 | var sURLVariables = sPageURL.split('&'); 4 | for (var i = 0; i < sURLVariables.length; i++) { 5 | var sParameterName = sURLVariables[i].split('='); 6 | if (sParameterName[0] == 'q') { 7 | return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20')); 8 | } 9 | } 10 | } 11 | 12 | function joinUrl (base, path) { 13 | if (path.substring(0, 1) === "/") { 14 | // path starts with `/`. Thus it is absolute. 15 | return path; 16 | } 17 | if (base.substring(base.length-1) === "/") { 18 | // base ends with `/` 19 | return base + path; 20 | } 21 | return base + "/" + path; 22 | } 23 | 24 | function escapeHtml (value) { 25 | return value.replace(/&/g, '&') 26 | .replace(/"/g, '"') 27 | .replace(//g, '>'); 29 | } 30 | 31 | function formatResult (location, title, summary) { 32 | return ''; 33 | } 34 | 35 | function displayResults (results) { 36 | var search_results = document.getElementById("mkdocs-search-results"); 37 | while (search_results.firstChild) { 38 | search_results.removeChild(search_results.firstChild); 39 | } 40 | if (results.length > 0){ 41 | for (var i=0; i < results.length; i++){ 42 | var result = results[i]; 43 | var html = formatResult(result.location, result.title, result.summary); 44 | search_results.insertAdjacentHTML('beforeend', html); 45 | } 46 | } else { 47 | var noResultsText = search_results.getAttribute('data-no-results-text'); 48 | if (!noResultsText) { 49 | noResultsText = "No results found"; 50 | } 51 | search_results.insertAdjacentHTML('beforeend', '

' + noResultsText + '

'); 52 | } 53 | } 54 | 55 | function doSearch () { 56 | var query = document.getElementById('mkdocs-search-query').value; 57 | if (query.length > min_search_length) { 58 | if (!window.Worker) { 59 | displayResults(search(query)); 60 | } else { 61 | searchWorker.postMessage({query: query}); 62 | } 63 | } else { 64 | // Clear results for short queries 65 | displayResults([]); 66 | } 67 | } 68 | 69 | function initSearch () { 70 | var search_input = document.getElementById('mkdocs-search-query'); 71 | if (search_input) { 72 | search_input.addEventListener("keyup", doSearch); 73 | } 74 | var term = getSearchTermFromLocation(); 75 | if (term) { 76 | search_input.value = term; 77 | doSearch(); 78 | } 79 | } 80 | 81 | function onWorkerMessage (e) { 82 | if (e.data.allowSearch) { 83 | initSearch(); 84 | } else if (e.data.results) { 85 | var results = e.data.results; 86 | displayResults(results); 87 | } else if (e.data.config) { 88 | min_search_length = e.data.config.min_search_length-1; 89 | } 90 | } 91 | 92 | if (!window.Worker) { 93 | console.log('Web Worker API not supported'); 94 | // load index in main thread 95 | $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { 96 | console.log('Loaded worker'); 97 | init(); 98 | window.postMessage = function (msg) { 99 | onWorkerMessage({data: msg}); 100 | }; 101 | }).fail(function (jqxhr, settings, exception) { 102 | console.error('Could not load worker.js'); 103 | }); 104 | } else { 105 | // Wrap search in a web worker 106 | var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); 107 | searchWorker.postMessage({init: true}); 108 | searchWorker.onmessage = onWorkerMessage; 109 | } 110 | -------------------------------------------------------------------------------- /docs/site/search/search_index.json: -------------------------------------------------------------------------------- 1 | {"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"python_webex_bot A python3 library meant to help you create a cisco webex teams bot and take advantage of some of the features available to these bots. Most of the python libraries setup for webex have been lacking in terms of connecting you to a webhook and this aims at solving that Installation and setup The following are items this documentation assumes you already have installed: - virtualenv - python3 - ngrok Step 1: setup the virtual environment to initialize the virtual environment, run the following command in your Command Line or Command Prompt virtualenv venv then we activate it: Windows venv\\Scripts\\activate Linux source venv/bin/activate and there, you have your virtual environment setup and ready for action Step 2: install python_webex_bot while still in your activated virtual environment, run the following command to install python_webex_bot via pip: pip install python_webex_bot then download ngrok which will be used in the concurrent steps Quickstart Lets get a simple bot up, running and responsive on our local machine. Step 1: Create the bot on Cisco Webex If you haven't already, create your Webex account. Then head on to create your bot You should be provided with an access token for the bot. Take this access token and place it in your environment variable as auth_token. this can be done via your Command prompt or Command Line as: export auth_token=my_auth_token If you're on Windows , run the following on the command prompt: set auth_token=my_auth_token replace my_auth_token with your bots access token This is a crutial part of running your bot as the python_webex_bot library uses this to identify your bot If you still have some questions on environment variables, why we need them and how to use them, this may be a good start Step 2: setup ngrok in a different terminal from the one used in steps 1 and 2, navigate to the folder where you have the ngrok placed. Then run the following command: ngrok http 5000 This should produce an output similar to the one shown below: Session Status online Session Expires 7 hours, 59 minutes Update update available (version 2.3.25, Ctrl-U to update) Version 2.3.18 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://87a942a1.ngrok.io -> http://localhost:5000 Forwarding https://87a942a1.ngrok.io -> http://localhost:5000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00 Now you are ready for the quest Step 3: create the python file and run it Create a python file where you intend to run the bot. In my case, I will name my file run.py copy and paste the following code: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() # the program will automatically know the bot being referred to y the auth_token # create a webhook to expose it to the internet # rememer that url we got from step 2, this is where we use it. In my case it was http://87a942a1.ngrok.io. # We will be creating a webhook that will be listening when messages are sent bot.create_webhook( name=\"quickstart_webhook\", target_url=\"http://87a942a1.ngrok.io\", resource=\"messages\", event=\"created\" ) # we create a function that responds when someone says hi # the room_id will automatically be filled with the webhook. Do not forget it @bot.on_hears(\"hi\") def greet_back(room_id=None): return bot.send_message(room_id=room_id, text=\"Hi, how are you doing?\") # We create a default response in case anyone types anything else that we have not set a response for # this is done using * [ don't ask me what happend when someone sends '*' as the message, that's on my TODO] @bot.on_hears(\"*\") def default_response(room_id=None): return bot.send_message(room_id=room_id, text=\"Sorry, could not understand that\") # make the webhook know the bot to be listening for, and we are done webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) # don't keep debug=True in production Now, when we text our bot \"hi\", it will respond with \"Hi, how are you doing?\" And when we text anything else, like \"When can we meet up?\" it will respond with \"Sorry, I could not understand that\"","title":"python_webex_bot"},{"location":"#python_webex_bot","text":"A python3 library meant to help you create a cisco webex teams bot and take advantage of some of the features available to these bots. Most of the python libraries setup for webex have been lacking in terms of connecting you to a webhook and this aims at solving that","title":"python_webex_bot"},{"location":"#installation-and-setup","text":"The following are items this documentation assumes you already have installed: - virtualenv - python3 - ngrok","title":"Installation and setup"},{"location":"#step-1-setup-the-virtual-environment","text":"to initialize the virtual environment, run the following command in your Command Line or Command Prompt virtualenv venv then we activate it: Windows venv\\Scripts\\activate Linux source venv/bin/activate and there, you have your virtual environment setup and ready for action","title":"Step 1: setup the virtual environment"},{"location":"#step-2-install-python_webex_bot","text":"while still in your activated virtual environment, run the following command to install python_webex_bot via pip: pip install python_webex_bot then download ngrok which will be used in the concurrent steps","title":"Step 2: install python_webex_bot"},{"location":"#quickstart","text":"Lets get a simple bot up, running and responsive on our local machine.","title":"Quickstart"},{"location":"#step-1-create-the-bot-on-cisco-webex","text":"If you haven't already, create your Webex account. Then head on to create your bot You should be provided with an access token for the bot. Take this access token and place it in your environment variable as auth_token. this can be done via your Command prompt or Command Line as: export auth_token=my_auth_token If you're on Windows , run the following on the command prompt: set auth_token=my_auth_token replace my_auth_token with your bots access token This is a crutial part of running your bot as the python_webex_bot library uses this to identify your bot If you still have some questions on environment variables, why we need them and how to use them, this may be a good start","title":"Step 1: Create the bot on Cisco Webex"},{"location":"#step-2-setup-ngrok","text":"in a different terminal from the one used in steps 1 and 2, navigate to the folder where you have the ngrok placed. Then run the following command: ngrok http 5000 This should produce an output similar to the one shown below: Session Status online Session Expires 7 hours, 59 minutes Update update available (version 2.3.25, Ctrl-U to update) Version 2.3.18 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://87a942a1.ngrok.io -> http://localhost:5000 Forwarding https://87a942a1.ngrok.io -> http://localhost:5000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00 Now you are ready for the quest","title":"Step 2: setup ngrok"},{"location":"#step-3-create-the-python-file-and-run-it","text":"Create a python file where you intend to run the bot. In my case, I will name my file run.py copy and paste the following code: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() # the program will automatically know the bot being referred to y the auth_token # create a webhook to expose it to the internet # rememer that url we got from step 2, this is where we use it. In my case it was http://87a942a1.ngrok.io. # We will be creating a webhook that will be listening when messages are sent bot.create_webhook( name=\"quickstart_webhook\", target_url=\"http://87a942a1.ngrok.io\", resource=\"messages\", event=\"created\" ) # we create a function that responds when someone says hi # the room_id will automatically be filled with the webhook. Do not forget it @bot.on_hears(\"hi\") def greet_back(room_id=None): return bot.send_message(room_id=room_id, text=\"Hi, how are you doing?\") # We create a default response in case anyone types anything else that we have not set a response for # this is done using * [ don't ask me what happend when someone sends '*' as the message, that's on my TODO] @bot.on_hears(\"*\") def default_response(room_id=None): return bot.send_message(room_id=room_id, text=\"Sorry, could not understand that\") # make the webhook know the bot to be listening for, and we are done webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) # don't keep debug=True in production Now, when we text our bot \"hi\", it will respond with \"Hi, how are you doing?\" And when we text anything else, like \"When can we meet up?\" it will respond with \"Sorry, I could not understand that\"","title":"Step 3: create the python file and run it"},{"location":"cards/","text":"Cards Create & Send Blank Card Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Cards are meant to increase interactivity during the chat. They can be used, for example to send a form that the bot would like an end user to respond to. In this instance, we are sending a blank card to the user, which is pretty much useless. This can from python.webex.v1.Card import Card from python.webex.v1.Bot import Bot bot = Bot() card = Card() bot.send_card(card=card, room_id=\"room-id\") Add text items on the card Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. The card we just sent above is pretty much useless. If we are to send a card, the user needs to be able to interact with the card and the bot should be able to read whatever has been input on the user side. from python_webex.v1.Card import Card from python_webex.v1.Bot import Bot bot = Bot() bot.create_webhook( name='attachment-response-2', target_url=\"[your-bot-url]/attachment-response\", resource=\"attachmentActions\", event=\"created\" ) Card = Card() bot.send_card(card=card, room_id='room-id') Create Card Webhook Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Here, we create a webhook for the card responses. For instance, if one fills a form that has been sent on a card, the response will be sent to the specific webhook. from python_webex.v1.Card import Card from python_webex.v1.Bot import Bot bot = Bot() bot.create_webhook( name='attachment-response-2', target_url=\"[your-bot-url]/attachment-response\", resource=\"attachmentActions\", event=\"created\" ) Note: always make sure to setup this webhook to be whatever link you will be using and append /attachment-response to it. For example, if you are using 'https://abc.com', your value on target_url will be 'https://abc.com/attachment-response' Listen for response on card Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Now, what happens when the user has filled a card form and the response has been sent to the webhook, how do we get the information about the card that has been filled from our end? Here is how: from python_webex.v1.Card import Card from python_webex.v1.Bot imporrt Bot from pprint import pprint from python_webex import webhook bot = Bot() card = Card() card.add_input_text( input_id=\"first-name-input\", input_placeholder=\"First Name\" ) card.add_input_text( input_id=\"last-name-input\", input_placeholder=\"Last Name\" ) card.add_submit_action_btn( title=\"Submit\" ) message = bot.send_card(card=card, room_id=\"room-id\") message_id = message.json()['id'] @bot.attachment_response(message_id=message_id) def respond_to_card(msg): pprint(msg) webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True)","title":"Cards"},{"location":"cards/#cards","text":"","title":"Cards"},{"location":"cards/#create-send-blank-card","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Cards are meant to increase interactivity during the chat. They can be used, for example to send a form that the bot would like an end user to respond to. In this instance, we are sending a blank card to the user, which is pretty much useless. This can from python.webex.v1.Card import Card from python.webex.v1.Bot import Bot bot = Bot() card = Card() bot.send_card(card=card, room_id=\"room-id\")","title":"Create & Send Blank Card"},{"location":"cards/#add-text-items-on-the-card","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. The card we just sent above is pretty much useless. If we are to send a card, the user needs to be able to interact with the card and the bot should be able to read whatever has been input on the user side. from python_webex.v1.Card import Card from python_webex.v1.Bot import Bot bot = Bot() bot.create_webhook( name='attachment-response-2', target_url=\"[your-bot-url]/attachment-response\", resource=\"attachmentActions\", event=\"created\" ) Card = Card() bot.send_card(card=card, room_id='room-id')","title":"Add text items on the card"},{"location":"cards/#create-card-webhook","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Here, we create a webhook for the card responses. For instance, if one fills a form that has been sent on a card, the response will be sent to the specific webhook. from python_webex.v1.Card import Card from python_webex.v1.Bot import Bot bot = Bot() bot.create_webhook( name='attachment-response-2', target_url=\"[your-bot-url]/attachment-response\", resource=\"attachmentActions\", event=\"created\" ) Note: always make sure to setup this webhook to be whatever link you will be using and append /attachment-response to it. For example, if you are using 'https://abc.com', your value on target_url will be 'https://abc.com/attachment-response'","title":"Create Card Webhook"},{"location":"cards/#listen-for-response-on-card","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Now, what happens when the user has filled a card form and the response has been sent to the webhook, how do we get the information about the card that has been filled from our end? Here is how: from python_webex.v1.Card import Card from python_webex.v1.Bot imporrt Bot from pprint import pprint from python_webex import webhook bot = Bot() card = Card() card.add_input_text( input_id=\"first-name-input\", input_placeholder=\"First Name\" ) card.add_input_text( input_id=\"last-name-input\", input_placeholder=\"Last Name\" ) card.add_submit_action_btn( title=\"Submit\" ) message = bot.send_card(card=card, room_id=\"room-id\") message_id = message.json()['id'] @bot.attachment_response(message_id=message_id) def respond_to_card(msg): pprint(msg) webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True)","title":"Listen for response on card"},{"location":"messages/","text":"Messages Create Message Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Want to send a message from your bot to a specific room? No worries, here is how: from python_webex.v1.Bot import Bot bot = Bot() bot.send_message(to_person_email='person-email@gmail.com', text='This is some text') # or bot.send_message(room_id='someroomid', text='This is the text') # you can use either `room_id` will be given priority over `to_person_email` when both are used at the same time. Attach files with message Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. To attach a files when sending a message, do this: from python_webex.v1.Bot import Bot bot = Bot() bot.send_message(room_id='room-id', text='I am sending a file', files=['https://image.shutterstock.com/image-photo/white-transparent-leaf-on-mirror-260nw-1029171697.jpg']) Note the files parameter may be a list, but only one field is allowed. Why Exactly? No Idea, ask Webex. And also you can only keep URI's on the files field and not a path to a file in a local directory. Send message with Markdown Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. When we want to style our messages e.g using bold, italivs and so on, we can use markups. Have a look here to read more about how markups work. So, this is how we can send a markup: from python_webex.v1.Bot import Bot bot = Bot() markdown = \"\"\" *This is italic*\\n **This is bold**\\n `This looks like code` \"\"\" bot.send_markdown( to_person_email=\"some-email@gmail.com\", markdown = markdown ) Get messages Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. This lists all the messages that have been received by the bot on a specific room. This is how we can get these details: from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() pprint(bot.get_messages(room_id=\"room-id\").json()) Get Direct Messages Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Gets a list of all messages sent in 1:1 rooms. This is basically a list of all the bot's DMs with a particular individual, done by providing the person's ID. This is how this is done: from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() pprint(bot.get_direct_messages(person_id=\"person-id\").json()) Get Message Details Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Gives you details about a specific message with ID message-id from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() pprin(bot.get_message_details(message_id=\"message-id\").json()) Delete Message Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Deletes a specific message with ID message-id from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() bot.delete_message(message_id='message-id') Setup Webhook for incoming messages Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. If your bot need to be constantly listening for incoming messages, you need to set up a webhook. To set up a webhook, you need an address that is available via the public internet. You can use ngrok for this. If you have ngrok downloaded, type ./ngrok http 5000 if you are on Linux . Otherwise, type ngrok.exe http 5000 if you are on Windows . This will give you an output as such: Session Expires 7 hours, 6 minutes Version 2.3.35 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://cff51342.ngrok.io -> http://localhost:5000 Forwarding https://cff51342.ngrok.io -> http://localhost:5000 Now, you can open up your text editor and type in the following: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() bot.create_webhook( name='some-normal-webhook', target_url='https://cff51342.ngrok.io', resource='messages', event='created' ) webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) You willl now be constantly listening for any incoming message via the message that has been sent to the bot. Set default action for incoming message Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. When you receive a message, it normally comes with text. The following is how to set a default response whatever text is sent to the bot: from python_webex.v1.Bot import Bot bot = Bot() @bot.on_hears(\"*\") def default_on_hears_function(room_id=None): bot.send_message(room_id=room_id, text=\"This is the default response for the message that has been sent\") Set default listener for incoming files Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. You want your bot to receive files? So do many other people. We need to have a way to handle how these files are received and handled. So here is how we set the default function for handling incoming files: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() @bot.set_default_file_response() def default_file_response(files, room_id=None): bot.send_message(room_id=room_id, text='this is the default response for an attached file') webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) If you need to attach anything in the response, refer to the previous tutorial about attaching files with messages Set listener for specific text attached with file Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. What do you do when someone send you a file attached with a specific set of text, and you do know what you need your bot to do at this point: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() @bot.set_file_action(\"This is me\") def custom_response(room_id=None, files=None): print(files) bot.send_message(room_id=room_id, text=\"You look amazing\") webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) In def custom_response(room_id=None, files=None): , files represent a list of the files being sent to the bot.","title":"Messages"},{"location":"messages/#messages","text":"","title":"Messages"},{"location":"messages/#create-message","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Want to send a message from your bot to a specific room? No worries, here is how: from python_webex.v1.Bot import Bot bot = Bot() bot.send_message(to_person_email='person-email@gmail.com', text='This is some text') # or bot.send_message(room_id='someroomid', text='This is the text') # you can use either `room_id` will be given priority over `to_person_email` when both are used at the same time.","title":"Create Message"},{"location":"messages/#attach-files-with-message","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. To attach a files when sending a message, do this: from python_webex.v1.Bot import Bot bot = Bot() bot.send_message(room_id='room-id', text='I am sending a file', files=['https://image.shutterstock.com/image-photo/white-transparent-leaf-on-mirror-260nw-1029171697.jpg']) Note the files parameter may be a list, but only one field is allowed. Why Exactly? No Idea, ask Webex. And also you can only keep URI's on the files field and not a path to a file in a local directory.","title":"Attach files with message"},{"location":"messages/#send-message-with-markdown","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. When we want to style our messages e.g using bold, italivs and so on, we can use markups. Have a look here to read more about how markups work. So, this is how we can send a markup: from python_webex.v1.Bot import Bot bot = Bot() markdown = \"\"\" *This is italic*\\n **This is bold**\\n `This looks like code` \"\"\" bot.send_markdown( to_person_email=\"some-email@gmail.com\", markdown = markdown )","title":"Send message with Markdown"},{"location":"messages/#get-messages","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. This lists all the messages that have been received by the bot on a specific room. This is how we can get these details: from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() pprint(bot.get_messages(room_id=\"room-id\").json())","title":"Get messages"},{"location":"messages/#get-direct-messages","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Gets a list of all messages sent in 1:1 rooms. This is basically a list of all the bot's DMs with a particular individual, done by providing the person's ID. This is how this is done: from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() pprint(bot.get_direct_messages(person_id=\"person-id\").json())","title":"Get Direct Messages"},{"location":"messages/#get-message-details","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Gives you details about a specific message with ID message-id from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() pprin(bot.get_message_details(message_id=\"message-id\").json())","title":"Get Message Details"},{"location":"messages/#delete-message","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Deletes a specific message with ID message-id from python_webex.v1.Bot import Bot from pprint import pprint bot = Bot() bot.delete_message(message_id='message-id')","title":"Delete Message"},{"location":"messages/#setup-webhook-for-incoming-messages","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. If your bot need to be constantly listening for incoming messages, you need to set up a webhook. To set up a webhook, you need an address that is available via the public internet. You can use ngrok for this. If you have ngrok downloaded, type ./ngrok http 5000 if you are on Linux . Otherwise, type ngrok.exe http 5000 if you are on Windows . This will give you an output as such: Session Expires 7 hours, 6 minutes Version 2.3.35 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://cff51342.ngrok.io -> http://localhost:5000 Forwarding https://cff51342.ngrok.io -> http://localhost:5000 Now, you can open up your text editor and type in the following: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() bot.create_webhook( name='some-normal-webhook', target_url='https://cff51342.ngrok.io', resource='messages', event='created' ) webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) You willl now be constantly listening for any incoming message via the message that has been sent to the bot.","title":"Setup Webhook for incoming messages"},{"location":"messages/#set-default-action-for-incoming-message","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. When you receive a message, it normally comes with text. The following is how to set a default response whatever text is sent to the bot: from python_webex.v1.Bot import Bot bot = Bot() @bot.on_hears(\"*\") def default_on_hears_function(room_id=None): bot.send_message(room_id=room_id, text=\"This is the default response for the message that has been sent\")","title":"Set default action for incoming message"},{"location":"messages/#set-default-listener-for-incoming-files","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. You want your bot to receive files? So do many other people. We need to have a way to handle how these files are received and handled. So here is how we set the default function for handling incoming files: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() @bot.set_default_file_response() def default_file_response(files, room_id=None): bot.send_message(room_id=room_id, text='this is the default response for an attached file') webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) If you need to attach anything in the response, refer to the previous tutorial about attaching files with messages","title":"Set default listener for incoming files"},{"location":"messages/#set-listener-for-specific-text-attached-with-file","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. What do you do when someone send you a file attached with a specific set of text, and you do know what you need your bot to do at this point: from python_webex.v1.Bot import Bot from python_webex import webhook bot = Bot() @bot.set_file_action(\"This is me\") def custom_response(room_id=None, files=None): print(files) bot.send_message(room_id=room_id, text=\"You look amazing\") webhook.bot = bot if __name__ == \"__main__\": webhook.app.run(debug=True) In def custom_response(room_id=None, files=None): , files represent a list of the files being sent to the bot.","title":"Set listener for specific text attached with file"},{"location":"rooms/","text":"Rooms Get all rooms Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on here. What we are aiming to do here is to get all the rooms that the bot is currently in. All from group rooms to individual rooms, we get all the details. Let us look at what we have to do: from python_webex.v1.Bot import Bot bot = Bot() all_rooms_response = bot.get_all_rooms() all_rooms = all_rooms_response.json() print(all_rooms) If everything works out fine you should see the following output: { 'items': [ { 'title': 'room-title', 'ownerId': 'consumer', 'id': 'room-id', 'teamId': 'team-id', # this will show if it is a group room 'lastActivity': '2019-03-29T07:36:12.214Z', 'created': '2019-03-29T07:34:21.521Z', 'isLocked': False, 'creatorId': 'creator-id', 'type': 'group' } ] } Get room details Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. This gets the details of a specific room, we can use the output from here and get a single rooms ID. We will call the room ID room_id We will use this room_id to get the details of that specific room, here is how: from python_webex.v1.Bot import Bot bot = Bot() room_id = 'someroomid' room_details_response = bot.get_room_details(room_id=room_id) room_details = room_details_response.json() print(room_details) You should see an output similar to this: { 'creatorId': 'creator-id', 'lastActivity': '2019-03-29T07:36:12.214Z', 'id': 'room-id', 'title': 'Discussion', 'created': '2019-03-29T07:34:21.521Z', 'type': 'group', 'ownerId': 'consumer', 'isLocked': False, 'teamId': 'team-id' # if the room is a team } Use this information wisely. Create Room Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Some of the functionality for creating a room is still being worked on, bear with us. The following should work for creating a room: from python_webex.v1.Bot import Bot bot = Bot() bot.create_room(title=\"Bot's room with best friend\", team_id=\"team-id\", room_type=\"something either 'direct' or 'group'\") Update Room Details Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Currently, we can only edit the title of a room. To do so, run the following script: from python_webex.v1.Bot import Bot bot = Bot() room_id = 'room-id' bot.update_room_details(room_id=room_id, title='New Title') Delete a room Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Let us wreck some havock and delete a room. This can be done through: from python_webex.v1.Bot import Bot bot = Bot() room_id = 'room-id' bot.delete_room(room_id=room_id)","title":"Rooms"},{"location":"rooms/#rooms","text":"","title":"Rooms"},{"location":"rooms/#get-all-rooms","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on here. What we are aiming to do here is to get all the rooms that the bot is currently in. All from group rooms to individual rooms, we get all the details. Let us look at what we have to do: from python_webex.v1.Bot import Bot bot = Bot() all_rooms_response = bot.get_all_rooms() all_rooms = all_rooms_response.json() print(all_rooms) If everything works out fine you should see the following output: { 'items': [ { 'title': 'room-title', 'ownerId': 'consumer', 'id': 'room-id', 'teamId': 'team-id', # this will show if it is a group room 'lastActivity': '2019-03-29T07:36:12.214Z', 'created': '2019-03-29T07:34:21.521Z', 'isLocked': False, 'creatorId': 'creator-id', 'type': 'group' } ] }","title":"Get all rooms"},{"location":"rooms/#get-room-details","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. This gets the details of a specific room, we can use the output from here and get a single rooms ID. We will call the room ID room_id We will use this room_id to get the details of that specific room, here is how: from python_webex.v1.Bot import Bot bot = Bot() room_id = 'someroomid' room_details_response = bot.get_room_details(room_id=room_id) room_details = room_details_response.json() print(room_details) You should see an output similar to this: { 'creatorId': 'creator-id', 'lastActivity': '2019-03-29T07:36:12.214Z', 'id': 'room-id', 'title': 'Discussion', 'created': '2019-03-29T07:34:21.521Z', 'type': 'group', 'ownerId': 'consumer', 'isLocked': False, 'teamId': 'team-id' # if the room is a team } Use this information wisely.","title":"Get room details"},{"location":"rooms/#create-room","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Some of the functionality for creating a room is still being worked on, bear with us. The following should work for creating a room: from python_webex.v1.Bot import Bot bot = Bot() bot.create_room(title=\"Bot's room with best friend\", team_id=\"team-id\", room_type=\"something either 'direct' or 'group'\")","title":"Create Room"},{"location":"rooms/#update-room-details","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Currently, we can only edit the title of a room. To do so, run the following script: from python_webex.v1.Bot import Bot bot = Bot() room_id = 'room-id' bot.update_room_details(room_id=room_id, title='New Title')","title":"Update Room Details"},{"location":"rooms/#delete-a-room","text":"Always remember that you need to have already set the value auth_token as your bot's Access token before you run this any of the other examples on this tutorial. Let us wreck some havock and delete a room. This can be done through: from python_webex.v1.Bot import Bot bot = Bot() room_id = 'room-id' bot.delete_room(room_id=room_id)","title":"Delete a room"}]} -------------------------------------------------------------------------------- /docs/site/search/worker.js: -------------------------------------------------------------------------------- 1 | var base_path = 'function' === typeof importScripts ? '.' : '/search/'; 2 | var allowSearch = false; 3 | var index; 4 | var documents = {}; 5 | var lang = ['en']; 6 | var data; 7 | 8 | function getScript(script, callback) { 9 | console.log('Loading script: ' + script); 10 | $.getScript(base_path + script).done(function () { 11 | callback(); 12 | }).fail(function (jqxhr, settings, exception) { 13 | console.log('Error: ' + exception); 14 | }); 15 | } 16 | 17 | function getScriptsInOrder(scripts, callback) { 18 | if (scripts.length === 0) { 19 | callback(); 20 | return; 21 | } 22 | getScript(scripts[0], function() { 23 | getScriptsInOrder(scripts.slice(1), callback); 24 | }); 25 | } 26 | 27 | function loadScripts(urls, callback) { 28 | if( 'function' === typeof importScripts ) { 29 | importScripts.apply(null, urls); 30 | callback(); 31 | } else { 32 | getScriptsInOrder(urls, callback); 33 | } 34 | } 35 | 36 | function onJSONLoaded () { 37 | data = JSON.parse(this.responseText); 38 | var scriptsToLoad = ['lunr.js']; 39 | if (data.config && data.config.lang && data.config.lang.length) { 40 | lang = data.config.lang; 41 | } 42 | if (lang.length > 1 || lang[0] !== "en") { 43 | scriptsToLoad.push('lunr.stemmer.support.js'); 44 | if (lang.length > 1) { 45 | scriptsToLoad.push('lunr.multi.js'); 46 | } 47 | if (lang.includes("ja") || lang.includes("jp")) { 48 | scriptsToLoad.push('tinyseg.js'); 49 | } 50 | for (var i=0; i < lang.length; i++) { 51 | if (lang[i] != 'en') { 52 | scriptsToLoad.push(['lunr', lang[i], 'js'].join('.')); 53 | } 54 | } 55 | } 56 | loadScripts(scriptsToLoad, onScriptsLoaded); 57 | } 58 | 59 | function onScriptsLoaded () { 60 | console.log('All search scripts loaded, building Lunr index...'); 61 | if (data.config && data.config.separator && data.config.separator.length) { 62 | lunr.tokenizer.separator = new RegExp(data.config.separator); 63 | } 64 | 65 | if (data.index) { 66 | index = lunr.Index.load(data.index); 67 | data.docs.forEach(function (doc) { 68 | documents[doc.location] = doc; 69 | }); 70 | console.log('Lunr pre-built index loaded, search ready'); 71 | } else { 72 | index = lunr(function () { 73 | if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) { 74 | this.use(lunr[lang[0]]); 75 | } else if (lang.length > 1) { 76 | this.use(lunr.multiLanguage.apply(null, lang)); // spread operator not supported in all browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility 77 | } 78 | this.field('title'); 79 | this.field('text'); 80 | this.ref('location'); 81 | 82 | for (var i=0; i < data.docs.length; i++) { 83 | var doc = data.docs[i]; 84 | this.add(doc); 85 | documents[doc.location] = doc; 86 | } 87 | }); 88 | console.log('Lunr index built, search ready'); 89 | } 90 | allowSearch = true; 91 | postMessage({config: data.config}); 92 | postMessage({allowSearch: allowSearch}); 93 | } 94 | 95 | function init () { 96 | var oReq = new XMLHttpRequest(); 97 | oReq.addEventListener("load", onJSONLoaded); 98 | var index_path = base_path + '/search_index.json'; 99 | if( 'function' === typeof importScripts ){ 100 | index_path = 'search_index.json'; 101 | } 102 | oReq.open("GET", index_path); 103 | oReq.send(); 104 | } 105 | 106 | function search (query) { 107 | if (!allowSearch) { 108 | console.error('Assets for search still loading'); 109 | return; 110 | } 111 | 112 | var resultDocuments = []; 113 | var results = index.search(query); 114 | for (var i=0; i < results.length; i++){ 115 | var result = results[i]; 116 | doc = documents[result.ref]; 117 | doc.summary = doc.text.substring(0, 200); 118 | resultDocuments.push(doc); 119 | } 120 | return resultDocuments; 121 | } 122 | 123 | if( 'function' === typeof importScripts ) { 124 | onmessage = function (e) { 125 | if (e.data.init) { 126 | init(); 127 | } else if (e.data.query) { 128 | postMessage({ results: search(e.data.query) }); 129 | } else { 130 | console.error("Worker - Unrecognized message: " + e); 131 | } 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /docs/site/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | None 5 | 2022-09-11 6 | daily 7 | 8 | 9 | None 10 | 2022-09-11 11 | daily 12 | 13 | 14 | None 15 | 2022-09-11 16 | daily 17 | 18 | 19 | None 20 | 2022-09-11 21 | daily 22 | 23 | -------------------------------------------------------------------------------- /docs/site/sitemap.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/docs/site/sitemap.xml.gz -------------------------------------------------------------------------------- /python_webex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/python_webex/__init__.py -------------------------------------------------------------------------------- /python_webex/v1/Bot.py: -------------------------------------------------------------------------------- 1 | from python_webex.v1.People import People 2 | from python_webex.v1.Room import Room 3 | from python_webex.v1.Webhook import Webhook 4 | from python_webex.v1.Message import Message 5 | 6 | import os 7 | import sys 8 | 9 | 10 | class Bot(People, Room, Webhook, Message): 11 | 12 | def __init__(self, auth_token=None): 13 | 14 | # declare headers and how the token will be gotten from the system 15 | self.URL = "https://webexapis.com/v1/" 16 | 17 | # looks for if the auth_token has been set in the initializer. 18 | # If not, goes looks for the `auth_token` or `AUTH_TOKEN` environment variable 19 | self.auth_token = auth_token if auth_token else os.getenv("auth_token") 20 | self.auth_token = self.auth_token if self.auth_token else os.getenv("AUTH_TOKEN") 21 | 22 | 23 | if self.auth_token == None: 24 | print("The auth_token needs to be specified for us to identify the bot being specified.") 25 | print("This can be done through: ") 26 | print(" 1. specifying in the intiializer. Bot('auth_token')") 27 | print(" 2. specifying in your environment vairiables. How environment variables are defined depends on your OS") 28 | sys.exit() 29 | 30 | self.headers = { 31 | "Authorization": "Bearer " + self.auth_token, 32 | "Content-Type": "application/json" 33 | } 34 | 35 | # self.hears to function maps when a specific word is heard to a function 36 | # for example, when one says 'hi' and you want to map it to say_hi() function 37 | self.hears_to_function = { 38 | 39 | } 40 | 41 | self.hears_file_to_function = { 42 | 43 | } 44 | 45 | self.attachment_response_to_function = { 46 | 47 | } 48 | 49 | # default attachment variable will hold the function that is supposed to be the 50 | # default action whenever an attachment is sent to the bot 51 | self.default_attachment = None 52 | 53 | # maps what will happen when a file is received with a particular type of text 54 | self.hears_file_to_function = { 55 | 56 | } 57 | 58 | self.attach_function = None 59 | 60 | 61 | """ 62 | decorator meant to do a specific action when called 63 | """ 64 | def on_hears(self, message_text): 65 | def hear_decorator(f): 66 | self.hears_to_function[message_text] = f 67 | return hear_decorator 68 | 69 | """ 70 | decorator waiting for any attachment that has been sent to the bot 71 | """ 72 | def set_default_file_response(self): 73 | def hear_default_file_attachment(f): 74 | self.default_attachment = f 75 | return hear_default_file_attachment 76 | 77 | """ 78 | decorator for when a file is received with a particular type of text. 79 | """ 80 | def set_file_action(self, message_text: str): 81 | def hear_file_attachment(f): 82 | self.hears_file_to_function[message_text] = f 83 | return hear_file_attachment 84 | 85 | """ 86 | decorator waiting for an attachment to be filled and returned for it to do a specific action 87 | """ 88 | def attachment_response(self, message_id: str): 89 | def response_decorator(f): 90 | self.attachment_response_to_function[message_id] = f 91 | return response_decorator 92 | -------------------------------------------------------------------------------- /python_webex/v1/Card.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import requests 3 | 4 | 5 | """ 6 | Cards give a better level of interactivity to the Webex Platform. 7 | They can allow us to create form like interactive interfaces from the bot. 8 | """ 9 | 10 | class Card: 11 | def __init__(self): 12 | self.content = [ 13 | { 14 | "contentType": "application/vnd.microsoft.card.adaptive", 15 | "content": { 16 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 17 | "type": "AdaptiveCard", 18 | "version": "1.0", 19 | "body": [ 20 | { 21 | "type": "ColumnSet", 22 | "columns": [ 23 | { 24 | "type": "Column", 25 | "width": 3, 26 | "items": [] 27 | }] 28 | } 29 | ], 30 | "actions": [ 31 | ] 32 | } 33 | } 34 | ] 35 | 36 | def __add_element(self, element_dict): 37 | self.content[0]["content"]["body"][0]["columns"][0]["items"].append(element_dict) 38 | 39 | def __add_action(self, action_dict): 40 | self.content[0]["content"]["actions"].append(action_dict) 41 | 42 | def get_all_input_ids(self): 43 | inputs = [] 44 | items = self.content[0]["content"]["body"][0]["columns"][0]["items"] 45 | for item in items: 46 | if "id" in item.keys(): 47 | inputs.append(item["id"]) 48 | return inputs 49 | 50 | def check_if_id_exists(self, id): 51 | ids = self.get_all_input_ids() 52 | items = self.content[0]["content"]["body"][0]["columns"][0]["items"] 53 | if id in ids: 54 | for item in items: 55 | if item['id'] == id: 56 | items.remove(item) 57 | 58 | def check_if_submit_button_exists(self): 59 | actions = self.content[0]["content"]["actions"] 60 | for action in actions: 61 | if action["type"] == "Action.Submit": 62 | actions.remove(action) 63 | 64 | 65 | """ 66 | Textblock is essentially normal text in html. as how you would put a

Here is the text

67 | The TextBlock attributes and their explanations can be found at: https://adaptivecards.io/schemas/adaptive-card.json#/definitions/TextBlock 68 | """ 69 | def add_text_block( 70 | self, text: str, is_subtle: bool = False, size: str = "default", weight: str = "default", wrap: bool = False 71 | ): 72 | element = { 73 | "type": "TextBlock", 74 | "text": text, 75 | "isSubtle": is_subtle, 76 | "size": size, 77 | "weight": weight, 78 | "wrap": wrap 79 | } 80 | self.__add_element(element) 81 | 82 | 83 | """ 84 | add_input_text() adds the traditional text fields as they are used in normal forms. interprated as when it comes to html 85 | The various attribute values can be found at: https://adaptivecards.io/schemas/adaptive-card.json#/definitions/Input.Text 86 | Have a look at the URL above for more details on how the field is used 87 | """ 88 | def add_input_text( 89 | self, input_id:str, input_is_multiline:bool = False, input_max_length:int = None, input_placeholder: str = None, input_value: str = None 90 | ): 91 | self.check_if_id_exists(input_id) 92 | element = { 93 | "id": input_id, 94 | "type": "Input.Text", 95 | "isMultiline": input_is_multiline 96 | } 97 | 98 | if input_max_length is not None: element["maxLength"] = input_max_length 99 | if input_placeholder is not None: element["placeholder"] = input_placeholder 100 | if input_value is not None: element["value"] = input_value 101 | self.__add_element(element) 102 | 103 | """ 104 | add_submit_action_btn() adds the button that submits the 'form' that has been sent as a message. Works like the element in html. 105 | """ 106 | def add_submit_action_btn( 107 | self, title: str = "submit" 108 | ): 109 | self.check_if_submit_button_exists() 110 | action = { 111 | "type": "Action.Submit", 112 | "title": title 113 | } 114 | self.__add_action(action) 115 | 116 | 117 | """ 118 | The various attribute values can be found at: https://adaptivecards.io/schemas/adaptive-card.json#/definitions/Input.ChoiceSet 119 | """ 120 | def add_input_choiceset( 121 | self, input_id: str, input_choices:list=[], input_is_multiselect: bool = False, input_value:str = None 122 | ): 123 | self.check_if_id_exists(input_id) 124 | element = { 125 | "id": input_id, 126 | "type": "Input.ChoiceSet", 127 | "choices": input_choices, 128 | "isMultiSelect": input_is_multiselect 129 | } 130 | 131 | if input_value is not None: element["value"] = input_value 132 | self.__add_element(element) 133 | -------------------------------------------------------------------------------- /python_webex/v1/Message.py: -------------------------------------------------------------------------------- 1 | from python_webex.v1.Card import Card 2 | import requests 3 | import sys 4 | 5 | class Message: 6 | """ 7 | Message requests uses URL https://api.ciscospark.com/v1/messages 8 | 9 | Enables us to interact with the Messages in the Cisco Webex platform. 10 | sending messages, listing your messages etc. 11 | """ 12 | 13 | def send_message( 14 | self, 15 | to_person_email:str = None, 16 | room_id:str = None, 17 | text:str = None, 18 | markdown:str = None, 19 | files: list=[], # we attach normal files (images, PDFs etc...) 20 | attachments: list=[] # This is mainly used for cards that will be sent as part of the messages 21 | ): 22 | """ 23 | Allows for one to send a message to a room 24 | details on the rooms URL parameters can be found in https://developer.webex.com/docs/api/v1/messages/create-a-message 25 | 'files' is a list of the files(images, audios etc) you want to send to the user, if the user wants to attach files with the message 26 | 27 | ---- 28 | Arguments 29 | @ room_id: string => This is the ID of the room you are sending the message to 30 | @ text: string => The text being sent in the message 31 | @ files: list of string => A list of files you want to sell. Each element in the list is a directory path to the file. 32 | e.g files=['/this/is/my/path/this_image.jpg', 'this/is/my/second/path/this_pdf.pdf'] 33 | """ 34 | if room_id == None and to_person_email == None: 35 | sys.exit("either 'room_id', 'person_email' or 'toPersonId' must be present") 36 | 37 | if text == None and markdown == None: 38 | sys.exit("'text' or 'markdown' must be present") 39 | 40 | 41 | url_route = "messages" 42 | 43 | data = { 44 | "text": text, 45 | } 46 | 47 | # specify receiver of the message 48 | if room_id is not None: 49 | data["roomId"] = room_id 50 | elif to_person_email is not None: 51 | data["toPersonEmail"] = to_person_email 52 | 53 | if markdown != None: 54 | data["markdown"] = markdown 55 | 56 | if len(files) > 0: 57 | data["files"] = files 58 | 59 | if len(attachments) > 0: 60 | data["attachments"] = attachments 61 | 62 | data = requests.post( self.URL + url_route, headers=self.headers, json=data ) 63 | return data 64 | 65 | 66 | 67 | """ 68 | Message requests uses URL https://api.ciscospark.com/v1/messages 69 | 70 | Enables sending of markdown data such as lists, links, code formatted messages etc 71 | """ 72 | def send_markdown(self, to_person_email:str = None, room_id:str = None, markdown:str = None): 73 | """ 74 | ---- 75 | Arguments 76 | @ to_person_email => Email of the person we are sending the marked down message to 77 | @ room_id: str => ID of the room where the markdown is being sent to 78 | @ text: str => text to be sent to the user. This will be shown without a markdown in case 79 | the client device does not support rich text 80 | 81 | @ markdown: str => string with markdown information. For formatting information, we should 82 | use https://dev-preview.webex.com/formatting-messages.html 83 | """ 84 | if room_id == None and to_person_email == None: 85 | sys.exit("'room_id' or 'to_person_email' must be present") 86 | 87 | if markdown == None: 88 | sys.exit("'markdown' is a required field") 89 | 90 | return self.send_message(to_person_email=to_person_email, markdown=markdown, room_id=room_id) 91 | 92 | 93 | 94 | def send_card( 95 | self, 96 | card: Card, 97 | room_id: str = None, 98 | to_person_email: str = None, 99 | markdown: str="[This is the default markdown title]" 100 | ): 101 | """ 102 | Cars are elements that can hold forms and improve interactivity of the messages. 103 | For example, if you are using a bot to monitor your networking devices, this will require you to login the networking devices first. 104 | You can send a form for one to login to the networking devices. 105 | """ 106 | 107 | data = self.send_message( 108 | to_person_email = to_person_email, 109 | attachments = card.content, 110 | room_id = room_id, 111 | markdown = markdown 112 | ) 113 | 114 | return data 115 | 116 | 117 | def get_attachment_response(self, attachment_id: str): 118 | """ 119 | Gets the response for when an attachment has been sent 120 | """ 121 | url_route = "attachment/actions/{}".format(attachment_id) 122 | 123 | response = requests.get(self.URL + url_route, headers=self.headers) 124 | return response.json() 125 | 126 | def get_messages(self, room_id=None): 127 | """ 128 | gets all the messages sent and received in a specific room 129 | details on the list-messages URL parameters can be found in https://developer.webex.com/docs/api/v1/messages/list-messages 130 | """ 131 | 132 | if room_id == None: 133 | sys.exit("'room_id' is a required field") 134 | 135 | url_route = "messages" 136 | 137 | params = { 138 | "roomId": room_id 139 | } 140 | data = requests.get( self.URL + url_route, headers=self.headers, params=params ) 141 | return data 142 | 143 | def get_direct_messages(self, person_id=None): 144 | """ 145 | gets a list of all the messages sent in 1 to 1 rooms. This is basically a list all the DMs :) 146 | details on the list-direct-messages URL parameters can be found in https://developer.webex.com/docs/api/v1/messages/list-direct-messages 147 | """ 148 | 149 | if person_id == None: 150 | sys.exit("'person_id' is a mandatory field") 151 | 152 | url_route = "messages" 153 | 154 | params = { 155 | "personId": person_id 156 | } 157 | data = requests.get( self.URL + url_route + "/direct", headers=self.headers, params=params ) 158 | return data 159 | 160 | def get_message_details(self, message_id=None): 161 | """ 162 | gets details of a specific message 163 | e.g roomId, roomType, created, mentionedPeople ... 164 | details on the get message details URL parameters can be found in https://developer.webex.com/docs/api/v1/messages/get-message-details 165 | """ 166 | 167 | if message_id == None: 168 | sys.exit("'message_id' is a required field") 169 | 170 | url_route = "messages/" + message_id 171 | 172 | data = requests.get( self.URL + url_route, headers=self.headers) 173 | return data 174 | 175 | def delete_message(self, message_id=None): 176 | """ 177 | deletes a message with ID messageId 178 | details on the delete message URL can be found in https://developer.webex.com/docs/api/v1/messages/delete-a-message 179 | """ 180 | 181 | if message_id == None: 182 | sys.exit("'message_id' is not a required field") 183 | 184 | url_route = "messages/" + message_id 185 | 186 | data = requests.delete( self.URL + url_route, headers=self.headers ) 187 | return data 188 | -------------------------------------------------------------------------------- /python_webex/v1/People.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import requests 3 | 4 | 5 | class People: 6 | 7 | def get_people(self, email=None): 8 | """ 9 | gets a list of people with a particular attribute 10 | uses https://api.ciscospark.com/people - GET request 11 | """ 12 | 13 | if email is None: 14 | sys.exit("'email' is a required field") 15 | 16 | url_route = "people" 17 | 18 | params = { 19 | "email": email 20 | } 21 | data = requests.get(self.URL + url_route, headers=self.headers, params=params) 22 | return data 23 | 24 | def get_person_details(self, person_id=None): 25 | """ 26 | returns specific information of the person with ID personId 27 | uses https://api.ciscospark.com/people/{ personId } - GET request 28 | API reference can be found in: https://developer.webex.com/docs/api/v1/people/get-person-details 29 | """ 30 | 31 | if person_id is None: 32 | sys.exit("'personId' is a required field") 33 | 34 | url_route = "people" 35 | 36 | data = requests.get(self.URL + url_route + "/" + person_id, headers=self.headers) 37 | return data 38 | 39 | def get_own_details(self): 40 | """ 41 | gets the bots own information 42 | uses https://api.ciscospark.com/people - GET request 43 | API reference can be found in: https://developer.webex.com/docs/api/v1/people/get-my-own-details 44 | """ 45 | 46 | url_route = "people/me" 47 | 48 | data = requests.get(self.URL + url_route, headers=self.headers) 49 | return data 50 | -------------------------------------------------------------------------------- /python_webex/v1/Room.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | 5 | class Room: 6 | 7 | def get_all_rooms(self): 8 | """ 9 | Gives a list of all the rooms the specific bot is in 10 | this request uses url https://api.ciscospark.com/rooms 11 | details on the rooms URL can be found in: https://developer.webex.com/docs/api/v1/rooms/list-rooms 12 | """ 13 | url_route = "rooms" 14 | data = requests.get(self.URL + url_route, headers=self.headers) 15 | return data 16 | 17 | def create_room(self, title=None, team_id=None, room_type=None): 18 | """ 19 | Creates a room within a team, also known as a space. This will be within the team with ID teamId 20 | this request uses url https://api.ciscospark.com - POST request 21 | details on the create rooms can be found in https://developer.webex.com/docs/api/v1/rooms/create-a-room 22 | """ 23 | 24 | if title is None: 25 | sys.exit("'title' is a required field") 26 | 27 | if team_id is None: 28 | sys.exit("'teamId; is a required field") 29 | 30 | url_route = "rooms" 31 | 32 | json = { 33 | "teamId": team_id, 34 | "title": title 35 | } 36 | 37 | if room_type is not None: 38 | json["type"] = room_type 39 | 40 | data = requests.post(self.URL + url_route, json=json, headers=self.headers) 41 | return data 42 | 43 | def get_room_details(self, room_id=None): 44 | """ 45 | GETS DETAILS OF A PARTICULAR ROOM 46 | request uses url https://api.ciscospark.com/{roomId} - GET request 47 | details on the get room details can be found in https://developer.webex.com/docs/api/v1/rooms/get-room-details 48 | """ 49 | 50 | if room_id is None: 51 | sys.exit("'roomId' is a required field") 52 | 53 | url_route = "rooms" 54 | 55 | data = requests.get(self.URL + url_route + "/" + room_id, headers=self.headers) 56 | return data 57 | 58 | def update_room_details(self, room_id=None, title=None): 59 | """ 60 | UDPATES THE DETAILS OF A PARTICULAR ROOM based on the **kwargs given by the user 61 | request uses url https://api.ciscospark.com/{roomId} - PUT request 62 | details on the update room details can be found in https://developer.webex.com/docs/api/v1/rooms/update-a-room 63 | """ 64 | 65 | if room_id is None: 66 | sys.exit("'roomId' is a required field") 67 | 68 | elif title is None: 69 | sys.exit("'title' is a required field") 70 | 71 | json = { 72 | "title": title 73 | } 74 | 75 | url_route = "rooms" 76 | 77 | data = requests.put(self.URL + url_route + '/' + room_id, json=json, headers=self.headers) 78 | return data 79 | 80 | def delete_room(self, room_id=None): 81 | """ 82 | DELETES A ROOM with ID roomId 83 | uses url https://api.ciscospark.com/v1/rooms/{roomId} 84 | details on the delete room can be found in https://developer.webex.com/docs/api/v1/rooms/delete-a-room 85 | """ 86 | 87 | if room_id is None: 88 | sys.exit("'roomId' is a required field") 89 | 90 | url_route = "rooms" 91 | data = requests.delete(self.URL + url_route + "/" + room_id, headers=self.headers) 92 | return data 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /python_webex/v1/Webhook.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | class Webhook: 5 | 6 | def get_all_webhooks(self): 7 | """ 8 | GETS A LIST OF ALL THE WEBHOOKS CURRENTLY CONNECTED TO YOUR BOT 9 | uses the https://api.ciscospark.com/v1/webhooks - GET request 10 | details on the list webhooks URL can be found in https://developer.webex.com/docs/api/v1/webhooks/list-webhooks 11 | """ 12 | 13 | url_route = "webhooks" 14 | 15 | data = requests.get(self.URL + url_route, headers=self.headers) 16 | 17 | return data 18 | 19 | def create_webhook(self, name=None, target_url=None, resource=None, event=None): 20 | """ 21 | Enables one to create a webhook that will be listening to events sent to the bot 22 | uses the https://api.ciscospark.com/v1/webhooks - POST request 23 | details on create webhooks URL can be found in https://developer.webex.com/docs/api/v1/webhooks/create-a-webhook 24 | """ 25 | 26 | url_route = "webhooks" 27 | 28 | if name is None: 29 | sys.exit("'name' is a required field") 30 | 31 | elif target_url is None: 32 | sys.exit("'targetUrl' is a required field") 33 | 34 | elif resource is None: 35 | sys.exit("'resource' is a required field") 36 | 37 | elif event is None: 38 | sys.exit("'event' is a required field") 39 | 40 | # check for if a webhook with this URL already exists for this particular bot 41 | # cause apparently Cisco does not do that for us when creating webhooks. But tis all good :) 42 | existing_webhooks = self.get_all_webhooks().json() 43 | for webhook in existing_webhooks['items']: 44 | if webhook['targetUrl'] == target_url: 45 | return self.get_webhook_details(webhook_id=webhook['id']) 46 | 47 | json = { 48 | "name": name, "targetUrl": target_url, "resource": resource, "event": event 49 | } 50 | 51 | data = requests.post(self.URL + url_route, headers=self.headers, json=json) 52 | return data 53 | 54 | def delete_webhook(self, webhook_id=None): 55 | """ 56 | Deletes a webhook that has ID webhookId 57 | uses the https://api.ciscospark.com/webhooks - DELETE request 58 | details on delete webhooks URL can be found in https://developer.webex.com/docs/api/v1/webhooks/delete-a-webhook 59 | """ 60 | 61 | url_route = "webhooks" 62 | 63 | if webhook_id is None: 64 | sys.exit("'webhookId' is a required field") 65 | 66 | data = requests.delete(self.URL + url_route + "/" + webhook_id, headers=self.headers) 67 | return data 68 | 69 | def update_webhook(self, webhook_id=None, name=None, target_url=None): 70 | """ 71 | 'name' is the updated name of the webhook 72 | 'targetUrl' is the updated targetUrl of the webhook 73 | 74 | Edit a webhook with ID of webhookId 75 | uses the https://api.ciscospark.com/webhooks - PUT request 76 | details on edit webhook URL can be found in https://developer.webex.com/docs/api/v1/webhooks/update-a-webhook 77 | """ 78 | 79 | url_route = "webhooks" 80 | 81 | if webhook_id is None: 82 | sys.exit("'webhookId' is a required field") 83 | 84 | elif name is None: 85 | sys.exit("'name' is a required field") 86 | 87 | elif target_url is None: 88 | sys.exit("'targetUrl' is a required field") 89 | 90 | json = { 91 | "name": name, "targetUrl": target_url 92 | } 93 | 94 | data = requests.put(self.URL + url_route + "/" + webhook_id, json=json, headers=self.headers) 95 | return data 96 | 97 | def get_webhook_details(self, webhook_id=None): 98 | """ 99 | Get the details of a single webhook with id of webhookId 100 | uses https://api.ciscospark.com/webhooks/{roomId} - GET request 101 | details on get webhook details URL can be found in https://developer.webex.com/docs/api/v1/webhooks/get-webhook-details 102 | """ 103 | 104 | url_route = "webhooks" 105 | 106 | if webhook_id is None: 107 | sys.exit("'webhookId' is a required field") 108 | 109 | data = requests.get(self.URL + url_route, headers=self.headers) 110 | return data 111 | -------------------------------------------------------------------------------- /python_webex/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/python_webex_bot/3381d0a094f01a78145fa730cdf31d7be1cb174e/python_webex/v1/__init__.py -------------------------------------------------------------------------------- /python_webex/webhook/__init__.py: -------------------------------------------------------------------------------- 1 | from python_webex.webhook.handlers import MessageReceivingHandler, AttachmentReceivingHandler 2 | from flask import Flask, request 3 | 4 | 5 | app = Flask(__name__) 6 | bot = None 7 | 8 | 9 | @app.route("/", methods=[ 'GET', 'POST' ]) 10 | def index(): 11 | json_data = request.get_json() 12 | message_id = json_data[ "data" ][ "id" ] 13 | message_info = bot.get_message_details( message_id=message_id ).json() 14 | 15 | handler = MessageReceivingHandler(bot, message_info) 16 | handler.handle_message() 17 | response = "incoming message handled" 18 | return response 19 | 20 | @app.route("/attachment-response", methods=["GET", "POST"]) 21 | def attachment_response(): 22 | json_data = request.get_json() 23 | message_id = json_data['data']['messageId'] 24 | message_info = bot.get_attachment_response(json_data['data']['id']) 25 | 26 | handler = AttachmentReceivingHandler(bot, message_info, message_id) 27 | handler.handle_attachment() 28 | 29 | response = "incoming attachment handled" 30 | return response 31 | -------------------------------------------------------------------------------- /python_webex/webhook/handlers.py: -------------------------------------------------------------------------------- 1 | """Explanation of the route handlers 2 | 3 | 4 | This module takes care of handling logic of data from the '@app.route's in the webhook/__init__.py file 5 | webhook/__init__.py file mainly deals with getting requests and messages from the sender, 6 | while this route_handlers.py mainly deals with the logic after receiving this information. 7 | This mainly has to do with cleaning up the webhook/__init__.py which was rather messy. 8 | 9 | Attributes: 10 | # Bot below refers to Bot class from python_webex.v1.Bot 11 | - bot(Bot): an instance of the bot that we are dealing with when running our program. 12 | - message_info(dictionary): a dictionary of all the details that were gotten from the message sent. 13 | For more information, you can have a look at the 14 | 15 | Todo: 16 | * 17 | """ 18 | from python_webex.v1.Bot import Bot 19 | import json 20 | 21 | 22 | class MessageReceivingHandler: 23 | 24 | def __init__(self, bot, message_info): 25 | self.bot = bot 26 | self.message_info = message_info 27 | 28 | def handle_message(self): 29 | # looks for in case there was a file(image, document etc) attached in the message. 30 | if 'files' in self.message_info: 31 | self.handle_messages_with_files() 32 | return None 33 | 34 | # handles when a normal message is sent with text in the message 35 | # and a way to handle the text received has been defined by the bot programmer. 36 | elif self.message_info[ "text" ].strip() != "" and self.message_info[ "text" ] in self.bot.hears_to_function: 37 | self.handle_messages_without_file_and_with_text() 38 | return None 39 | 40 | # handles when a normal message is sent with text in the message 41 | # but a way to handle the text received has not been defined by the bot programmer 42 | elif self.message_info["text"].strip() != "" and self.message_info[ "text" ] not in self.bot.hears_to_function: 43 | 44 | # make sure the bot is not hearing its own messages 45 | sender_email = self.message_info["personEmail"] 46 | bot_emails = json.loads(self.bot.get_own_details().text)["emails"] 47 | if sender_email not in bot_emails: 48 | self.handle_messages_without_file_and_without_text() 49 | return None 50 | 51 | 52 | def handle_messages_with_files(self): 53 | """ 54 | function handles when messages are sent with file attachments (images, documents etc) 55 | """ 56 | # loop for when the file attached(image, document..etc) is sent with a text accomanied. 57 | # for example, if an image is sent with caption "Felt cure, might delete later" 58 | # this is considered a file attachment with a text alongside 59 | if "text" in self.message_info: 60 | self.handle_messages_with_file_and_text() 61 | 62 | elif self.bot.default_attachment is not None: 63 | self.handle_messages_with_files_and_without_text_and_with_default_attachment_set() 64 | 65 | else: 66 | self.handle_messages_without_file_and_without_text() 67 | 68 | def handle_messages_with_file_and_text(self): 69 | message_text = self.message_info["text"] 70 | 71 | if self.message_info['text'] in self.bot.hears_file_to_function: 72 | 73 | # handles when the bot has been programmed to handle messages with files 74 | # and the specific text that is being sent to the bot. 75 | # looks for if the function has message_info specified; and this is used in mapping of 76 | # the message information 77 | if 'message_info' in self.bot.hears_file_to_function[message_text].__code__.co_varnames: 78 | self.bot.hears_file_to_function[message_text]( 79 | files = self.message_info['files'], 80 | room_id = self.message_info['roomId'], 81 | message_info = self.message_info 82 | ) 83 | 84 | else: 85 | self.bot.hears_file_to_function[message_text]( 86 | files = self.message_info['files'], 87 | room_id = self.message_info['roomId'] 88 | ) 89 | 90 | 91 | elif "*" in self.bot.hears_file_to_function: 92 | 93 | if 'message_info' in self.bot.hears_to_function["*"].__code__.co_varnames: 94 | self.bot.hears_file_to_function["*"]( 95 | files = self.message_info["files"], 96 | room_id = self.message_info['roomId'], 97 | message_info = self.message_info 98 | ) 99 | 100 | else: 101 | self.bot.hears_file_to_function["*"]( 102 | files = self.message_info["files"], 103 | room_id = self.message_info["roomId"] 104 | ) 105 | 106 | else: 107 | return "Default response for file sent with text not set" 108 | 109 | def handle_messages_without_file_and_with_text(self): 110 | message_text = self.message_info["text"] 111 | 112 | if 'message_info' in self.bot.hears_to_function[message_text].__code__.co_varnames: 113 | self.bot.hears_to_function[message_text]( 114 | room_id = self.message_info["roomId"], 115 | message_info = self.message_info 116 | ) 117 | 118 | else: 119 | self.bot.hears_to_function[message_text](room_id=self.message_info["roomId"]) 120 | 121 | def handle_messages_without_file_and_without_text(self): 122 | if 'message_info' in self.bot.hears_to_function["*"].__code__.co_varnames: 123 | self.bot.hears_to_function["*"]( 124 | room_id = self.message_info["roomId"], 125 | message_info = self.message_info 126 | ) 127 | else: 128 | self.bot.hears_to_function["*"]( 129 | room_id=self.message_info["roomId"] 130 | ) 131 | 132 | def handle_messages_with_files_and_without_text_and_with_default_attachment_set(self): 133 | if 'message_info' in self.bot.default_attachment.__code__.co_varnames: 134 | self.bot.default_attachment( 135 | files = self.message_info['files'], 136 | room_id = self.message_info['roomId'], 137 | message_info = self.message_info 138 | ) 139 | 140 | else: 141 | self.bot.default_attachment( 142 | files = self.message_info['files'], 143 | room_id = self.message_info['roomId'] 144 | ) 145 | 146 | class AttachmentReceivingHandler: 147 | 148 | def __init__(self, bot, message_info, message_id): 149 | self.bot = bot 150 | self.message_info = message_info 151 | self.message_id = message_id 152 | 153 | def handle_attachment(self): 154 | if self.message_id in self.bot.attachment_response_to_function: 155 | self.handle_response_of_known_attachment() 156 | else: 157 | self.handle_default_response_for_attachments() 158 | 159 | def handle_response_of_known_attachment(self): 160 | response = self.bot.attachment_response_to_function[self.message_id](self.message_info) 161 | 162 | def handle_default_response_for_attachments(self): 163 | room_id = self.message_info["roomId"] 164 | self.bot.send_message( 165 | room_id=room_id, 166 | text="The bot has not been configured to handle this form's submission. Be patient" 167 | ) 168 | -------------------------------------------------------------------------------- /python_webex_bot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | echo "hey there, this is my first pip package" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bleach==5.0.1 2 | certifi==2023.7.22 3 | cffi==1.15.1 4 | chardet==3.0.4 5 | charset-normalizer==2.1.1 6 | click==7.1.2 7 | commonmark==0.9.1 8 | cryptography==42.0.4 9 | docutils==0.19 10 | Flask==2.3.2 11 | ghp-import==2.1.0 12 | idna==3.7 13 | importlib-metadata==4.12.0 14 | itsdangerous==1.1.0 15 | jaraco.classes==3.2.2 16 | jeepney==0.8.0 17 | Jinja2==3.1.4 18 | keyring==23.9.1 19 | Markdown==3.3.7 20 | MarkupSafe==1.1.1 21 | mergedeep==1.3.4 22 | mkdocs==1.3.1 23 | more-itertools==8.14.0 24 | packaging==21.3 25 | pkginfo==1.8.3 26 | pycparser==2.21 27 | Pygments==2.15.0 28 | pyparsing==3.0.9 29 | python-dateutil==2.8.2 30 | PyYAML==6.0 31 | pyyaml_env_tag==0.1 32 | readme-renderer==37.1 33 | requests==2.32.0 34 | requests-toolbelt==0.9.1 35 | rfc3986==2.0.0 36 | rich==12.5.1 37 | SecretStorage==3.3.3 38 | six==1.16.0 39 | twine==4.0.1 40 | urllib3==1.26.19 41 | virtualenv==16.4.3 42 | watchdog==2.1.9 43 | webencodings==0.5.1 44 | Werkzeug==3.0.3 45 | zipp==3.8.1 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | required = None 4 | with open('requirements.txt') as f: 5 | required = f.read().splitlines() 6 | 7 | 8 | with open("README.md", "r") as fh: 9 | long_description = fh.read() 10 | 11 | setuptools.setup( 12 | name="python_webex_bot", 13 | version="0.901", 14 | 15 | scripts=["python_webex_bot"], 16 | author="Paul-weqe", 17 | description="Enable sending markdown in messges", 18 | long_description=long_description, 19 | long_description_content_type="text/markdown", 20 | url="https://github.com/Paul-weqe/python_webex_bot", 21 | packages=setuptools.find_packages(), 22 | install_requires=[ 23 | "certifi", 24 | "chardet", 25 | "click", 26 | "Flask", 27 | "idna", 28 | "itsdangerous", 29 | "Jinja2", 30 | "MarkupSafe", 31 | "requests", 32 | "urllib3", 33 | "Werkzeug" 34 | ], 35 | classifiers=[ 36 | "Programming Language :: Python :: 3", 37 | "License :: OSI Approved :: MIT License", 38 | "Operating System :: OS Independent" 39 | ], 40 | ) 41 | --------------------------------------------------------------------------------