├── .github ├── CODEOWNERS └── workflows │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── example ├── Async_API │ ├── Audio_API │ │ ├── append_audio_file.py │ │ ├── append_audio_url.py │ │ ├── process_audio_file.py │ │ └── process_audio_url.py │ ├── Text_API │ │ ├── append_text.py │ │ └── process_text.py │ └── Video_API │ │ ├── append_video_file.py │ │ ├── append_video_url.py │ │ ├── process_video_file.py │ │ └── process_video_url.py ├── Conversation_APIs │ ├── Delete_APIs │ │ ├── delete_api_async.py │ │ ├── delete_api_conversationApi.py │ │ ├── delete_api_streaming.py │ │ ├── delete_api_telephony_pstn.py │ │ └── delete_api_telephony_sip.py │ ├── Get_APIs │ │ └── conversation_class.py │ ├── Post_APIs │ │ ├── formatted_transcript_ConversationApi.py │ │ ├── formatted_transcript_api_async.py │ │ ├── formatted_transcript_api_streaming.py │ │ ├── formatted_transcript_api_telephony_pstn.py │ │ └── formatted_transcript_api_telephony_sip.py │ └── Put_APIs │ │ ├── put_apis_example.py │ │ ├── put_members.py │ │ ├── put_speaker_events_async.py │ │ └── put_speaker_events_streaming.py ├── Streaming_API │ └── streaming.py ├── Telephony_API │ ├── telephony_pstn.py │ └── telephony_sip.py └── use_cases │ ├── NonBlockingCallsExample.py │ ├── PrettyPrintTranscription.py │ └── SpeechToTextOfDirectoryOfAudioFiles.py ├── readme.md ├── requirements.txt ├── setup.py ├── symbl ├── AuthenticationToken.py ├── Connection.py ├── Conversations.py ├── __init__.py ├── async_api │ ├── Audio.py │ ├── Text.py │ ├── Video.py │ └── __init__.py ├── callScore.py ├── configs │ ├── __init__.py │ └── configs.py ├── conversations_api │ ├── ConversationsApi.py │ └── __init__.py ├── jobs_api │ ├── Job.py │ ├── JobStatus.py │ └── __init__.py ├── readme.md ├── streaming_api │ ├── StreamingApi.py │ ├── StreamingConnection.py │ └── __init__.py ├── telephony_api │ ├── TelephonyApi.py │ ├── TelephonyValidators.py │ └── __init__.py └── utils │ ├── Decorators.py │ ├── Helper.py │ ├── Logger.py │ ├── Threads.py │ └── __init__.py └── tests ├── __init__.py ├── audio_test.py ├── authentication_token_test.py ├── conversations_api_test.py ├── conversations_test.py ├── telephony_test.py ├── text_test.py └── video_test.py /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @atmnks @rishabh-chaturvedi 6 | 7 | # Order is important. The last matching pattern has the most precedence. 8 | # So if a pull request only touches javascript files, only these owners 9 | # will be requested to review. 10 | #*.js 11 | 12 | # You can also use email addresses if you prefer. 13 | #docs/* -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | build/ 3 | dist/ 4 | venv/ 5 | Symbl.egg-info/ 6 | .coverage 7 | .idea/ 8 | .vscode/ 9 | **/*.pyc 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /example/Async_API/Audio_API/append_audio_file.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | file = "" 4 | 5 | conversation_id = "1234567890" #update with the conversation ID with which you want to perform the append operation 6 | 7 | conversation_object = symbl.Audio.append_file(file_path=file, conversation_id=conversation_id) 8 | 9 | #To get the message from the conversation 10 | print(conversation_object.get_messages()) 11 | 12 | #To get the conversation data from the conversation 13 | #print(conversation_object.get_conversation()) 14 | 15 | #To get the action items from the conversation 16 | #print(conversation_object.get_action_items()) 17 | 18 | #To get the follow ups from the conversation 19 | #print(conversation_object.get_follow_ups()) 20 | 21 | #To get the members information from the conversation 22 | #print(conversation_object.get_members()) 23 | 24 | #To get the topics from the conversation 25 | #print(conversation_object.get_topics()) 26 | 27 | #To get the questions from the conversation 28 | #print(conversation_object.get_questions()) 29 | 30 | # To get the analytics from the conversation 31 | #print(conversation_object.get_analytics()) 32 | 33 | # To get the trackers from the conversation 34 | #print(conversation_object.get_trackers()) 35 | 36 | # To get the entities from the conversation 37 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Audio_API/append_audio_url.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | payload = {'url': ""} #write the url path of the audio, which you want to append 4 | 5 | conversationId = "1234567890" #update with the conversation ID with which you want to perform the append operation 6 | conversation_object = symbl.Audio.append_url(payload=payload, conversation_id=conversationId) 7 | 8 | 9 | #To get the message from the conversation 10 | print(conversation_object.get_messages()) 11 | 12 | #To get the conversation data from the conversation 13 | #print(conversation_object.get_conversation()) 14 | 15 | #To get the action items from the conversation 16 | #print(conversation_object.get_action_items()) 17 | 18 | #To get the follow ups from the conversation 19 | #print(conversation_object.get_follow_ups()) 20 | 21 | #To get the members information from the conversation 22 | #print(conversation_object.get_members()) 23 | 24 | #To get the topics from the conversation 25 | #print(conversation_object.get_topics()) 26 | 27 | #To get the questions from the conversation 28 | #print(conversation_object.get_questions()) 29 | 30 | # To get the analytics from the conversation 31 | #print(conversation_object.get_analytics()) 32 | 33 | # To get the trackers from the conversation 34 | #print(conversation_object.get_trackers()) 35 | 36 | # To get the entities from the conversation 37 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Audio_API/process_audio_file.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | file = "" 4 | 5 | ''' like this you can pass the parameter 6 | entities = [ 7 | { 8 | "customType": "custom_type", 9 | "text": "entity_name" 10 | } 11 | ] 12 | 13 | trackers = [{ 14 | "name": "tracker_name", 15 | "vocabulary": [ 16 | "vocabulary_1", 17 | "vocabulary_2", 18 | "vocabulary_n" 19 | ] 20 | }] 21 | params = { 22 | 'name': "Meeting", 23 | "detectEntities": "true", 24 | "enableAllTrackers":"true", 25 | "entities":entities, 26 | 'trackers': trackers, 27 | 'enableSpeakerDiarization': "true", 28 | "diarizationSpeakerCount": "2", 29 | "channelMetadata": [ 30 | { 31 | "channel": 1, 32 | "speaker": { 33 | "name": "Robert Bartheon", 34 | "email": "robertbartheon@gmail.com" 35 | } 36 | }, 37 | { 38 | "channel": 2, 39 | "speaker": { 40 | "name": "Arya Stark", 41 | "email": "aryastark@gmail.com" 42 | } 43 | } 44 | ] 45 | } 46 | conversation_object = symbl.Audio.process_file(file_path=file, parameters=params) 47 | ''' 48 | conversation_object = symbl.Audio.process_file(file_path=file) 49 | 50 | 51 | #To get the message from the conversation 52 | print(conversation_object.get_messages()) 53 | 54 | #To get the conversation data from the conversation 55 | #print(conversation_object.get_conversation()) 56 | 57 | #To get the action items from the conversation 58 | #print(conversation_object.get_action_items()) 59 | 60 | #To get the follow ups from the conversation 61 | #print(conversation_object.get_follow_ups()) 62 | 63 | #To get the members information from the conversation 64 | #print(conversation_object.get_members()) 65 | 66 | #To get the topics from the conversation 67 | #print(conversation_object.get_topics()) 68 | 69 | #To get the questions from the conversation 70 | #print(conversation_object.get_questions()) 71 | 72 | # To get the analytics from the conversation 73 | #print(conversation_object.get_analytics()) 74 | 75 | # To get the trackers from the conversation 76 | #print(conversation_object.get_trackers()) 77 | 78 | # To get the entities from the conversation 79 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Audio_API/process_audio_url.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | ''' 3 | you can also pass other parameters with the payload 4 | 5 | entities = [ 6 | { 7 | "customType": "custom_type", 8 | "text": "entity_name" 9 | } 10 | ] 11 | 12 | trackers = [{ 13 | "name": "tracker_name", 14 | "vocabulary": [ 15 | "vocabulary_1", 16 | "vocabulary_2", 17 | "vocabulary_n" 18 | ] 19 | }] 20 | 21 | payload = { 22 | 'url': "", #write the url path of the audio, which you want to process 23 | 'name': "TestingMeeting", 24 | "detectEntities": "true", 25 | "enableAllTrackers":"true", 26 | "entities":entities, 27 | 'trackers': trackers, 28 | 'enableSpeakerDiarization': "true", 29 | 'diarizationSpeakerCount': "2", 30 | 'channelMetadata': [ 31 | { 32 | "channel": 1, 33 | "speaker": { 34 | "name": "Robert Bartheon", 35 | "email": "robertbartheon@gmail.com" 36 | } 37 | }, 38 | { 39 | "channel": 2, 40 | "speaker": { 41 | "name": "Arya Stark", 42 | "email": "aryastark@gmail.com" 43 | } 44 | } 45 | ] 46 | } 47 | 48 | ''' 49 | payload = {'url': ""} #write the url path of the audio, which you want to process 50 | conversation_object = symbl.Audio.process_url(payload=payload) 51 | 52 | #To get the message from the conversation 53 | print(conversation_object.get_messages()) 54 | 55 | #To get the conversation data from the conversation 56 | #print(conversation_object.get_conversation()) 57 | 58 | #To get the action items from the conversation 59 | #print(conversation_object.get_action_items()) 60 | 61 | #To get the follow ups from the conversation 62 | #print(conversation_object.get_follow_ups()) 63 | 64 | #To get the members information from the conversation 65 | #print(conversation_object.get_members()) 66 | 67 | #To get the topics from the conversation 68 | #print(conversation_object.get_topics()) 69 | 70 | #To get the questions from the conversation 71 | #print(conversation_object.get_questions()) 72 | 73 | # To get the analytics from the conversation 74 | #print(conversation_object.get_analytics()) 75 | 76 | # To get the trackers from the conversation 77 | #print(conversation_object.get_trackers()) 78 | 79 | # To get the entities from the conversation 80 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Text_API/append_text.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | payload = { 4 | "name": "TextAPI", #you can update the name of the conversation 5 | 6 | "messages": [ 7 | { 8 | "payload": {"content": "Hi Anthony. I saw your complaints about bad call reception on your mobile phone. Can I know what issues you are currently facing?"}, 9 | "from": {"userId": "surbhi@example.com","name": "Surbhi Rathore"} 10 | }, 11 | { 12 | "payload": {"content": "Hey Surbhi, thanks for reaching out. Whenever I am picking up the call there is a lot of white noise and I literally can’t hear anything."}, 13 | "from": {"userId": "anthony@example.com","name": "Anthony Claudia"} 14 | } 15 | ] 16 | } 17 | 18 | conversation_id = "1234567890" #update with the conversation ID with which you want to perform the append operation 19 | 20 | conversation_object = symbl.Text.append(payload=payload, conversation_id=conversation_id) 21 | 22 | #To get the message from the conversation 23 | print(conversation_object.get_messages()) 24 | 25 | #To get the conversation data from the conversation 26 | #print(conversation_object.get_conversation()) 27 | 28 | #To get the action items from the conversation 29 | #print(conversation_object.get_action_items()) 30 | 31 | #To get the follow ups from the conversation 32 | #print(conversation_object.get_follow_ups()) 33 | 34 | #To get the members information from the conversation 35 | #print(conversation_object.get_members()) 36 | 37 | #To get the topics from the conversation 38 | #print(conversation_object.get_topics()) 39 | 40 | #To get the questions from the conversation 41 | #print(conversation_object.get_questions()) 42 | 43 | # To get the analytics from the conversation 44 | #print(conversation_object.get_analytics()) 45 | 46 | # To get the trackers from the conversation 47 | #print(conversation_object.get_trackers()) 48 | 49 | # To get the entities from the conversation 50 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Text_API/process_text.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | #you can add the different vocabulary, which you would like to track 4 | trackers = [{ 5 | "name": "text_tracker", 6 | "vocabulary": [ 7 | "white", 8 | "issues", 9 | "vaccination" 10 | ] 11 | }] 12 | payload = { 13 | 14 | "name": "TextAPI", # you can update the name of the conversation 15 | 16 | "features": { 17 | "featureList": [ 18 | "insights", 19 | "callScore" 20 | ] 21 | }, 22 | "metadata": { 23 | "salesStage": "qualification", 24 | "prospectName": "DeepMind AI" 25 | }, 26 | 27 | #"trackers": trackers, #To detect the trackers 28 | 29 | #"detectEntities": "true", #To get the entities 30 | 31 | #To define Custom entities 32 | "entities": [ 33 | { 34 | "customType": "identify_org", 35 | "text": "platform" 36 | } 37 | ], 38 | "messages": [ 39 | { 40 | "payload": {"content": "Hi Anthony. I saw your complaints about bad call reception on your mobile phone. Can I know what issues you are currently facing?"}, 41 | "from": {"userId": "surbhi@example.com", "name": "Surbhi Rathore"} 42 | }, 43 | { 44 | "payload": {"content": "Hey Surbhi, thanks for reaching out. Whenever I am picking up the call there is a lot of white noise and I literally can’t hear anything."}, 45 | "from": {"userId": "anthony@example.com", "name": "Anthony Claudia"} 46 | }, 47 | { 48 | "payload": {"content": "Okay. I can schedule a visit from one of our technicians for tomorrow afternoon at 1:00 PM. He can look at your mobile and handle any issue right away"}, 49 | "from": {"userId": "surbhi@example.com", "name": "Surbhi Rathore"} 50 | }, 51 | { 52 | "payload": {"content": "That will be really helpful. I'll follow up with the technician about some other issues too, tomorrow"}, 53 | "from": {"userId": "anthony@example.com", "name": "Anthony Claudia"} 54 | }, 55 | { 56 | "payload": {"content": "Sure. We are happy to help. I am scheduling the visit for tomorrow. Thanks for using Abccorp networks. Have a good day."}, 57 | "from": {"userId": "surbhi@example.com", "name": "Surbhi Rathore"} 58 | } 59 | ] 60 | } 61 | 62 | conversation_object = symbl.Text.process(payload=payload) 63 | 64 | # To get the message from the conversation 65 | print(conversation_object.get_messages()) 66 | print(conversation_object.get_call_score_status()) 67 | 68 | #To get the conversation data from the conversation 69 | #print(conversation_object.get_conversation()) 70 | 71 | # To get the action items from the conversation 72 | # print(conversation_object.get_action_items()) 73 | 74 | # To get the follow ups from the conversation 75 | # print(conversation_object.get_follow_ups()) 76 | 77 | # To get the members information from the conversation 78 | # print(conversation_object.get_members()) 79 | 80 | # To get the topics from the conversation 81 | # print(conversation_object.get_topics()) 82 | 83 | # To get the questions from the conversation 84 | # print(conversation_object.get_questions()) 85 | 86 | # To get the analytics from the conversation 87 | #print(conversation_object.get_analytics()) 88 | 89 | # To get the trackers from the conversation 90 | #print(conversation_object.get_trackers()) 91 | 92 | # To get the entities from the conversation 93 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Video_API/append_video_file.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | file = "" 4 | 5 | conversation_id = "1234567890" #update with the conversation ID with which you want to perform the append operation 6 | 7 | conversation_object = symbl.Video.append_file(file_path=file, conversation_id=conversation_id) 8 | 9 | 10 | #To get the message from the conversation 11 | print(conversation_object.get_messages()) 12 | 13 | #To get the conversation data from the conversation 14 | #print(conversation_object.get_conversation()) 15 | 16 | #To get the action items from the conversation 17 | #print(conversation_object.get_action_items()) 18 | 19 | #To get the follow ups from the conversation 20 | #print(conversation_object.get_follow_ups()) 21 | 22 | #To get the members information from the conversation 23 | #print(conversation_object.get_members()) 24 | 25 | #To get the topics from the conversation 26 | #print(conversation_object.get_topics()) 27 | 28 | #To get the questions from the conversation 29 | #print(conversation_object.get_questions()) 30 | 31 | # To get the analytics from the conversation 32 | #print(conversation_object.get_analytics()) 33 | 34 | # To get the trackers from the conversation 35 | #print(conversation_object.get_trackers()) 36 | 37 | # To get the entities from the conversation 38 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Video_API/append_video_url.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | payload = {'url': ""} #write the url path of the video, which you want to process 4 | 5 | conversationId = "1234567890" #update with the conversation ID with which you want to perform the append operation 6 | conversation_object = symbl.Video.append_url(payload=payload, conversation_id=conversationId) 7 | 8 | 9 | #To get the message from the conversation 10 | print(conversation_object.get_messages()) 11 | 12 | #To get the conversation data from the conversation 13 | #print(conversation_object.get_conversation()) 14 | 15 | #To get the action items from the conversation 16 | #print(conversation_object.get_action_items()) 17 | 18 | #To get the follow ups from the conversation 19 | #print(conversation_object.get_follow_ups()) 20 | 21 | #To get the members information from the conversation 22 | #print(conversation_object.get_members()) 23 | 24 | #To get the topics from the conversation 25 | #print(conversation_object.get_topics()) 26 | 27 | #To get the questions from the conversation 28 | #print(conversation_object.get_questions()) 29 | 30 | # To get the analytics from the conversation 31 | #print(conversation_object.get_analytics()) 32 | 33 | # To get the trackers from the conversation 34 | #print(conversation_object.get_trackers()) 35 | 36 | # To get the entities from the conversation 37 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Video_API/process_video_file.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | file = "" 4 | ''' like this you can pass the parameter 5 | entities = [ 6 | { 7 | "customType": "custom_type", 8 | "text": "entity_name" 9 | } 10 | ] 11 | 12 | trackers = [{ 13 | "name": "tracker_name", 14 | "vocabulary": [ 15 | "vocabulary_1", 16 | "vocabulary_2", 17 | "vocabulary_n" 18 | ] 19 | }] 20 | params = { 21 | 'name': "Meeting", 22 | "detectEntities": "true", 23 | "enableAllTrackers":"true", 24 | "entities":entities, 25 | 'trackers': trackers, 26 | 'enableSpeakerDiarization': "true", 27 | "diarizationSpeakerCount": "2", 28 | "channelMetadata": [ 29 | { 30 | "channel": 1, 31 | "speaker": { 32 | "name": "Robert Bartheon", 33 | "email": "robertbartheon@gmail.com" 34 | } 35 | }, 36 | { 37 | "channel": 2, 38 | "speaker": { 39 | "name": "Arya Stark", 40 | "email": "aryastark@gmail.com" 41 | } 42 | } 43 | ] 44 | } 45 | conversation_object = symbl.Video.process_file(file_path=file, parameters=params) 46 | ''' 47 | conversation_object = symbl.Video.process_file(file_path=file) 48 | 49 | 50 | #To get the message from the conversation 51 | print(conversation_object.get_messages()) 52 | 53 | #To get the conversation data from the conversation 54 | #print(conversation_object.get_conversation()) 55 | 56 | #To get the action items from the conversation 57 | #print(conversation_object.get_action_items()) 58 | 59 | #To get the follow ups from the conversation 60 | #print(conversation_object.get_follow_ups()) 61 | 62 | #To get the members information from the conversation 63 | #print(conversation_object.get_members()) 64 | 65 | #To get the topics from the conversation 66 | #print(conversation_object.get_topics()) 67 | 68 | #To get the questions from the conversation 69 | #print(conversation_object.get_questions()) 70 | 71 | # To get the analytics from the conversation 72 | #print(conversation_object.get_analytics()) 73 | 74 | # To get the trackers from the conversation 75 | #print(conversation_object.get_trackers()) 76 | 77 | # To get the entities from the conversation 78 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Async_API/Video_API/process_video_url.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | ''' 4 | you can also pass other parameters with the payload 5 | entities = [ 6 | { 7 | "customType": "custom_type", 8 | "text": "entity_name" 9 | } 10 | ] 11 | 12 | trackers = [{ 13 | "name": "tracker_name", 14 | "vocabulary": [ 15 | "vocabulary_1", 16 | "vocabulary_2", 17 | "vocabulary_n" 18 | ] 19 | }] 20 | 21 | payload = { 22 | 'url': "", #write the url path of the video, which you want to process 23 | 'name': "TestingMeeting", 24 | "detectEntities": "true", 25 | "enableAllTrackers":"true", 26 | "entities":entities, 27 | 'trackers': trackers, 28 | 'enableSpeakerDiarization': "true", 29 | 'diarizationSpeakerCount': "2", 30 | 'channelMetadata': [ 31 | { 32 | "channel": 1, 33 | "speaker": { 34 | "name": "Robert Bartheon", 35 | "email": "robertbartheon@gmail.com" 36 | } 37 | }, 38 | { 39 | "channel": 2, 40 | "speaker": { 41 | "name": "Arya Stark", 42 | "email": "aryastark@gmail.com" 43 | } 44 | } 45 | ] 46 | } 47 | 48 | conversation_object = symbl.Video.process_url(payload=payload) 49 | ''' 50 | 51 | payload = {'url': ""} #write the url path of the video, which you want to process 52 | conversation_object = symbl.Video.process_url(payload=payload) 53 | 54 | 55 | #To get the message from the conversation 56 | print(conversation_object.get_messages()) 57 | 58 | #To get the conversation data from the conversation 59 | #print(conversation_object.get_conversation()) 60 | 61 | #To get the action items from the conversation 62 | #print(conversation_object.get_action_items()) 63 | 64 | #To get the follow ups from the conversation 65 | #print(conversation_object.get_follow_ups()) 66 | 67 | #To get the members information from the conversation 68 | #print(conversation_object.get_members()) 69 | 70 | #To get the topics from the conversation 71 | #print(conversation_object.get_topics()) 72 | 73 | #To get the questions from the conversation 74 | #print(conversation_object.get_questions()) 75 | 76 | # To get the analytics from the conversation 77 | #print(conversation_object.get_analytics()) 78 | 79 | # To get the trackers from the conversation 80 | #print(conversation_object.get_trackers()) 81 | 82 | # To get the entities from the conversation 83 | #print(conversation_object.get_entities()) -------------------------------------------------------------------------------- /example/Conversation_APIs/Delete_APIs/delete_api_async.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | payload2 = { 4 | "name": "TestingTextAPI", 5 | "trackers": [{ 6 | "name": "text_tracker", 7 | "vocabulary": [ 8 | "indians", 9 | "thoughts", 10 | "vaccination" 11 | ] 12 | }], 13 | "detectEntities": "true", 14 | "enableAllTrackers": "true", 15 | "messages": [ 16 | { 17 | "payload": { 18 | "content": "Let us talk about India Australia Testaments start seeing each other in the past as much we thought like it will be one-sided." 19 | }, 20 | "from": { 21 | "userId": "natalia@example.com", 22 | "name": "Natalia" 23 | }, 24 | "duration": { 25 | "startTime": "2020-07-21T16:04:19.01Z", 26 | "endTime": "2020-07-21T16:04:20.99Z" 27 | } 28 | }, 29 | { 30 | "payload": { 31 | "content": "Only working with is occupied at the moment:blush: , but just generally /there's going to be a lot of (infographic) making done and if you would like to make them yourself I can give you access to then gauge:confetti_ball: . ." 32 | }, 33 | "from": { 34 | "userId": "mike@abccorp.com", 35 | "name": "Mike" 36 | }, 37 | "duration": { 38 | "startTime": "2020-07-21T16:04:19.99Z", 39 | "endTime": "2020-07-21T16:04:20.99Z" 40 | } 41 | }, 42 | { 43 | "payload": { 44 | "content": "We wouldn't have thought like India can win that test match. tomorrow on 21 june, 2021 is our match i think" 45 | }, 46 | "from": { 47 | "userId": "natalia@example.com", 48 | "name": "Natalia" 49 | }, 50 | "duration": { 51 | "startTime": "2020-07-21T16:04:20.99Z", 52 | "endTime": "2020-07-21T16:04:21.99Z" 53 | } 54 | }, 55 | { 56 | "payload": { 57 | "content": "Yeah, no one could ever like predict that India would dominate all the other matches against really started harassing Indians alone, including mom." 58 | }, 59 | "from": { 60 | "userId": "steve@abccorp.com", 61 | "name": "Steve" 62 | }, 63 | "duration": { 64 | "startTime": "2020-07-21T16:04:20.99Z", 65 | "endTime": "2020-07-21T16:04:22.99Z" 66 | } 67 | }, 68 | { 69 | "payload": { 70 | "content": "Other mothers but against give it to fight and Australians also then accepted the defeat gracefully and congratulated the Indian team for their unique victory." 71 | }, 72 | "from": { 73 | "userId": "natalia@example.com", 74 | "name": "Natalia" 75 | }, 76 | "duration": { 77 | "startTime": "2020-07-21T16:04:23.99Z", 78 | "endTime": "2020-07-21T16:04:24.99Z" 79 | } 80 | }, 81 | { 82 | "payload": { 83 | "content": "And a test on this much." 84 | }, 85 | "from": { 86 | "userId": "mike@abccorp.com", 87 | "name": "Mike" 88 | }, 89 | "duration": { 90 | "startTime": "2020-07-21T16:04:25.99Z", 91 | "endTime": "2020-07-21T16:04:26.99Z" 92 | } 93 | }, 94 | { 95 | "payload": { 96 | "content": "We will again have to talk based on the topic Rohit water to the topics, and we see the topics that are generated here." 97 | }, 98 | "from": { 99 | "userId": "natalia@example.com", 100 | "name": "Natalia" 101 | }, 102 | "duration": { 103 | "startTime": "2020-07-21T16:04:27.99Z", 104 | "endTime": "2020-07-21T16:04:29.99Z" 105 | } 106 | }, 107 | { 108 | "payload": { 109 | "content": "I don't know what clean water spaghetti tacos." 110 | }, 111 | "from": { 112 | "userId": "steve@abccorp.com", 113 | "name": "Steve" 114 | }, 115 | "duration": { 116 | "startTime": "2020-07-21T16:04:30.99Z", 117 | "endTime": "2020-07-21T16:04:31.99Z" 118 | } 119 | }, 120 | { 121 | "payload": { 122 | "content": "Australian distance Indians, so we can talk about Indians." 123 | }, 124 | "from": { 125 | "userId": "steve@abccorp.com", 126 | "name": "Steve" 127 | }, 128 | "duration": { 129 | "startTime": "2020-07-21T16:04:32.99Z", 130 | "endTime": "2020-07-21T16:04:33.99Z" 131 | } 132 | }, 133 | { 134 | "payload": { 135 | "content": "Yeah, yeah, we can talk about it." 136 | }, 137 | "from": { 138 | "userId": "natalia@example.com", 139 | "name": "Natalia" 140 | }, 141 | "duration": { 142 | "startTime": "2020-07-21T16:04:34.99Z", 143 | "endTime": "2020-07-21T16:04:35.99Z" 144 | } 145 | }, 146 | { 147 | "payload": { 148 | "content": "Let us talk about the Indian culture." 149 | }, 150 | "from": { 151 | "userId": "mike@abccorp.com", 152 | "name": "Mike" 153 | }, 154 | "duration": { 155 | "startTime": "2020-07-21T16:04:36.99Z", 156 | "endTime": "2020-07-21T16:04:37.99Z" 157 | } 158 | }, 159 | { 160 | "payload": { 161 | "content": "We will again have to talk based on the topic Rohit water to the topics, and we see the topics that are generated here." 162 | }, 163 | "from": { 164 | "userId": "natalia@example.com", 165 | "name": "Natalia" 166 | }, 167 | "duration": { 168 | "startTime": "2020-07-21T16:04:38.99Z", 169 | "endTime": "2020-07-21T16:04:40.99Z" 170 | } 171 | }, 172 | { 173 | "payload": { 174 | "content": "Let us go." 175 | }, 176 | "from": { 177 | "userId": "steve@abccorp.com", 178 | "name": "Steve" 179 | }, 180 | "duration": { 181 | "startTime": "2020-07-21T16:04:40.99Z", 182 | "endTime": "2020-07-21T16:04:41.99Z" 183 | } 184 | }, 185 | { 186 | "payload": { 187 | "content": "Indians are really very current Indians are great people but sometimes pay." 188 | }, 189 | "from": { 190 | "userId": "steve@abccorp.com", 191 | "name": "Steve" 192 | }, 193 | "duration": { 194 | "startTime": "2020-07-21T16:04:42.99Z", 195 | "endTime": "2020-07-21T16:04:43.99Z" 196 | } 197 | }, 198 | { 199 | "payload": { 200 | "content": "Also, very violent." 201 | }, 202 | "from": { 203 | "userId": "steve@abccorp.com", 204 | "name": "Steve" 205 | }, 206 | "duration": { 207 | "startTime": "2020-07-21T16:08:44.99Z", 208 | "endTime": "2020-07-21T16:08:45.99Z" 209 | } 210 | } 211 | ], 212 | "entities": [ 213 | { 214 | "customType": "identify_org", 215 | "text": "platform" 216 | } 217 | ] 218 | 219 | } 220 | 221 | conversation_object = symbl.Text.process(payload=payload2) 222 | print(conversation_object.get_members()) 223 | print(conversation_object.delete_conversation()) 224 | print(conversation_object.get_messages()) 225 | 226 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Delete_APIs/delete_api_conversationApi.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | #to use the delete API using ConversationApi class 3 | conversation_id = "1234567890" 4 | print(symbl.Conversations.delete_conversation(conversation_id)) 5 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Delete_APIs/delete_api_streaming.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | events = { 3 | 'message': lambda response: print(response), 4 | 'message_response': lambda response: print('Final Messages -> ', [ message['payload']['content'] for message in response['messages']]) 5 | } 6 | connection_object = symbl.Streaming.start_connection(insight_types=['question', 'action_item'],speaker= { 7 | 'userId': 'abc@example.com', 8 | 'name': 'abc', 9 | }) 10 | connection_object.subscribe(events) 11 | 12 | connection_object.send_audio_from_mic() 13 | 14 | print(connection_object.conversation.delete_conversation()) -------------------------------------------------------------------------------- /example/Conversation_APIs/Delete_APIs/delete_api_telephony_pstn.py: -------------------------------------------------------------------------------- 1 | 2 | import symbl 3 | 4 | 5 | phoneNumber = "" # Zoom phone number to be called, which is mentioned into the Zoom meeting invitation 6 | meetingId = "" # Your zoom meetingId 7 | password = "" # Your zoom meeting passcode 8 | emailId = "" # Email address on which you would like to receive the detailed summary of the meeting 9 | 10 | 11 | # here are all events supported by Telephony API, you just need to uncomment the event which you would like to use 12 | events = { 13 | 'transcript_response': lambda transcript: print('printing the transcript response ', str(transcript)) 14 | ,'message_response': lambda message: print('printing the message response ', str(message)) 15 | #,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 16 | #,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 17 | } 18 | 19 | connection_object = symbl.Telephony.start_pstn( 20 | phone_number=phoneNumber, 21 | dtmf = ",,{}#,,{}#".format(meetingId, password), 22 | actions = [ 23 | { 24 | "invokeOn": "stop", 25 | "name": "sendSummaryEmail", 26 | "parameters": { 27 | "emails": [ 28 | emailId 29 | ], 30 | }, 31 | }, 32 | ]) 33 | 34 | connection_object.subscribe(events) 35 | 36 | connection_object.conversation.delete_conversation() 37 | 38 | # you can get the response from the conversation object, when you will stop the connection explicitly using keyboard interrupt or by using 39 | # connection_object.stop() # you can also stop the connection after sspecifying some interval of timing 40 | 41 | # To get the message from the meeting 42 | #print(connection_object.conversation.get_messages()) 43 | 44 | #To get the conversation data from the conversation 45 | #print(connection_object.conversation.get_conversation()) 46 | 47 | # To get the action items from the meeting 48 | # print(connection_object.conversation.get_action_items()) 49 | 50 | # To get the follow ups from the meeting 51 | # print(connection_object.conversation.get_follow_ups()) 52 | 53 | # To get the members information from the meeting 54 | # print(connection_object.conversation.get_members()) 55 | 56 | # To get the topics from the meeting 57 | # print(connection_object.conversation.get_topics()) 58 | 59 | # To get the questions from the meeting 60 | # print(connection_object.conversation.get_questions()) 61 | 62 | # To get the analytics from the conversation 63 | #print(connection_object.conversation.get_analytics()) 64 | 65 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Delete_APIs/delete_api_telephony_sip.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Threads import Thread 2 | import symbl 3 | import time 4 | 5 | # here are all events supported by Telephony API, you just need to uncomment the event which you would like to use 6 | events = { 7 | 'transcript_response': lambda transcript: print('printing the transcript response ', str(transcript)) 8 | ,'message_response': lambda message: print('printing the message response ', str(message)) 9 | #,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 10 | #,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 11 | } 12 | 13 | sip_uri = "" 14 | connection_object = symbl.Telephony.start_sip(uri=sip_uri) 15 | 16 | connection_object.subscribe(events) 17 | 18 | print(connection_object.conversation.delete_conversation()) 19 | 20 | # you can get the response from the conversation object, when you will stop the connection explicitly using keyboard interrupt or by using 21 | # connection_object.stop() 22 | 23 | # To get the message from the conversation 24 | #print(connection_object.conversation.get_messages()) 25 | 26 | #To get the conversation data from the conversation 27 | #print(connection_object.conversation.get_conversation()) 28 | 29 | # To get the action items from the conversation 30 | # print(connection_object.conversation.get_action_items()) 31 | 32 | # To get the follow ups from the conversation 33 | # print(connection_object.conversation.get_follow_ups()) 34 | 35 | # To get the members information from the conversation 36 | # print(connection_object.conversation.get_members()) 37 | 38 | # To get the topics from the conversation 39 | # print(connection_object.conversation.get_topics()) 40 | 41 | # To get the questions from the conversation 42 | # print(connection_object.conversation.get_questions()) 43 | 44 | # To get the analytics from the conversation 45 | #print(connection_object.conversation.get_analytics()) 46 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Get_APIs/conversation_class.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | conversation_id=5180676375576576 # Update with the conversation Id of your conversation 4 | 5 | print(symbl.Conversations.get_messages(conversation_id)) 6 | print(symbl.Conversations.get_conversation(conversation_id)) 7 | print(symbl.Conversations.get_action_items(conversation_id)) 8 | print(symbl.Conversations.get_follow_ups(conversation_id)) 9 | print(symbl.Conversations.get_members(conversation_id)) 10 | print(symbl.Conversations.get_topics(conversation_id)) 11 | print(symbl.Conversations.get_questions(conversation_id)) 12 | print(symbl.Conversations.get_analytics(conversation_id)) 13 | print(symbl.Conversations.get_trackers(conversation_id)) 14 | print(symbl.Conversations.get_entities(conversation_id)) -------------------------------------------------------------------------------- /example/Conversation_APIs/Post_APIs/formatted_transcript_ConversationApi.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | #to test API using ConversationApi class 3 | 4 | payload = { 5 | 'contentType': 'text/markdown', 6 | # 'contentType': 'text/srt', 7 | 'createParagraphs': "true", 8 | 'phrases': { 9 | 'highlightOnlyInsightKeyPhrases': "true", 10 | 'highlightAllKeyPhrases': "true" 11 | }, 12 | 'showSpeakerSeparation': "true" 13 | } 14 | conversation_id = "1234567890" #update with your conversation id 15 | print(symbl.Conversations.get_formatted_transcript(parameters=payload,conversation_id=conversation_id)) 16 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Post_APIs/formatted_transcript_api_async.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | payload2 = { 4 | "name": "TestingTextAPI", 5 | "trackers": [{ 6 | "name": "text_tracker", 7 | "vocabulary": [ 8 | "indians", 9 | "thoughts", 10 | "vaccination" 11 | ] 12 | }], 13 | "detectEntities": "true", 14 | "enableAllTrackers": "true", 15 | "messages": [ 16 | { 17 | "payload": { 18 | "content": "Let us talk about India Australia Testaments start seeing each other in the past as much we thought like it will be one-sided." 19 | }, 20 | "from": { 21 | "userId": "natalia@example.com", 22 | "name": "Natalia" 23 | }, 24 | "duration": { 25 | "startTime": "2020-07-21T16:04:19.01Z", 26 | "endTime": "2020-07-21T16:04:20.99Z" 27 | } 28 | }, 29 | { 30 | "payload": { 31 | "content": "Only working with is occupied at the moment:blush: , but just generally /there's going to be a lot of (infographic) making done and if you would like to make them yourself I can give you access to then gauge:confetti_ball: . ." 32 | }, 33 | "from": { 34 | "userId": "mike@abccorp.com", 35 | "name": "Mike" 36 | }, 37 | "duration": { 38 | "startTime": "2020-07-21T16:04:19.99Z", 39 | "endTime": "2020-07-21T16:04:20.99Z" 40 | } 41 | }, 42 | { 43 | "payload": { 44 | "content": "We wouldn't have thought like India can win that test match. tomorrow on 21 june, 2021 is our match i think" 45 | }, 46 | "from": { 47 | "userId": "natalia@example.com", 48 | "name": "Natalia" 49 | }, 50 | "duration": { 51 | "startTime": "2020-07-21T16:04:20.99Z", 52 | "endTime": "2020-07-21T16:04:21.99Z" 53 | } 54 | }, 55 | { 56 | "payload": { 57 | "content": "Yeah, no one could ever like predict that India would dominate all the other matches against really started harassing Indians alone, including mom." 58 | }, 59 | "from": { 60 | "userId": "steve@abccorp.com", 61 | "name": "Steve" 62 | }, 63 | "duration": { 64 | "startTime": "2020-07-21T16:04:20.99Z", 65 | "endTime": "2020-07-21T16:04:22.99Z" 66 | } 67 | }, 68 | { 69 | "payload": { 70 | "content": "Other mothers but against give it to fight and Australians also then accepted the defeat gracefully and congratulated the Indian team for their unique victory." 71 | }, 72 | "from": { 73 | "userId": "natalia@example.com", 74 | "name": "Natalia" 75 | }, 76 | "duration": { 77 | "startTime": "2020-07-21T16:04:23.99Z", 78 | "endTime": "2020-07-21T16:04:24.99Z" 79 | } 80 | }, 81 | { 82 | "payload": { 83 | "content": "And a test on this much." 84 | }, 85 | "from": { 86 | "userId": "mike@abccorp.com", 87 | "name": "Mike" 88 | }, 89 | "duration": { 90 | "startTime": "2020-07-21T16:04:25.99Z", 91 | "endTime": "2020-07-21T16:04:26.99Z" 92 | } 93 | }, 94 | { 95 | "payload": { 96 | "content": "We will again have to talk based on the topic Rohit water to the topics, and we see the topics that are generated here." 97 | }, 98 | "from": { 99 | "userId": "natalia@example.com", 100 | "name": "Natalia" 101 | }, 102 | "duration": { 103 | "startTime": "2020-07-21T16:04:27.99Z", 104 | "endTime": "2020-07-21T16:04:29.99Z" 105 | } 106 | }, 107 | { 108 | "payload": { 109 | "content": "I don't know what clean water spaghetti tacos." 110 | }, 111 | "from": { 112 | "userId": "steve@abccorp.com", 113 | "name": "Steve" 114 | }, 115 | "duration": { 116 | "startTime": "2020-07-21T16:04:30.99Z", 117 | "endTime": "2020-07-21T16:04:31.99Z" 118 | } 119 | }, 120 | { 121 | "payload": { 122 | "content": "Australian distance Indians, so we can talk about Indians." 123 | }, 124 | "from": { 125 | "userId": "steve@abccorp.com", 126 | "name": "Steve" 127 | }, 128 | "duration": { 129 | "startTime": "2020-07-21T16:04:32.99Z", 130 | "endTime": "2020-07-21T16:04:33.99Z" 131 | } 132 | }, 133 | { 134 | "payload": { 135 | "content": "Yeah, yeah, we can talk about it." 136 | }, 137 | "from": { 138 | "userId": "natalia@example.com", 139 | "name": "Natalia" 140 | }, 141 | "duration": { 142 | "startTime": "2020-07-21T16:04:34.99Z", 143 | "endTime": "2020-07-21T16:04:35.99Z" 144 | } 145 | }, 146 | { 147 | "payload": { 148 | "content": "Let us talk about the Indian culture." 149 | }, 150 | "from": { 151 | "userId": "mike@abccorp.com", 152 | "name": "Mike" 153 | }, 154 | "duration": { 155 | "startTime": "2020-07-21T16:04:36.99Z", 156 | "endTime": "2020-07-21T16:04:37.99Z" 157 | } 158 | }, 159 | { 160 | "payload": { 161 | "content": "We will again have to talk based on the topic Rohit water to the topics, and we see the topics that are generated here." 162 | }, 163 | "from": { 164 | "userId": "natalia@example.com", 165 | "name": "Natalia" 166 | }, 167 | "duration": { 168 | "startTime": "2020-07-21T16:04:38.99Z", 169 | "endTime": "2020-07-21T16:04:40.99Z" 170 | } 171 | }, 172 | { 173 | "payload": { 174 | "content": "Let us go." 175 | }, 176 | "from": { 177 | "userId": "steve@abccorp.com", 178 | "name": "Steve" 179 | }, 180 | "duration": { 181 | "startTime": "2020-07-21T16:04:40.99Z", 182 | "endTime": "2020-07-21T16:04:41.99Z" 183 | } 184 | }, 185 | { 186 | "payload": { 187 | "content": "Indians are really very current Indians are great people but sometimes pay." 188 | }, 189 | "from": { 190 | "userId": "steve@abccorp.com", 191 | "name": "Steve" 192 | }, 193 | "duration": { 194 | "startTime": "2020-07-21T16:04:42.99Z", 195 | "endTime": "2020-07-21T16:04:43.99Z" 196 | } 197 | }, 198 | { 199 | "payload": { 200 | "content": "Also, very violent." 201 | }, 202 | "from": { 203 | "userId": "steve@abccorp.com", 204 | "name": "Steve" 205 | }, 206 | "duration": { 207 | "startTime": "2020-07-21T16:08:44.99Z", 208 | "endTime": "2020-07-21T16:08:45.99Z" 209 | } 210 | } 211 | ], 212 | "entities": [ 213 | { 214 | "customType": "identify_org", 215 | "text": "platform" 216 | } 217 | ] 218 | 219 | } 220 | payload = { 221 | 'contentType': 'text/markdown', 222 | # 'contentType': 'text/srt', 223 | 'createParagraphs': "true", 224 | 'phrases': { 225 | 'highlightOnlyInsightKeyPhrases': "true", 226 | 'highlightAllKeyPhrases': "true" 227 | }, 228 | 'showSpeakerSeparation': "true" 229 | } 230 | 231 | #to test API using conversation class 232 | conversation_object = symbl.Text.process(payload=payload2) 233 | print(conversation_object.get_formatted_transcript(parameters= payload)) 234 | 235 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Post_APIs/formatted_transcript_api_streaming.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | events = { 3 | 'message': lambda response: print(response), 4 | 'message_response': lambda response: print('Final Messages -> ', [ message['payload']['content'] for message in response['messages']]) 5 | } 6 | connection_object = symbl.Streaming.start_connection(insight_types=['question', 'action_item'],speaker= { 7 | 'userId': 'abc@example.com', 8 | 'name': 'abc', 9 | }) 10 | connection_object.subscribe(events) 11 | 12 | connection_object.send_audio_from_mic() 13 | 14 | payload = { 15 | 'contentType': 'text/markdown', 16 | # 'contentType': 'text/srt', 17 | 'createParagraphs': "true", 18 | 'phrases': { 19 | 'highlightOnlyInsightKeyPhrases': "true", 20 | 'highlightAllKeyPhrases': "true" 21 | }, 22 | 'showSpeakerSeparation': "true" 23 | } 24 | print(connection_object.conversation.get_formatted_transcript(parameters= payload)) -------------------------------------------------------------------------------- /example/Conversation_APIs/Post_APIs/formatted_transcript_api_telephony_pstn.py: -------------------------------------------------------------------------------- 1 | 2 | import symbl 3 | 4 | 5 | phoneNumber = "" # Zoom phone number to be called, which is mentioned into the Zoom meeting invitation 6 | meetingId = "" # Your zoom meetingId 7 | password = "" # Your zoom meeting passcode 8 | emailId = "" # Email address on which you would like to receive the detailed summary of the meeting 9 | 10 | 11 | # here are all events supported by Telephony API, you just need to uncomment the event which you would like to use 12 | events = { 13 | 'transcript_response': lambda transcript: print('printing the transcript response ', str(transcript)) 14 | ,'message_response': lambda message: print('printing the message response ', str(message)) 15 | #,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 16 | #,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 17 | } 18 | 19 | connection_object = symbl.Telephony.start_pstn( 20 | phone_number=phoneNumber, 21 | dtmf = ",,{}#,,{}#".format(meetingId, password), 22 | actions = [ 23 | { 24 | "invokeOn": "stop", 25 | "name": "sendSummaryEmail", 26 | "parameters": { 27 | "emails": [ 28 | emailId 29 | ], 30 | }, 31 | }, 32 | ]) 33 | 34 | connection_object.subscribe(events) 35 | 36 | payload = { 37 | 'contentType': 'text/markdown', 38 | # 'contentType': 'text/srt', 39 | 'createParagraphs': "true", 40 | 'phrases': { 41 | 'highlightOnlyInsightKeyPhrases': "true", 42 | 'highlightAllKeyPhrases': "true" 43 | }, 44 | 'showSpeakerSeparation': "true" 45 | } 46 | print(connection_object.conversation.get_formatted_transcript(parameters= payload)) 47 | 48 | # you can get the response from the conversation object, when you will stop the connection explicitly using keyboard interrupt or by using 49 | # connection_object.stop() # you can also stop the connection after sspecifying some interval of timing 50 | 51 | # To get the message from the meeting 52 | #print(connection_object.conversation.get_messages()) 53 | 54 | #To get the conversation data from the conversation 55 | #print(connection_object.conversation.get_conversation()) 56 | 57 | # To get the action items from the meeting 58 | # print(connection_object.conversation.get_action_items()) 59 | 60 | # To get the follow ups from the meeting 61 | # print(connection_object.conversation.get_follow_ups()) 62 | 63 | # To get the members information from the meeting 64 | # print(connection_object.conversation.get_members()) 65 | 66 | # To get the topics from the meeting 67 | # print(connection_object.conversation.get_topics()) 68 | 69 | # To get the questions from the meeting 70 | # print(connection_object.conversation.get_questions()) 71 | 72 | # To get the analytics from the conversation 73 | #print(connection_object.conversation.get_analytics()) 74 | 75 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Post_APIs/formatted_transcript_api_telephony_sip.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Threads import Thread 2 | import symbl 3 | import time 4 | 5 | # here are all events supported by Telephony API, you just need to uncomment the event which you would like to use 6 | events = { 7 | 'transcript_response': lambda transcript: print('printing the transcript response ', str(transcript)) 8 | ,'message_response': lambda message: print('printing the message response ', str(message)) 9 | #,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 10 | #,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 11 | } 12 | 13 | sip_uri = "" 14 | connection_object = symbl.Telephony.start_sip(uri=sip_uri) 15 | 16 | connection_object.subscribe(events) 17 | 18 | 19 | payload = { 20 | 'contentType': 'text/markdown', 21 | # 'contentType': 'text/srt', 22 | 'createParagraphs': "true", 23 | 'phrases': { 24 | 'highlightOnlyInsightKeyPhrases': "true", 25 | 'highlightAllKeyPhrases': "true" 26 | }, 27 | 'showSpeakerSeparation': "true" 28 | } 29 | print(connection_object.conversation.get_formatted_transcript(parameters= payload)) 30 | 31 | # you can get the response from the conversation object, when you will stop the connection explicitly using keyboard interrupt or by using 32 | # connection_object.stop() 33 | 34 | # To get the message from the conversation 35 | #print(connection_object.conversation.get_messages()) 36 | 37 | #To get the conversation data from the conversation 38 | #print(connection_object.conversation.get_conversation()) 39 | 40 | # To get the action items from the conversation 41 | # print(connection_object.conversation.get_action_items()) 42 | 43 | # To get the follow ups from the conversation 44 | # print(connection_object.conversation.get_follow_ups()) 45 | 46 | # To get the members information from the conversation 47 | # print(connection_object.conversation.get_members()) 48 | 49 | # To get the topics from the conversation 50 | # print(connection_object.conversation.get_topics()) 51 | 52 | # To get the questions from the conversation 53 | # print(connection_object.conversation.get_questions()) 54 | 55 | # To get the analytics from the conversation 56 | #print(connection_object.conversation.get_analytics()) 57 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Put_APIs/put_apis_example.py: -------------------------------------------------------------------------------- 1 | from symbl import Text 2 | 3 | payload = { 4 | "name": "TestingTextAPI", 5 | "trackers": [{ 6 | "name": "text_tracker", 7 | "vocabulary": [ 8 | "Testaments", 9 | "thoughts", 10 | "vaccination" 11 | ] 12 | }], 13 | "detectEntities": "true", 14 | "enableAllTrackers": "true", 15 | "messages": [ 16 | { 17 | "payload": { 18 | "content": "Let us talk about India Australia Testaments start seeing each other in the past as much we thought like it will be one-sided." 19 | }, 20 | "from": { 21 | "userId": "natalia@example.com", 22 | "name": "Natalia" 23 | }, 24 | "duration": { 25 | "startTime": "2020-07-21T16:04:19.01Z", 26 | "endTime": "2020-07-21T16:04:20.99Z" 27 | } 28 | }, 29 | { 30 | "payload": { 31 | "content": "Only working with is occupied at the moment:blush: , but just generally /there's going to be a lot of (infographic) making done and if you would like to make them yourself I can give you access to then gauge:confetti_ball: . ." 32 | }, 33 | "from": { 34 | "userId": "mike@abccorp.com", 35 | "name": "Mike" 36 | }, 37 | "duration": { 38 | "startTime": "2020-07-21T16:04:19.99Z", 39 | "endTime": "2020-07-21T16:04:20.99Z" 40 | } 41 | }, 42 | { 43 | "payload": { 44 | "content": "We wouldn't have thought like India can win that test match. tomorrow on 21 june, 2021 is our match i think" 45 | }, 46 | "from": { 47 | "userId": "natalia@example.com", 48 | "name": "Natalia" 49 | }, 50 | "duration": { 51 | "startTime": "2020-07-21T16:04:20.99Z", 52 | "endTime": "2020-07-21T16:04:21.99Z" 53 | } 54 | }, 55 | { 56 | "payload": { 57 | "content": "Yeah, no one could ever like predict that India would dominate all the other matches against really started harassing Indians alone, including mom." 58 | }, 59 | "from": { 60 | "userId": "steve@abccorp.com", 61 | "name": "Steve" 62 | }, 63 | "duration": { 64 | "startTime": "2020-07-21T16:04:20.99Z", 65 | "endTime": "2020-07-21T16:04:22.99Z" 66 | } 67 | }, 68 | { 69 | "payload": { 70 | "content": "Other mothers but against give it to fight and Australians also then accepted the defeat gracefully and congratulated the Indian team for their unique victory." 71 | }, 72 | "from": { 73 | "userId": "natalia@example.com", 74 | "name": "Natalia" 75 | }, 76 | "duration": { 77 | "startTime": "2020-07-21T16:04:23.99Z", 78 | "endTime": "2020-07-21T16:04:24.99Z" 79 | } 80 | }, 81 | { 82 | "payload": { 83 | "content": "And a test on this much." 84 | }, 85 | "from": { 86 | "userId": "mike@abccorp.com", 87 | "name": "Mike" 88 | }, 89 | "duration": { 90 | "startTime": "2020-07-21T16:04:25.99Z", 91 | "endTime": "2020-07-21T16:04:26.99Z" 92 | } 93 | }, 94 | { 95 | "payload": { 96 | "content": "We will again have to talk based on the topic Rohit water to the topics, and we see the topics that are generated here." 97 | }, 98 | "from": { 99 | "userId": "natalia@example.com", 100 | "name": "Natalia" 101 | }, 102 | "duration": { 103 | "startTime": "2020-07-21T16:04:27.99Z", 104 | "endTime": "2020-07-21T16:04:29.99Z" 105 | } 106 | }, 107 | { 108 | "payload": { 109 | "content": "I don't know what clean water spaghetti tacos." 110 | }, 111 | "from": { 112 | "userId": "steve@abccorp.com", 113 | "name": "Steve" 114 | }, 115 | "duration": { 116 | "startTime": "2020-07-21T16:04:30.99Z", 117 | "endTime": "2020-07-21T16:04:31.99Z" 118 | } 119 | }, 120 | { 121 | "payload": { 122 | "content": "Australian distance Indians, so we can talk about Indians." 123 | }, 124 | "from": { 125 | "userId": "steve@abccorp.com", 126 | "name": "Steve" 127 | }, 128 | "duration": { 129 | "startTime": "2020-07-21T16:04:32.99Z", 130 | "endTime": "2020-07-21T16:04:33.99Z" 131 | } 132 | }, 133 | { 134 | "payload": { 135 | "content": "Yeah, yeah, we can talk about it." 136 | }, 137 | "from": { 138 | "userId": "natalia@example.com", 139 | "name": "Natalia" 140 | }, 141 | "duration": { 142 | "startTime": "2020-07-21T16:04:34.99Z", 143 | "endTime": "2020-07-21T16:04:35.99Z" 144 | } 145 | }, 146 | { 147 | "payload": { 148 | "content": "Let us talk about the Indian culture." 149 | }, 150 | "from": { 151 | "userId": "mike@abccorp.com", 152 | "name": "Mike" 153 | }, 154 | "duration": { 155 | "startTime": "2020-07-21T16:04:36.99Z", 156 | "endTime": "2020-07-21T16:04:37.99Z" 157 | } 158 | }, 159 | { 160 | "payload": { 161 | "content": "We will again have to talk based on the topic Rohit water to the topics, and we see the topics that are generated here." 162 | }, 163 | "from": { 164 | "userId": "natalia@example.com", 165 | "name": "Natalia" 166 | }, 167 | "duration": { 168 | "startTime": "2020-07-21T16:04:38.99Z", 169 | "endTime": "2020-07-21T16:04:40.99Z" 170 | } 171 | }, 172 | { 173 | "payload": { 174 | "content": "Let us go." 175 | }, 176 | "from": { 177 | "userId": "steve@abccorp.com", 178 | "name": "Steve" 179 | }, 180 | "duration": { 181 | "startTime": "2020-07-21T16:04:40.99Z", 182 | "endTime": "2020-07-21T16:04:41.99Z" 183 | } 184 | }, 185 | { 186 | "payload": { 187 | "content": "Indians are really very current Indians are great people but sometimes pay." 188 | }, 189 | "from": { 190 | "userId": "steve@abccorp.com", 191 | "name": "Steve" 192 | }, 193 | "duration": { 194 | "startTime": "2020-07-21T16:04:42.99Z", 195 | "endTime": "2020-07-21T16:04:43.99Z" 196 | } 197 | }, 198 | { 199 | "payload": { 200 | "content": "Also, very violent." 201 | }, 202 | "from": { 203 | "userId": "steve@abccorp.com", 204 | "name": "Steve" 205 | }, 206 | "duration": { 207 | "startTime": "2020-07-21T16:08:44.99Z", 208 | "endTime": "2020-07-21T16:08:45.99Z" 209 | } 210 | } 211 | ], 212 | "entities": [ 213 | { 214 | "customType": "identify_org", 215 | "text": "platform" 216 | } 217 | ] 218 | 219 | } 220 | 221 | conversation = Text.process(payload=payload) 222 | 223 | #fectch conversation_id using conversaton object 224 | Conversation_id = conversation.get_conversation_id() 225 | 226 | #get the list of members from response of get_members 227 | list_of_members = conversation.get_members().members 228 | 229 | members_id = [] 230 | 231 | #store members id in list 232 | for i in list_of_members: 233 | members_id.append(i.id) 234 | 235 | id1 = members_id[0] 236 | id2 = members_id[1] 237 | 238 | #update member's details 239 | param = {"id": id1, 240 | "email": "john@example.in", 241 | "name": "john"} 242 | 243 | print(conversation.put_members(id1,parameters= param)) 244 | ''' 245 | payload={ 246 | "speakerEvents" : [ 247 | { 248 | "type": "started_speaking", 249 | "user": { 250 | "id": id1, 251 | "name": "john", 252 | "email": "john@example.com" 253 | }, 254 | "offset": { 255 | "seconds": 52, 256 | "nanos": 5000000000 257 | } 258 | }, 259 | { 260 | "type": "stopped_speaking", 261 | "user": { 262 | "id": id1, 263 | "name": "john", 264 | "email": "john@example.com" 265 | }, 266 | "offset": { 267 | "seconds": 15, 268 | "nanos": 5000000000 269 | } 270 | }, 271 | { 272 | "type": "started_speaking", 273 | "user": { 274 | "id":id2, 275 | "name": "Richard", 276 | "email": "Richard@example.com" 277 | }, 278 | "offset": { 279 | "seconds": 10, 280 | "nanos": 5000000000 281 | } 282 | }, 283 | { 284 | "type": "stopped_speaking", 285 | "user": { 286 | "id": id2, 287 | "name": "Richard", 288 | "email": "Richard@example.com" 289 | }, 290 | "offset": { 291 | "seconds": 20, 292 | "nanos": 5000000000 293 | } 294 | } 295 | ] 296 | } 297 | 298 | print(conversation.put_speakers_events(parameters= payload)) 299 | ''' 300 | print(conversation.get_members()) 301 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Put_APIs/put_members.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | Conversation_id = "1234567890" # update with valid conversation id 4 | id = "abcd" # write member's id 5 | 6 | param = {"id": id, 7 | "email": "john@example.in", 8 | "name": "john"} 9 | 10 | print(symbl.Conversations.put_members(Conversation_id, id, parameters= param)) 11 | print(symbl.Conversations.get_members(Conversation_id)) 12 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Put_APIs/put_speaker_events_async.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | id1 = "abcdefg" #member's Id 4 | id2 = "031c61c" #member's id 5 | 6 | payload={ 7 | "speakerEvents" : [ 8 | { 9 | "type": "started_speaking", 10 | "user": { 11 | "id": id1, 12 | "name": "john", 13 | "email": "john@example.com" 14 | }, 15 | "offset": { 16 | "seconds": 52, 17 | "nanos": 5000000000 18 | } 19 | }, 20 | { 21 | "type": "stopped_speaking", 22 | "user": { 23 | "id": id1, 24 | "name": "john", 25 | "email": "john@example.com" 26 | }, 27 | "offset": { 28 | "seconds": 15, 29 | "nanos": 5000000000 30 | } 31 | }, 32 | { 33 | "type": "started_speaking", 34 | "user": { 35 | "id":id2, 36 | "name": "Richard", 37 | "email": "Richard@example.com" 38 | }, 39 | "offset": { 40 | "seconds": 10, 41 | "nanos": 5000000000 42 | } 43 | }, 44 | { 45 | "type": "stopped_speaking", 46 | "user": { 47 | "id": id2, 48 | "name": "Richard", 49 | "email": "Richard@example.com" 50 | }, 51 | "offset": { 52 | "seconds": 20, 53 | "nanos": 5000000000 54 | } 55 | } 56 | ] 57 | } 58 | 59 | Conversation_id = "1234567890" # update with valid conversation id 60 | print(symbl.Conversations.put_speakers_events(Conversation_id, payload)) 61 | 62 | print(symbl.Conversations.get_members(Conversation_id)) 63 | -------------------------------------------------------------------------------- /example/Conversation_APIs/Put_APIs/put_speaker_events_streaming.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | id1 = "abcd" 3 | 4 | payload_streaming={ 5 | "speakerEvents" : [ 6 | { 7 | "type": "started_speaking", 8 | "user": { 9 | "id": id1, 10 | "name": "john", 11 | "email": "john@example.com" 12 | }, 13 | "offset": { 14 | "seconds": 52, 15 | "nanos": 5000000000 16 | } 17 | }, 18 | { 19 | "type": "stopped_speaking", 20 | "user": { 21 | "id": id1, 22 | "name": "john", 23 | "email": "john@example.com" 24 | }, 25 | "offset": { 26 | "seconds": 15, 27 | "nanos": 5000000000 28 | } 29 | } 30 | ] 31 | } 32 | Conversation_id = "1234567890" # update with valid conversation id 33 | print(symbl.Conversations.put_speakers_events(Conversation_id, payload_streaming)) 34 | 35 | print(symbl.Conversations.get_members(Conversation_id)) 36 | -------------------------------------------------------------------------------- /example/Streaming_API/streaming.py: -------------------------------------------------------------------------------- 1 | import symbl 2 | 3 | 4 | # here you can add more events which support the Streaming API like 'insight_response', 'message_response' etc. 5 | events = { 6 | 'message': lambda message: print('printing the message response ', str(message)) 7 | # ,'message_response': lambda message: print('printing the transcription', str(message)) 8 | # ,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 9 | ,'tracker_response': lambda tracker: print('printing the tracker response ', str(tracker)) 10 | # ,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 11 | } 12 | 13 | 14 | #To access the Trackers API, you will need to specify 'tracker_response' event 15 | trackers = [{ 16 | "name": "tracker_name", 17 | "vocabulary": [ 18 | "hello", 19 | "vocabulary_2", 20 | "vocabulary_n" 21 | ] 22 | }] 23 | 24 | 25 | insight_types = ['question', 'action_item'] 26 | 27 | speaker = { 28 | 'userId': 'John@example.com', 29 | 'name': 'John', 30 | } 31 | 32 | connection_object = symbl.Streaming.start_connection( 33 | insight_types=insight_types, speaker=speaker,trackers=trackers) 34 | 35 | connection_object.subscribe(events) 36 | 37 | connection_object.send_audio_from_mic() 38 | 39 | # you can get the response from the conversation object, when you will stop the connection explicitly 40 | # or Python SDK will detect the silence in the on going conversation 41 | 42 | #To get the message from the conversation 43 | #print(connection_object.conversation.get_messages()) 44 | 45 | #To get the conversation data from the conversation 46 | #print(connection_object.conversation.get_conversation()) 47 | 48 | #To get the action items from the conversation 49 | #print(connection_object.conversation.get_action_items()) 50 | 51 | #To get the follow ups from the conversation 52 | #print(connection_object.conversation.get_follow_ups()) 53 | 54 | #To get the members information from the conversation 55 | #print(connection_object.conversation.get_members()) 56 | 57 | #To get the topics from the conversation 58 | #print(connection_object.conversation.get_topics()) 59 | 60 | #To get the questions from the conversation 61 | #print(connection_object.conversation.get_questions()) 62 | 63 | # To get the analytics from the conversation 64 | #print(connection_object.conversation.get_analytics()) 65 | 66 | -------------------------------------------------------------------------------- /example/Telephony_API/telephony_pstn.py: -------------------------------------------------------------------------------- 1 | 2 | import symbl 3 | 4 | 5 | phoneNumber = "" # Zoom phone number to be called, which is mentioned into the Zoom meeting invitation 6 | meetingId = "" # Your zoom meetingId 7 | password = "" # Your zoom meeting passcode 8 | emailId = "" # Email address on which you would like to receive the detailed summary of the meeting 9 | 10 | 11 | # here are all events supported by Telephony API, you just need to uncomment the event which you would like to use 12 | events = { 13 | 'transcript_response': lambda transcript: print('printing the transcript response ', str(transcript)) 14 | ,'message_response': lambda message: print('printing the message response ', str(message)) 15 | #,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 16 | #,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 17 | } 18 | 19 | connection_object = symbl.Telephony.start_pstn( 20 | phone_number=phoneNumber, 21 | dtmf = ",,{}#,,{}#".format(meetingId, password), 22 | actions = [ 23 | { 24 | "invokeOn": "stop", 25 | "name": "sendSummaryEmail", 26 | "parameters": { 27 | "emails": [ 28 | emailId 29 | ], 30 | }, 31 | }, 32 | ]) 33 | 34 | connection_object.subscribe(events) 35 | 36 | # you can get the response from the conversation object, when you will stop the connection explicitly using keyboard interrupt or by using 37 | # connection_object.stop() # you can also stop the connection after sspecifying some interval of timing 38 | 39 | # To get the message from the meeting 40 | #print(connection_object.conversation.get_messages()) 41 | 42 | #To get the conversation data from the conversation 43 | #print(connection_object.conversation.get_conversation()) 44 | 45 | # To get the action items from the meeting 46 | # print(connection_object.conversation.get_action_items()) 47 | 48 | # To get the follow ups from the meeting 49 | # print(connection_object.conversation.get_follow_ups()) 50 | 51 | # To get the members information from the meeting 52 | # print(connection_object.conversation.get_members()) 53 | 54 | # To get the topics from the meeting 55 | # print(connection_object.conversation.get_topics()) 56 | 57 | # To get the questions from the meeting 58 | # print(connection_object.conversation.get_questions()) 59 | 60 | # To get the analytics from the conversation 61 | #print(connection_object.conversation.get_analytics()) 62 | 63 | -------------------------------------------------------------------------------- /example/Telephony_API/telephony_sip.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Threads import Thread 2 | import symbl 3 | import time 4 | 5 | # here are all events supported by Telephony API, you just need to uncomment the event which you would like to use 6 | events = { 7 | 'transcript_response': lambda transcript: print('printing the transcript response ', str(transcript)) 8 | ,'message_response': lambda message: print('printing the message response ', str(message)) 9 | #,'insight_response': lambda insight: print('printing the insight response ', str(insight)) 10 | #,'topic_response': lambda topic: print('printing the topic response ', str(topic)) 11 | } 12 | 13 | sip_uri = "" 14 | connection_object = symbl.Telephony.start_sip(uri=sip_uri) 15 | 16 | connection_object.subscribe(events) 17 | 18 | # you can get the response from the conversation object, when you will stop the connection explicitly using keyboard interrupt or by using 19 | # connection_object.stop() 20 | 21 | # To get the message from the conversation 22 | #print(connection_object.conversation.get_messages()) 23 | 24 | #To get the conversation data from the conversation 25 | #print(connection_object.conversation.get_conversation()) 26 | 27 | # To get the action items from the conversation 28 | # print(connection_object.conversation.get_action_items()) 29 | 30 | # To get the follow ups from the conversation 31 | # print(connection_object.conversation.get_follow_ups()) 32 | 33 | # To get the members information from the conversation 34 | # print(connection_object.conversation.get_members()) 35 | 36 | # To get the topics from the conversation 37 | # print(connection_object.conversation.get_topics()) 38 | 39 | # To get the questions from the conversation 40 | # print(connection_object.conversation.get_questions()) 41 | 42 | # To get the analytics from the conversation 43 | #print(connection_object.conversation.get_analytics()) 44 | -------------------------------------------------------------------------------- /example/use_cases/NonBlockingCallsExample.py: -------------------------------------------------------------------------------- 1 | # Non-blocking calls means that if an response can't be returned rapidly, the API returns immediately with an error and does nothing else 2 | # or returns response after the completion of the execution. 3 | # so, in this code snippet we have defined functions for error and success response which will be called automatically by API 4 | 5 | import symbl 6 | 7 | # A success callback to be triggered if Job is completed 8 | def success_callback(conversation_object): 9 | print(conversation_object.get_messages()) 10 | 11 | # An error callback to be triggered if Job is Failed 12 | def error_callback(conversation_object): 13 | print('job with jobId {0} has failed!'.format(conversation_object.job_id)) 14 | 15 | # Non blocking call to API to process audio file 16 | symbl.Audio.process_file(file_path="", wait=False).on_complete(success_callback).on_error(error_callback) 17 | 18 | print("Continued Execution without a blocking call!") -------------------------------------------------------------------------------- /example/use_cases/PrettyPrintTranscription.py: -------------------------------------------------------------------------------- 1 | # Sample Code to pretty print all the messages 2 | # Using this example you can extract the different keys from response object returned by conversation object 3 | 4 | import symbl 5 | 6 | # API call to process audio file 7 | conversation_object = symbl.Audio.process_file(file_path="") 8 | 9 | # Function to extract a key from the json array response 10 | extract_text = lambda responses : [response.text for response in responses] 11 | 12 | # Function to print each message of a text_array on a new line 13 | pretty_print = lambda text_array : [print(text) for text in text_array] 14 | 15 | # Fetching actual transcripts using the conversationId 16 | response = conversation_object.get_messages() 17 | 18 | # pretty printing transcripts (every message on a new line) 19 | pretty_print(extract_text(response.messages)) -------------------------------------------------------------------------------- /example/use_cases/SpeechToTextOfDirectoryOfAudioFiles.py: -------------------------------------------------------------------------------- 1 | # SpeechToText of multiple audio files in a directory 2 | # Using this code snippet you can convert multiple audio files from specific directory to the text format 3 | # It will make seperate .txt files for each audio file into the given directory path and will save the transcriptions of all audio files into those .txt files 4 | 5 | import symbl 6 | from os import listdir 7 | from os.path import isfile, join 8 | 9 | # returns lambda function with fileName which is under processing 10 | def save_transcriptions_in_file(fileName): 11 | return lambda conversation_object: on_success(conversation_object, fileName) 12 | 13 | # returns actual callback to save the transcriptions of a conversation in a file 14 | def on_success(conversation_object, fileName): 15 | transcriptions = conversation_object.get_messages() 16 | 17 | file = open(fileName + ".txt","w+") 18 | file.write(str(transcriptions)) 19 | file.close() 20 | 21 | # An ‘r’ preceding a string denotes a raw, (almost) un-escaped string. 22 | # The escape character is backslash, that is why a normal string will not work as a Windows path string. 23 | # If you are using Mac or Linux, no need to append `r` before 24 | directory_path = r'' 25 | 26 | files = [join(directory_path, file) for file in listdir(directory_path) if isfile(join(directory_path, file))] 27 | 28 | # Process audio files in the above mentioned directory 29 | for file in files: 30 | job = symbl.Audio.process_file( 31 | # credentials={app_id: , app_secret: }, #Optional, Don't add this parameter if you have symbl.conf file in your home directory 32 | file_path=file, wait=False).on_complete(save_transcriptions_in_file(file)) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Symbl Python SDK 2 | 3 | The Symbl Python SDK provides convenient access to the Symbl API from applications written in the Python language. It includes a pre-defined set of classes for a simple and clear utilization of APIs. 4 | 5 | ## Documentation 6 | 7 | See the [Python API docs](https://docs.symbl.ai/docs/). 8 | 9 | ### Requirements 10 | 11 | - Python 2.7+ or Python 3.4+ (PyPy supported) 12 | > 13 | >just run the command mentioned below in your terminal to know the version of Python installed in your system: 14 | 15 | ```sh 16 | python --version 17 | ``` 18 | 19 | ## Installation 20 | 21 | First make sure that Python is installed in your system. 22 | 23 | To install the python, just visit the links mentioned below: 24 | 25 | - Windows: https://phoenixnap.com/kb/how-to-install-python-3-windows 26 | - Mac: https://flaviocopes.com/python-installation-macos/ 27 | 28 | You don't need this source code unless you want to modify the package. If you just 29 | want to use the package, then you can install it, either using 'pip' or with 'source': 30 | 31 | >just run the command mentioned below to install using 'pip' 32 | > 33 | >Use `pip` if you are working on Python 2.x 34 | > 35 | >Use `pip3` if you are working on Python 3.x 36 | 37 | ```sh 38 | pip install --upgrade symbl 39 | ``` 40 | 41 | >or you can also install the package with source: 42 | 43 | ```sh 44 | python setup.py install 45 | ``` 46 | 47 | ## Configuration 48 | 49 | The library needs to be configured with your account's credentials (appId & appSecret) which is 50 | available in your [Symbl Platform][api-keys]. 51 | 52 | You can either provide the credentials by saving a file named symbl.conf in your working directory or home directory in the following format. 53 | 54 | >Home directory will be C:/Users/\ on your windows system, or ~ in your Linux or Mac system. 55 | 56 | ```conf 57 | [credentials] 58 | app_id= 59 | app_secret= 60 | ``` 61 | Example for 'symbl.conf' file 62 | 63 | ```conf 64 | [credentials] 65 | app_id=1234567890 #Update with your app_id, without any quotes 66 | app_secret=abcdefghijklmnop #Update with your app_secret, without any quotes 67 | ``` 68 | ## A speech to text converter under 5 lines of code 69 | 70 | To know more about **Async Audio Api**, click [here][async_audio-docs]. To know more about the Python SDK Audio Package, click [here][extended_readme-audio] 71 | 72 | ```python 73 | import symbl 74 | 75 | # Process audio file 76 | conversation_object = symbl.Audio.process_file( 77 | # credentials={app_id: , app_secret: }, #Optional, Don't add this parameter if you have symbl.conf file in your home directory 78 | file_path="") 79 | 80 | # Printing transcription messages 81 | print(conversation_object.get_messages()) 82 | ``` 83 | 84 | To know more about conversation object and it's functions, click [here][extended_readme-conversation-object] 85 | 86 | ## Extracting insights from Textual conversation 87 | 88 | To know more about **Async Text Api**, click [here][async_text-docs]. To know more about the Python SDK Text Package, click [here][extended_readme-text] 89 | 90 | ``` python 91 | 92 | import symbl 93 | 94 | payload = { 95 | "messages": [ 96 | { 97 | "payload": {"content": "Hi Anthony. I saw your complaints about bad call reception on your mobile phone. Can I know what issues you are currently facing?"}, 98 | "from": {"userId": "surbhi@example.com","name": "Surbhi Rathore"} 99 | }, 100 | { 101 | "payload": {"content": "Hey Surbhi, thanks for reaching out. Whenever I am picking up the call there is a lot of white noise and I literally can’t hear anything."}, 102 | "from": {"userId": "anthony@example.com","name": "Anthony Claudia"} 103 | }, 104 | { 105 | "payload": {"content": "Okay. I can schedule a visit from one of our technicians for tomorrow afternoon at 1:00 PM. He can look at your mobile and handle any issue right away"}, 106 | "from": {"userId": "surbhi@example.com","name": "Surbhi Rathore"} 107 | }, 108 | { 109 | "payload": {"content": "That will be really helpful. I'll follow up with the technician about some other issues too, tomorrow"}, 110 | "from": {"userId": "anthony@example.com","name": "Anthony Claudia"} 111 | }, 112 | { 113 | "payload": {"content": "Sure. We are happy to help. I am scheduling the visit for tomorrow. Thanks for using Abccorp networks. Have a good day."}, 114 | "from": {"userId": "surbhi@example.com","name": "Surbhi Rathore"} 115 | } 116 | ] 117 | } 118 | 119 | conversation_object = symbl.Text.process(payload=payload) 120 | 121 | print(conversation_object.get_messages()) 122 | print(conversation_object.get_topics()) 123 | print(conversation_object.get_action_items()) 124 | print(conversation_object.get_follow_ups()) 125 | 126 | ``` 127 | 128 | ## Analysis of your Zoom Call on your email (Symbl will join your zoom call and send you analysis on provided email) 129 | 130 | To know more about **telephony api**, click [here][telephony_api-docs]. To know more about the Python SDK Telephony Package, click [here][extended_readme-telephony] 131 | 132 | ```python 133 | 134 | import symbl 135 | 136 | phoneNumber = "" # Zoom phone number to be called, check here https://us02web.zoom.us/zoomconference 137 | meetingId = "" # Your zoom meetingId 138 | password = "" # Your zoom meeting passcode 139 | emailId = "" 140 | 141 | connection_object = symbl.Telephony.start_pstn( 142 | # credentials={app_id: , app_secret: }, #Optional, Don't add this parameter if you have symbl.conf file in your home directory or working directory 143 | phone_number=phoneNumber, 144 | dtmf = ",,{}#,,{}#".format(meetingId, password), 145 | actions = [ 146 | { 147 | "invokeOn": "stop", 148 | "name": "sendSummaryEmail", 149 | "parameters": { 150 | "emails": [ 151 | emailId 152 | ], 153 | }, 154 | }, 155 | ] 156 | ) 157 | 158 | print(connection_object) 159 | 160 | ``` 161 | 162 | ## Live audio transcript using your system's microphone 163 | 164 | To know more about **streaming api**, click [here][streaming_api-docs]. To know more about the Python SDK Streaming Package, click [here][extended_readme-streaming] 165 | 166 | ```python 167 | import symbl 168 | 169 | connection_object = symbl.Streaming.start_connection() 170 | 171 | connection_object.subscribe({'message_response': lambda response: print('got this response from callback', response)}) 172 | 173 | connection_object.send_audio_from_mic() 174 | ``` 175 | 176 | ## Extended Readme 177 | 178 | You can see all the functions provided by SDK in the **extended [readme.md](https://github.com/symblai/symbl-python/blob/main/symbl/readme.md) file**. 179 | 180 | You can go through some examples for understanding the use of all functionality [Explore more example](https://github.com/symblai/symbl-python/tree/main/example) 181 | 182 | ## Possible Errors 183 | 184 | 1. PortAudio Errors on Mac Systems:- 185 | 186 | If you're getting PortAudio Error which looks like this 187 | > sounddevice.PortAudioError: Error opening InputStream: Internal PortAudio error [PaErrorCode -9986] 188 | 189 | Please consider updating the PortAudio library in your system. Running the following command can help. 190 | > brew install portaudio 191 | 192 | ## Need support 193 | 194 | If you are looking for some specific use cases do check our [examples][examples] folder. 195 | 196 | If you can't find your answers, do let us know at support@symbl.ai or join our slack channel [here][slack-invite]. 197 | 198 | [api-keys]: https://platform.symbl.ai/#/login 199 | [symbl-docs]: https://docs.symbl.ai/docs/ 200 | [streaming_api-docs]: https://docs.symbl.ai/docs/streamingapi/introduction 201 | [telephony_api-docs]: https://docs.symbl.ai/docs/telephony/introduction 202 | [async_text-docs]: https://docs.symbl.ai/docs/async-api/overview/text/post-text/ 203 | [async_audio-docs]: https://docs.symbl.ai/docs/async-api/overview/audio/post-audio 204 | [extended-readme]: https://github.com/symblai/symbl-python/blob/main/symbl/readme.md 205 | [extended_readme-conversation-object]: https://github.com/symblai/symbl-python/blob/main/symbl/readme.md#conversation-object 206 | [extended_readme-streaming]: https://github.com/symblai/symbl-python/blob/main/symbl/readme.md#streaming-class 207 | [extended_readme-telephony]: https://github.com/symblai/symbl-python/blob/main/symbl/readme.md#telephony-class 208 | [extended_readme-text]: 209 | [extended_readme-audio]: https://github.com/symblai/symbl-python/blob/main/symbl/readme.md#audio-class 210 | [examples]: https://github.com/symblai/symbl-python/tree/main/example 211 | [unicodeerror]: https://stackoverflow.com/questions/37400974/unicode-error-unicodeescape-codec-cant-decode-bytes-in-position-2-3-trunca 212 | [slack-invite]: https://symbldotai.slack.com/join/shared_invite/zt-4sic2s11-D3x496pll8UHSJ89cm78CA#/ 213 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | symbl_rest >= 1.0.14 2 | websocket-client >= 0.59.0 3 | pyaudio ~= 0.2.12 4 | numpy 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | symbl.ai APIs 5 | 6 |

Symbl's APIs for external consumers.

Language Insights API

Symbl's Language Insights API provides an interface for applications to perform the analysis on the raw text and get insights from it. The API automatically detects sentence boundaries and punctuates the sentences, and also returns the updated messages in the response. Conversations are the most unstructured piece of information that we represent information in, and which most of the leads to lot of loss of information by not being able to capture them correctly.
Language Insights API focuses on understanding such texts and generate the useful and important information from them.
Currently the API supports detection of the Action Items in any type of unstructured text. In future the same API will also have support to detect \"Information\" and \"Event\", where Information is any informational piece and Event is a reference to something that has happened in the past.

Telephony Integration

Symbl can currently integrate with two types of telephony endpoints: 1. SIP trunks
2. PSTN endpoints
Results are sent via HTTP WebHooks as and when they are available.

Flow

1. External Application invokes REST API to join a meeting/session, with the mode (SIP/PSTN) and joining details
2. Symbl joins the meeting via SIP or PSTN integration
3. Symbl continuously processes the audio stream received
4. Symbl calls WebHook whenever transcription results are available
# noqa: E501 7 | 8 | Contact: info@symbl.ai 9 | """ 10 | 11 | from setuptools import setup, find_packages # noqa: H301 12 | 13 | # To install the library, run the following 14 | # 15 | # python setup.py install 16 | # 17 | # prerequisite: setuptools 18 | # http://pypi.python.org/pypi/setuptools 19 | 20 | 21 | setup( 22 | name="symbl", 23 | version="2.0.6", 24 | description="symbl.ai SDK", 25 | author_email="info@symbl.ai", 26 | url="", 27 | keywords=["Symbl.ai SDK"], 28 | install_requires=["symbl_rest >= 1.0.14", "websocket-client >= 0.59.0", "pyaudio ~= 0.2.12", "numpy"], 29 | packages=find_packages(), 30 | include_package_data=True, 31 | long_description="""\ 32 | <h1>Symbl's APIs for external consumers.</h1> <h2>Language Insights API</h2> Symbl's Language Insights API provides an interface for applications to perform the analysis on the raw text and get insights from it. The API automatically detects sentence boundaries and punctuates the sentences, and also returns the updated messages in the response. Conversations are the most unstructured piece of information that we represent information in, and which most of the leads to lot of loss of information by not being able to capture them correctly.<br/> Language Insights API focuses on understanding such texts and generate the useful and important information from them. <br/> Currently the API supports detection of the Action Items in any type of unstructured text. In future the same API will also have support to detect \"Information\" and \"Event\", where Information is any informational piece and Event is a reference to something that has happened in the past.<br/> <h2>Telephony Integration</h2> Symbl can currently integrate with two types of telephony endpoints: 1. SIP trunks<br/> 2. PSTN endpoints<br/> Results are sent via HTTP WebHooks as and when they are available.<br/> <h2>Flow</h2> 1. External Application invokes REST API to join a meeting/session, with the mode (SIP/PSTN) and joining details<br/> 2. Symbl joins the meeting via SIP or PSTN integration<br/> 3. Symbl continuously processes the audio stream received<br/> 4. Symbl calls WebHook whenever transcription results are available<br/> # noqa: E501 33 | """ 34 | ) 35 | -------------------------------------------------------------------------------- /symbl/AuthenticationToken.py: -------------------------------------------------------------------------------- 1 | from symbl_rest import AuthenticationApi, Configuration, ApiClient 2 | 3 | import configparser 4 | import threading 5 | import os 6 | 7 | SYMBL_CONFIGURATION_FILENAME = 'symbl.conf' 8 | SYMBL_CREDENTIALS_SECTION = 'credentials' 9 | SYMBL_APP_ID_CONSTANT = 'app_id' 10 | SYMBL_APP_SECRET_CONSTANT = 'app_secret' 11 | 12 | configuration = None 13 | 14 | cached_app_id = "" 15 | cached_app_secret = "" 16 | 17 | 18 | config_parser = configparser.ConfigParser() 19 | 20 | 21 | ''' 22 | It will return an object of ApiClient with a prefilled token. 23 | ''' 24 | 25 | def init(app_id=None, app_secret=None): 26 | if app_id == None and app_secret == None: 27 | app_id, app_secret = fetchAppCredentialsFromFiles() 28 | 29 | generateAndInitializeToken(app_id=app_id, app_secret=app_secret) 30 | 31 | 32 | def generateAndInitializeToken(app_id, app_secret): 33 | 34 | if app_id is None or len(app_id) == 0: 35 | raise ValueError('app_id is required') 36 | 37 | if app_secret is None or len(app_secret) == 0: 38 | raise ValueError('app_secret is required') 39 | 40 | generate_token(app_id, app_secret) 41 | 42 | def fetchAppCredentialsFromFiles(): 43 | # configuration file exists in local directory 44 | if os.path.isfile(SYMBL_CONFIGURATION_FILENAME): 45 | config_parser.read_file(open(SYMBL_CONFIGURATION_FILENAME)) 46 | if config_parser.has_option(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_ID_CONSTANT) and config_parser.has_option(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_SECRET_CONSTANT): 47 | return config_parser.get(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_ID_CONSTANT), config_parser.get(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_SECRET_CONSTANT) 48 | else: 49 | # configuration file exists in home directory of system 50 | return fetchAppCredentialsInPath() 51 | else: 52 | # configuration file exists in home directory of system 53 | return fetchAppCredentialsInPath() 54 | 55 | def fetchAppCredentialsInPath(): 56 | app_id, app_secret = None, None 57 | if os.path.isfile("{}/{}".format(os.path.expanduser('~'), SYMBL_CONFIGURATION_FILENAME)): 58 | config_parser.read_file(open("{}/{}".format(os.path.expanduser('~'), SYMBL_CONFIGURATION_FILENAME))) 59 | if config_parser.has_option(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_ID_CONSTANT) and config_parser.has_option(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_SECRET_CONSTANT): 60 | app_id, app_secret = config_parser.get(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_ID_CONSTANT), config_parser.get(SYMBL_CREDENTIALS_SECTION, SYMBL_APP_SECRET_CONSTANT) 61 | 62 | return app_id, app_secret 63 | 64 | 65 | def generate_token(app_id, app_secret): 66 | configuration_instance = Configuration() 67 | body={ 68 | 'type': 'application', 69 | 'appId': app_id, 70 | 'appSecret': app_secret 71 | } 72 | 73 | authenticationResponse = AuthenticationApi().generate_token(body) 74 | # refreshing token 60 secs before current one expires 75 | time = authenticationResponse.expires_in 76 | 77 | global configuration 78 | 79 | timer = threading.Timer(time, generate_token, args=[app_id, app_secret]) 80 | timer.daemon = True 81 | timer.start() 82 | 83 | configuration_instance.api_key['x-api-key'] = authenticationResponse.access_token 84 | configuration = configuration_instance 85 | 86 | def get_api_client(credentials=None): 87 | 88 | app_id, app_secret = None, None 89 | 90 | if credentials != None and 'app_id' in credentials: 91 | app_id = credentials['app_id'] 92 | 93 | if credentials != None and 'app_secret' in credentials: 94 | app_secret = credentials['app_secret'] 95 | 96 | global configuration 97 | if configuration == None: 98 | init(app_id, app_secret) 99 | return ApiClient(configuration) 100 | 101 | def get_api_header(credentials=None): 102 | app_id, app_secret = None, None 103 | 104 | if credentials != None and 'app_id' in credentials: 105 | app_id = credentials['app_id'] 106 | 107 | if credentials != None and 'app_secret' in credentials: 108 | app_secret = credentials['app_secret'] 109 | 110 | global configuration 111 | if configuration == None: 112 | init(app_id, app_secret) 113 | 114 | return configuration.api_key 115 | 116 | def get_access_token(credentials=None): 117 | app_id, app_secret = None, None 118 | 119 | if credentials != None and 'app_id' in credentials: 120 | app_id = credentials['app_id'] 121 | 122 | if credentials != None and 'app_secret' in credentials: 123 | app_secret = credentials['app_secret'] 124 | 125 | global configuration 126 | if configuration == None: 127 | init(app_id, app_secret) 128 | 129 | return configuration.api_key['x-api-key'] -------------------------------------------------------------------------------- /symbl/Connection.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Logger import Log 2 | from symbl.Conversations import Conversation 3 | from symbl.configs.configs import SYMBL_WEBSOCKET_BASE_PATH, X_API_KEY_HEADER 4 | from symbl.AuthenticationToken import get_api_header 5 | import json 6 | import websocket 7 | 8 | class Connection(): 9 | 10 | def __init__(self, conversationId: str, connectionId: str, resultWebSocketUrl: str, eventUrl: str, credentials=None): 11 | 12 | self.conversation = Conversation(conversationId) 13 | self.credentials = credentials 14 | self.connectionId = connectionId 15 | self.resultWebSocketUrl = resultWebSocketUrl 16 | self.eventUrl = eventUrl 17 | access_token = get_api_header(self.credentials).get(X_API_KEY_HEADER) 18 | self.header = "{}:{}".format(X_API_KEY_HEADER, access_token) 19 | self.connection = None 20 | 21 | def __on_message_handler(self, data, event_callbacks): 22 | try: 23 | if data != None and type(data) == str: 24 | json_data = json.loads(data) 25 | if 'type' in json_data and json_data['type'] in event_callbacks: 26 | event_callbacks[json_data['type']](data) 27 | except Exception as error: 28 | Log.getInstance().error(error) 29 | 30 | def subscribe(self, event_callbacks: dict): 31 | try: 32 | if event_callbacks != None or event_callbacks != {}: 33 | Log.getInstance().info("Eshtablishing connection") 34 | self.connection = websocket.WebSocketApp(url=SYMBL_WEBSOCKET_BASE_PATH + self.connectionId, header=[self.header]) 35 | Log.getInstance().info("Connection Eshtablished") 36 | 37 | self.connection.on_message = lambda this, data : self.__on_message_handler(data=data, event_callbacks=event_callbacks) 38 | self.connection.on_error = lambda self, error : Log.getInstance().error(error) 39 | 40 | self.connection.run_forever() 41 | else: 42 | Log.getInstance().error("Can not subscribe to empty events") 43 | except (KeyboardInterrupt, SystemExit): 44 | self.stop() 45 | Log.getInstance().error("Exiting") 46 | 47 | def stop(self): 48 | self.connection.send(data=str({ 49 | 'type': 'stop_request' 50 | })) 51 | self.connection.close() -------------------------------------------------------------------------------- /symbl/Conversations.py: -------------------------------------------------------------------------------- 1 | from symbl.jobs_api.Job import Job 2 | from symbl.conversations_api.ConversationsApi import ConversationsApi 3 | from symbl.utils.Logger import Log 4 | 5 | 6 | def validate_conversation_id(function): 7 | def wrapper(*args, **kwargs): 8 | self = args[0] 9 | if self.get_conversation_id() != None: 10 | return function(*args, **kwargs) 11 | else: 12 | Log.getInstance().error("Conversation not initialized") 13 | return wrapper 14 | class Conversation(): 15 | 16 | __INTERVAL_TIME_IN_SECONDS = 30 ## in seconds 17 | 18 | def __init__(self, conversation_id: str, job_id: str=None, wait=True, credentials=None): 19 | 20 | self.__conversation_id = conversation_id 21 | self.__wait = wait 22 | self.__credentials = credentials 23 | self.__conversation_api = ConversationsApi() 24 | 25 | if job_id != None: 26 | self.__job = Job(conversation_id=conversation_id, job_id=job_id, wait=wait) 27 | self.__job.monitor_job(self, interval=self.__INTERVAL_TIME_IN_SECONDS, wait=self.__wait, credentials=self.__credentials) 28 | else: 29 | self.__job = None 30 | 31 | def get_conversation_id(self): 32 | return self.__conversation_id 33 | 34 | def get_job_status(self): 35 | if self.__job != None: 36 | return self.__job.get_job_status() 37 | return None 38 | 39 | def get_job_id(self): 40 | if self.__job != None: 41 | return self.__job.get_job_id() 42 | return None 43 | 44 | def on_complete(self, func): 45 | self.__job.on_complete(func) 46 | return self 47 | 48 | def on_error(self, func): 49 | self.__job.on_error(func) 50 | return self 51 | 52 | @validate_conversation_id 53 | def get_action_items(self): 54 | return self.__conversation_api.get_action_items(self.__conversation_id, credentials=self.__credentials) 55 | 56 | @validate_conversation_id 57 | def get_follow_ups(self): 58 | return self.__conversation_api.get_follow_ups(self.__conversation_id, credentials=self.__credentials) 59 | 60 | @validate_conversation_id 61 | def get_members(self): 62 | return self.__conversation_api.get_members(self.__conversation_id, credentials=self.__credentials) 63 | 64 | @validate_conversation_id 65 | def get_messages(self, parameters={}): 66 | return self.__conversation_api.get_messages(self.__conversation_id, credentials=self.__credentials, parameters=parameters) 67 | 68 | @validate_conversation_id 69 | def get_questions(self): 70 | return self.__conversation_api.get_questions(self.__conversation_id, credentials=self.__credentials) 71 | 72 | @validate_conversation_id 73 | def get_topics(self, parameters={}): 74 | return self.__conversation_api.get_topics(self.__conversation_id, credentials=self.__credentials, parameters=parameters) 75 | 76 | @validate_conversation_id 77 | def get_conversation(self): 78 | return self.__conversation_api.get_conversation(self.__conversation_id, credentials=self.__credentials) 79 | 80 | @validate_conversation_id 81 | def get_trackers(self): 82 | return self.__conversation_api.get_trackers(self.__conversation_id, credentials=self.__credentials) 83 | 84 | @validate_conversation_id 85 | def get_entities(self): 86 | return self.__conversation_api.get_entities(self.__conversation_id, credentials=self.__credentials) 87 | 88 | @validate_conversation_id 89 | def get_analytics(self): 90 | return self.__conversation_api.get_analytics(self.__conversation_id, credentials=self.__credentials) 91 | 92 | @validate_conversation_id 93 | def put_members(self, members_id, parameters={}): 94 | return self.__conversation_api.put_members(self.__conversation_id, members_id, parameters, credentials=self.__credentials) 95 | 96 | @validate_conversation_id 97 | def put_speakers_events(self, parameters={}): 98 | return self.__conversation_api.put_speakers_events(self.__conversation_id, parameters, credentials=self.__credentials) 99 | 100 | @validate_conversation_id 101 | def delete_conversation(self): 102 | return self.__conversation_api.delete_conversation(self.__conversation_id, credentials=self.__credentials) 103 | 104 | @validate_conversation_id 105 | def get_formatted_transcript(self, parameters={}): 106 | return self.__conversation_api.get_formatted_transcript(self.__conversation_id,parameters,credentials=self.__credentials) 107 | 108 | @validate_conversation_id 109 | def get_call_score(self): 110 | return self.__conversation_api.get_call_score(self.__conversation_id, credentials=self.__credentials) 111 | 112 | @validate_conversation_id 113 | def get_call_score_status(self): 114 | return self.__conversation_api.get_call_score_status(self.__conversation_id, credentials=self.__credentials) 115 | -------------------------------------------------------------------------------- /symbl/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from symbl.async_api.Audio import Audio 4 | from symbl.async_api.Video import Video 5 | from symbl.async_api.Text import Text 6 | 7 | from symbl.Connection import Connection 8 | from symbl.streaming_api.StreamingConnection import StreamingConnection 9 | 10 | from symbl.conversations_api.ConversationsApi import ConversationsApi 11 | from symbl.telephony_api.TelephonyApi import TelephonyApi 12 | from symbl.streaming_api.StreamingApi import StreamingApi 13 | 14 | Audio = Audio() 15 | Video = Video() 16 | Text = Text() 17 | Conversations = ConversationsApi() 18 | Telephony = TelephonyApi() 19 | Streaming = StreamingApi() 20 | -------------------------------------------------------------------------------- /symbl/async_api/Audio.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Helper import dictionary_to_valid_json, correct_boolean_values, initialize_api_client 2 | from symbl.utils.Logger import Log 3 | from symbl.Conversations import Conversation 4 | from symbl_rest import AsyncApi as async_api_rest 5 | 6 | class Audio(): 7 | 8 | def __init__(self): 9 | ''' 10 | It will initialize the Analysis class 11 | with the object of Initialize Class 12 | ''' 13 | self.__async_api_rest = async_api_rest() 14 | 15 | @initialize_api_client 16 | def process_file(self, file_path:str, credentials=None, content_type:str='', wait:bool=True, parameters={}): 17 | ''' 18 | audio files to be analyzed 19 | returns Conversation object 20 | ''' 21 | if file_path == None: 22 | raise ValueError("Please enter a valid file_path") 23 | 24 | params = dictionary_to_valid_json(parameters) 25 | 26 | file = open(file_path, 'rb') 27 | audio_file = file.read() 28 | response = self.__async_api_rest.add_audio(body=audio_file, content_type=content_type, **correct_boolean_values(params)) 29 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 30 | 31 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 32 | 33 | @initialize_api_client 34 | def process_url(self, payload:dict, credentials=None, wait:bool=True, parameters={}): 35 | ''' 36 | url of audio file to be analyzed 37 | returns Conversation object 38 | ''' 39 | 40 | if 'url' not in payload or payload['url'] == None: 41 | raise ValueError("Please enter a valid file_path") 42 | 43 | params = dictionary_to_valid_json(parameters) 44 | 45 | response = self.__async_api_rest.add_audio_url(body=payload, **correct_boolean_values(params)) 46 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 47 | 48 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 49 | 50 | 51 | @initialize_api_client 52 | def append_file(self, file_path:str, conversation_id:str, credentials=None, content_type:str='', wait:bool=True, parameters={}): 53 | ''' 54 | audio files to be appended 55 | returns Conversation object 56 | ''' 57 | if file_path == None: 58 | raise ValueError("Please enter a valid file_path") 59 | 60 | if conversation_id == None or len(conversation_id) == 0: 61 | raise ValueError("Please enter a valid conversation_id") 62 | 63 | params = dictionary_to_valid_json(parameters) 64 | 65 | file = open(file_path, 'rb') 66 | audio_file = file.read() 67 | response = self.__async_api_rest.append_audio(body=audio_file, content_type=content_type, conversation_id=conversation_id, **correct_boolean_values(params)) 68 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 69 | 70 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 71 | 72 | @initialize_api_client 73 | def append_url(self, payload:dict, conversation_id:str, credentials=None, wait:bool=True, parameters={}): 74 | ''' 75 | url of audio file to be appended 76 | returns Conversation object 77 | ''' 78 | if 'url' not in payload or payload['url'] == None: 79 | raise ValueError("Please enter a valid url") 80 | 81 | if conversation_id == None or len(conversation_id) == 0: 82 | raise ValueError("Please enter a valid conversationId") 83 | 84 | params = dictionary_to_valid_json(parameters) 85 | 86 | response = self.__async_api_rest.append_audio_url(body=payload, conversation_id=conversation_id, **correct_boolean_values(params)) 87 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 88 | 89 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 90 | -------------------------------------------------------------------------------- /symbl/async_api/Text.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Helper import correct_boolean_values, dictionary_to_valid_json, initialize_api_client 2 | from symbl.utils.Logger import Log 3 | from symbl.Conversations import Conversation 4 | from symbl_rest import AsyncApi as async_api_rest 5 | 6 | class Text(): 7 | 8 | def __init__(self): 9 | ''' 10 | It will initialize the Analysis class 11 | with the object of Initialize Class 12 | ''' 13 | self.__async_api_rest = async_api_rest() 14 | 15 | @initialize_api_client 16 | def process(self, payload : dict, credentials=None, wait: bool = True, parameters={}): 17 | ''' 18 | Text payload to be analyzed 19 | returns Conversation object 20 | ''' 21 | 22 | if payload == None: 23 | raise ValueError("Please enter a valid payload.") 24 | 25 | params = dictionary_to_valid_json(parameters) 26 | 27 | response = self.__async_api_rest.add_text(body=payload, **correct_boolean_values(params)) 28 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 29 | 30 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 31 | 32 | @initialize_api_client 33 | def append(self, payload:dict, conversation_id:str, credentials=None, wait: bool = True, parameters={}): 34 | ''' 35 | Text payload to be appended 36 | returns Conversation object 37 | ''' 38 | if payload == None: 39 | raise ValueError("Text Payload can not be None") 40 | 41 | if conversation_id == None or len(conversation_id) == 0: 42 | raise ValueError("Please enter a valid conversationId") 43 | 44 | params = dictionary_to_valid_json(parameters) 45 | 46 | response = self.__async_api_rest.append_text(conversation_id, body=payload, **correct_boolean_values(params)) 47 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 48 | 49 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) -------------------------------------------------------------------------------- /symbl/async_api/Video.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Helper import dictionary_to_valid_json, correct_boolean_values, initialize_api_client 2 | from symbl.Conversations import Conversation 3 | from symbl_rest import AsyncApi as async_api_rest 4 | from symbl.utils.Logger import Log 5 | 6 | class Video(): 7 | 8 | def __init__(self): 9 | ''' 10 | It will initialize the Analysis class 11 | with the object of Initialize Class 12 | ''' 13 | self.__async_api_rest = async_api_rest() 14 | 15 | @initialize_api_client 16 | def process_file(self, file_path:str, credentials=None, content_type:str='video/mp4', wait:bool=True, parameters={}): 17 | ''' 18 | video files to be analyzed 19 | returns Conversation object 20 | ''' 21 | if file_path == None: 22 | raise ValueError("Please enter a valid file_path") 23 | 24 | params = dictionary_to_valid_json(parameters) 25 | 26 | file = open(file_path, 'rb') 27 | video_file = file.read() 28 | response = self.__async_api_rest.add_video(body=video_file, content_type=content_type, **correct_boolean_values(params)) 29 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 30 | 31 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 32 | 33 | 34 | @initialize_api_client 35 | def process_url(self, payload:dict, credentials=None, wait:bool=True, parameters={}): 36 | ''' 37 | url of audio file to be analyzed 38 | returns Conversation object 39 | ''' 40 | 41 | if 'url' not in payload or payload['url'] == None: 42 | raise ValueError("Please enter a valid url.") 43 | 44 | params = dictionary_to_valid_json(parameters) 45 | 46 | response = self.__async_api_rest.add_video_url(body=payload, **correct_boolean_values(params)) 47 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 48 | 49 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 50 | 51 | 52 | @initialize_api_client 53 | def append_file(self, file_path:str, conversation_id:str, credentials=None, content_type:str='video/mp4', wait:bool=True, parameters={}): 54 | ''' 55 | video files to be appended 56 | returns Conversation object 57 | ''' 58 | if file_path == None: 59 | raise ValueError("Please enter a valid file_path") 60 | 61 | if conversation_id == None or len(conversation_id) == 0: 62 | raise ValueError("Please enter a valid conversation_id") 63 | 64 | params = dictionary_to_valid_json(parameters) 65 | 66 | file = open(file_path, 'rb') 67 | video_file = file.read() 68 | response = self.__async_api_rest.append_video(body=video_file, content_type=content_type, conversation_id=conversation_id, **correct_boolean_values(params)) 69 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 70 | 71 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) 72 | 73 | 74 | @initialize_api_client 75 | def append_url(self, payload:dict, conversation_id:str, credentials=None, wait:bool=True, parameters={}): 76 | ''' 77 | url of video file to be appended 78 | returns Conversation object 79 | ''' 80 | if 'url' not in payload or payload['url'] == None: 81 | raise ValueError("Please enter a valid url") 82 | 83 | if conversation_id == None or len(conversation_id) == 0: 84 | raise ValueError("Please enter a valid conversation_id") 85 | 86 | params = dictionary_to_valid_json(parameters) 87 | 88 | response = self.__async_api_rest.append_video_url(body=payload, conversation_id=conversation_id, **correct_boolean_values(params)) 89 | Log.getInstance().info("Job with jobId {} for conversationId {} started".format(response.job_id, response.conversation_id)) 90 | 91 | return Conversation(response.conversation_id, response.job_id, wait=wait, credentials=credentials) -------------------------------------------------------------------------------- /symbl/async_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/symbl/async_api/__init__.py -------------------------------------------------------------------------------- /symbl/callScore.py: -------------------------------------------------------------------------------- 1 | from conversations_api.ConversationsApi import ConversationsApi 2 | 3 | conversations = ConversationsApi(); 4 | 5 | conversation_id=6750867925630976 # Update with the conversation Id of your conversation 6 | 7 | print(conversations.get_call_score_insights_url(conversation_id)) 8 | -------------------------------------------------------------------------------- /symbl/configs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/symbl/configs/__init__.py -------------------------------------------------------------------------------- /symbl/configs/configs.py: -------------------------------------------------------------------------------- 1 | X_API_KEY_HEADER = "x-api-key" 2 | SYMBL_WEBSOCKET_BASE_PATH = "wss://api.symbl.ai/session/subscribe/" 3 | SYMBL_STREAMING_API_FORMAT = "wss://api.symbl.ai/v1/realtime/insights/{}?source=python_sdk" -------------------------------------------------------------------------------- /symbl/conversations_api/ConversationsApi.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Helper import correct_boolean_values, dictionary_to_valid_json, initialize_api_client, insert_valid_boolean_values 2 | from symbl_rest import ConversationsApi as conversations_api_rest 3 | from symbl.utils import Helper 4 | 5 | class ConversationsApi(): 6 | 7 | def __init__(self): 8 | ''' 9 | It will initialize the ConversationsApi class 10 | ''' 11 | 12 | self.conversations_api_rest = conversations_api_rest() 13 | 14 | @initialize_api_client 15 | def get_action_items(self, conversation_id, credentials=None): 16 | try: 17 | return self.conversations_api_rest.get_action_items_by_conversation_id(conversation_id) 18 | except: 19 | return {'message': 'The conversationId is either invalid or does not exist.'} 20 | 21 | 22 | @initialize_api_client 23 | def get_call_score(self, conversation_id, credentials=None): 24 | try: 25 | return self.conversations_api_rest.get_call_score_by_conversation_id(conversation_id) 26 | except: 27 | return {'message': 'The conversationId is either invalid or does not exist.'} 28 | 29 | @initialize_api_client 30 | def get_call_score_insights_url(self, conversation_id, credentials=None): 31 | try: 32 | params = {'include_call_score': True} 33 | return self.conversations_api_rest.get_experience_url_by_conversation_id(conversation_id, **correct_boolean_values(params)) 34 | except Exception as e: 35 | return {'message': 'The conversationId is either invalid or does not exist.'} 36 | 37 | @initialize_api_client 38 | def get_call_score_status(self, conversation_id, credentials=None): 39 | try: 40 | res = self.conversations_api_rest.get_call_score_status_by_conversation_id(conversation_id) 41 | return res 42 | except Exception as e: 43 | return {'message': 'The conversationId is either invalid or does not exist.'} 44 | 45 | @initialize_api_client 46 | def get_follow_ups(self, conversation_id, credentials=None ): 47 | try: 48 | return self.conversations_api_rest.get_follow_ups_by_conversation_id(conversation_id) 49 | except: 50 | return {'message': 'The conversationId is either invalid or does not exist.'} 51 | 52 | @initialize_api_client 53 | def get_members(self, conversation_id, credentials=None): 54 | try: 55 | return self.conversations_api_rest.get_members_by_conversation_id(conversation_id) 56 | except: 57 | return {'message': 'The conversationId is either invalid or does not exist.'} 58 | 59 | 60 | @initialize_api_client 61 | def get_messages(self, conversation_id, credentials=None, parameters={}): 62 | params = dictionary_to_valid_json(parameters) 63 | try: 64 | return self.conversations_api_rest.get_messages_by_conversation_id(conversation_id, **correct_boolean_values(params)) 65 | except: 66 | return {'message': 'The conversationId is either invalid or does not exist.'} 67 | 68 | @initialize_api_client 69 | def get_questions(self, conversation_id, credentials=None): 70 | try: 71 | return self.conversations_api_rest.get_questions_by_conversation_id(conversation_id) 72 | except: 73 | return {'message': 'The conversationId is either invalid or does not exist.'} 74 | 75 | 76 | @initialize_api_client 77 | def get_topics(self, conversation_id, credentials=None, parameters={}): 78 | try: 79 | params = dictionary_to_valid_json(parameters) 80 | return self.conversations_api_rest.get_topics_by_conversation_id(conversation_id, **correct_boolean_values(params)) 81 | except: 82 | return {'message': 'The conversationId is either invalid or does not exist.'} 83 | 84 | 85 | @initialize_api_client 86 | def get_conversation(self, conversation_id, credentials=None): 87 | try: 88 | return self.conversations_api_rest.get_conversation_by_conversation_id(conversation_id) 89 | except: 90 | return {'message': 'The conversationId is either invalid or does not exist.'} 91 | 92 | 93 | @initialize_api_client 94 | def get_trackers(self, conversation_id, credentials=None): 95 | try: 96 | return self.conversations_api_rest.get_trackers_by_conversation_id(conversation_id) 97 | except: 98 | return {'message': 'The conversationId is either invalid or does not exist.'} 99 | 100 | 101 | @initialize_api_client 102 | def get_entities(self, conversation_id, credentials=None): 103 | try: 104 | api_response = self.conversations_api_rest.get_entities_by_conversation_id(conversation_id) 105 | return Helper.parse_entity_response(api_response) if len(api_response.entities)!=0 else api_response 106 | except: 107 | return {'message': 'The conversationId is either invalid or does not exist.'} 108 | 109 | 110 | @initialize_api_client 111 | def get_analytics(self, conversation_id, credentials=None): 112 | try: 113 | return self.conversations_api_rest.get_analytics_by_conversation_id(conversation_id) 114 | except: 115 | return {'message': 'The conversationId is either invalid or does not exist.'} 116 | 117 | 118 | @initialize_api_client 119 | def put_members(self, conversation_id, members_id, parameters={}, credentials=None): 120 | try: 121 | return self.conversations_api_rest.put_members_information_by_members_id(conversation_id, members_id, body=parameters) 122 | except: 123 | return {'message': 'The conversationId is either invalid or does not exist.'} 124 | 125 | 126 | @initialize_api_client 127 | def put_speakers_events(self, conversation_id, parameters={}, credentials=None): 128 | try: 129 | return self.conversations_api_rest.put_speakers_event_by_conversation_id(conversation_id, body=parameters) 130 | except: 131 | return {'message': 'The conversationId is either invalid or does not exist.'} 132 | 133 | 134 | @initialize_api_client 135 | def delete_conversation(self, conversation_id, credentials=None): 136 | try: 137 | return self.conversations_api_rest.delete_conversation_by_conversation_id(conversation_id) 138 | except: 139 | pass 140 | 141 | @initialize_api_client 142 | def get_formatted_transcript(self, conversation_id, parameters = {},credentials=None): 143 | try: 144 | content_type = "application/json" 145 | params = insert_valid_boolean_values(parameters) 146 | return self.conversations_api_rest.get_formatted_transcript_by_conversation_id(params,content_type,conversation_id) 147 | except: 148 | return {'message': 'The conversationId is either invalid or does not exist.'} 149 | -------------------------------------------------------------------------------- /symbl/conversations_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/symbl/conversations_api/__init__.py -------------------------------------------------------------------------------- /symbl/jobs_api/Job.py: -------------------------------------------------------------------------------- 1 | from symbl.utils import wrap_keyboard_interrupt, Thread, Log 2 | from symbl.utils.Helper import initialize_api_client 3 | from symbl.jobs_api.JobStatus import JobStatus 4 | from symbl_rest import JobsApi 5 | 6 | import time 7 | 8 | class Job(): 9 | 10 | __INTERVAL_TIME_IN_SECONDS = 5 ## in seconds 11 | 12 | def __init__(self, job_id: str, conversation_id:str, wait=True): 13 | 14 | self.__job_id = job_id 15 | self.__conversation_id = conversation_id 16 | self.__success_func = None 17 | self.__jobs_api = JobsApi() 18 | self.__error_func = None 19 | self.__job_status = JobStatus.IN_PROGRESS 20 | self.__wait = wait 21 | 22 | def getConversationId(self): 23 | return self.__conversation_id 24 | 25 | def get_job_status(self): 26 | return self.__job_status.value 27 | 28 | def get_job_id(self): 29 | return self.__job_id 30 | 31 | def on_complete(self, func): 32 | self.__success_func = func 33 | return self 34 | 35 | def on_error(self, func): 36 | self.__error_func = func 37 | return self 38 | 39 | @initialize_api_client 40 | def __fetch_current_job_status(self, credentials=None): 41 | if self.__jobs_api is not None: 42 | response = self.__jobs_api.get_job_status(self.__job_id) 43 | self.__job_status = JobStatus(response.status) 44 | else: 45 | raise ValueError("Job object not initialized correctly. Please contact administrator.") 46 | 47 | def synchronous_monitor_job(self, conversation, interval=None, wait=True, credentials=None): 48 | if interval is None: 49 | interval = self.__INTERVAL_TIME_IN_SECONDS 50 | 51 | while self.__job_status != JobStatus.COMPLETED and self.__job_status != JobStatus.FAILED: 52 | time.sleep(interval) 53 | self.__fetch_current_job_status(credentials=credentials) 54 | Log.getInstance().info("Fetching latest status of job {0}, current status is {1}".format(self.__job_id, self.__job_status.value)) 55 | 56 | if self.__job_status == JobStatus.COMPLETED and self.__success_func != None: 57 | self.__success_func(conversation) 58 | 59 | elif self.__error_func != None: 60 | self.__error_func(conversation) 61 | 62 | @wrap_keyboard_interrupt 63 | def monitor_job(self, conversation, interval=None, wait=True, credentials=None): 64 | if wait: 65 | self.synchronous_monitor_job(conversation, interval, wait, credentials) 66 | else: 67 | Thread.getInstance().start_on_thread(self.synchronous_monitor_job, conversation, interval, wait, credentials) 68 | -------------------------------------------------------------------------------- /symbl/jobs_api/JobStatus.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class JobStatus(Enum): 4 | SCHEDULED = "scheduled" 5 | IN_PROGRESS = "in_progress" 6 | FAILED = "failed" 7 | COMPLETED = "completed" 8 | 9 | def __eq__(self, other): 10 | if not isinstance(other, JobStatus): 11 | # don't attempt to compare against unrelated types 12 | raise ValueError("Can not compare JobStatus type object with {} type object".format(type(other))) 13 | 14 | return self.value == other.value -------------------------------------------------------------------------------- /symbl/jobs_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/symbl/jobs_api/__init__.py -------------------------------------------------------------------------------- /symbl/streaming_api/StreamingApi.py: -------------------------------------------------------------------------------- 1 | from symbl import StreamingConnection 2 | from symbl.configs.configs import SYMBL_STREAMING_API_FORMAT 3 | from symbl.AuthenticationToken import get_access_token 4 | from symbl.utils import Helper 5 | import websocket 6 | import base64 7 | import random 8 | import string 9 | 10 | class StreamingApi(): 11 | def __init__(self): 12 | ''' 13 | It will initialize the ConversationsApi class 14 | ''' 15 | pass 16 | 17 | 18 | def start_connection(self, credentials=None, speaker=None, insight_types=None, config={},trackers=None): 19 | randomId = bytes(''.join(random.choices(string.ascii_uppercase +string.digits, k=12)), 'utf-8') 20 | id = base64.b64encode(randomId).decode("utf-8") 21 | token = get_access_token(credentials=credentials) 22 | url = SYMBL_STREAMING_API_FORMAT.format(id) 23 | 24 | if type(config) != dict: 25 | raise TypeError("config should be of type dict") 26 | 27 | config_object = { 28 | "confidenceThreshold": 0.5, 29 | "languageCode": 'en-US', 30 | "speechRecognition": { 31 | "encoding": 'LINEAR16', 32 | "sampleRateHertz": 44100, 33 | } 34 | } 35 | 36 | merged_config = Helper.merge_two_dicts(config_object, config) 37 | 38 | # Merge subdictionary 39 | speechKey = 'speechRecognition' 40 | if speechKey in config: 41 | merged_config[speechKey] = Helper.merge_two_dicts(config_object[speechKey], config[speechKey]) 42 | 43 | start_request = { 44 | "type": "start_request", 45 | "insightTypes": [] if insight_types == None else [] if type(insight_types) != list else insight_types, 46 | "speaker": speaker, 47 | "trackers":trackers, 48 | "config": merged_config 49 | } 50 | 51 | return StreamingConnection(url= url, connectionId=id, start_request=start_request, token=token) 52 | 53 | def stop_listening(self, url: str): 54 | connection = websocket.WebSocketApp(url=url) 55 | stop_payload = {'type': 'stop_request'} 56 | connection.send(str(stop_payload)) -------------------------------------------------------------------------------- /symbl/streaming_api/StreamingConnection.py: -------------------------------------------------------------------------------- 1 | from symbl.Conversations import Conversation 2 | from symbl.utils.Logger import Log 3 | from symbl.utils.Decorators import wrap_keyboard_interrupt 4 | from symbl.utils.Threads import Thread 5 | from time import sleep 6 | import json 7 | import websocket 8 | 9 | 10 | class StreamingConnection(): 11 | 12 | def __init__(self, url: str, connectionId: str, start_request: dict, token: str): 13 | self.conversation = Conversation(None) 14 | self.connectionId = connectionId 15 | self.url = url 16 | self.event_callbacks = {} 17 | self.start_request = start_request 18 | self.connection = None 19 | self.token = token 20 | self.__connect() 21 | 22 | 23 | def __connect(self): 24 | if self.connection == None: 25 | 26 | self.connection = websocket.WebSocketApp(url=self.url, on_message=lambda this, data: self.__listen_to_events(data), on_error=lambda error: Log.getInstance().error(error), header={'x-api-key': self.token}) 27 | 28 | Thread.getInstance().start_on_thread(target=self.connection.run_forever) 29 | conn_timeout = 5 30 | while not self.connection.sock.connected and conn_timeout: 31 | sleep(1) 32 | conn_timeout -= 1 33 | 34 | self.connection.send(json.dumps(self.start_request)) 35 | 36 | def __set_conversation(self, conversationId: str): 37 | self.conversation = Conversation(conversationId) 38 | 39 | def __listen_to_events(self, data): 40 | try: 41 | decoded_data = data if type(data) == str else data.decode('utf-8') 42 | json_data = json.loads(decoded_data) 43 | if 'type' in json_data and json_data['type'] == 'message' and 'type' in json_data['message'] and json_data['message']['type'] == "conversation_created": 44 | self.__set_conversation(str(json_data['message']['data']['conversationId'])) 45 | Log.getInstance().info("Conversation id is {}".format(str(json_data['message']['data']['conversationId']))) 46 | elif 'type' in json_data and json_data['type'] == 'message' and 'type' in json_data['message'] and json_data['message']['type'] == "started_listening": 47 | Log.getInstance().info("Started Listening...") 48 | elif 'type' in json_data and json_data['type'] in self.event_callbacks: 49 | self.event_callbacks[json_data['type']](json_data) 50 | except Exception as error: 51 | Log.getInstance().error(error) 52 | 53 | def subscribe(self, event_callbacks: dict): 54 | self.event_callbacks = event_callbacks 55 | 56 | def stop(self): 57 | if self.connection != None: 58 | stop_payload = {'type': 'stop_request'} 59 | self.connection.send(str(stop_payload)) 60 | 61 | @wrap_keyboard_interrupt 62 | def send_audio(self, data): 63 | if self.connection != None: 64 | self.connection.send(data, opcode=websocket.ABNF.OPCODE_BINARY) 65 | 66 | @wrap_keyboard_interrupt 67 | def send_audio_from_mic(self, device=None): 68 | import pyaudio 69 | audio = pyaudio.PyAudio() 70 | chunk = 4096 71 | samplerate = self.start_request['config']['speechRecognition']['sampleRateHertz'] 72 | stream = audio.open(format=pyaudio.paInt16, channels=1, rate=samplerate, input=True, frames_per_buffer=chunk) 73 | while True: 74 | data = stream.read(chunk) 75 | self.send_audio(data) 76 | -------------------------------------------------------------------------------- /symbl/streaming_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/symbl/streaming_api/__init__.py -------------------------------------------------------------------------------- /symbl/telephony_api/TelephonyApi.py: -------------------------------------------------------------------------------- 1 | from symbl.Connection import Connection 2 | from symbl.telephony_api.TelephonyValidators import validateActions, validateEndpoint 3 | from symbl_rest import ConnectionToEndpointApi as telephony_api_rest 4 | from symbl.utils.Helper import initialize_api_client 5 | class TelephonyApi(): 6 | def __init__(self): 7 | ''' 8 | It will initialize the Telephony class 9 | ''' 10 | 11 | self.telephony_api_rest = telephony_api_rest() 12 | 13 | def validateAndConnectToEndpoint(self, body, credentials=None): 14 | if body == None: 15 | raise ValueError('endpoint configuration is required.') 16 | 17 | if "endpoint" not in body : 18 | raise ValueError('Please enter the endpoint you want to connect.') 19 | 20 | validateEndpoint(body["endpoint"]) 21 | 22 | validateActions(body["actions"]) 23 | 24 | data = self.telephony_api_rest.connect_to_endpoint(body) 25 | connectionObject = Connection(connectionId=data.connection_id, conversationId=data.conversation_id, resultWebSocketUrl=data.result_web_socket_url, eventUrl=data.event_url, credentials=credentials) 26 | 27 | return connectionObject 28 | 29 | @initialize_api_client 30 | def start_pstn(self, phone_number, dtmf=None, credentials=None, actions={}, data={}, languages:list=[], timezone:str=None): 31 | body = dict() 32 | body = { 33 | "operation": "start", 34 | "endpoint": { 35 | "type": "pstn", 36 | "phoneNumber": phone_number, 37 | "dtmf": dtmf 38 | }, 39 | "actions": actions, 40 | "data": data, 41 | "pushSpeakerEvents": True 42 | } 43 | 44 | if type(languages) == list and len(languages) > 0: 45 | body['languages'] = languages 46 | elif type(languages) != list: 47 | raise TypeError("languages should be a list of string") 48 | 49 | if timezone != None: 50 | if type(timezone) == str: 51 | body["timezone"] = timezone 52 | elif type(timezone) != str: 53 | raise TypeError('timezone should be of type string') 54 | 55 | return self.validateAndConnectToEndpoint(body, credentials) 56 | 57 | @initialize_api_client 58 | def start_sip(self, uri, audio_config={}, credentials=None, actions={}, data={}, languages:list=[], timezone:str=None): 59 | body = dict() 60 | 61 | if audio_config == {}: 62 | 63 | audio_config = { 64 | "sampleRate": 16000, 65 | "sampleSize": "16" 66 | } 67 | 68 | body = { 69 | "operation": "start", 70 | "endpoint": { 71 | "type": "sip", 72 | "uri": uri, 73 | "audioConfig": audio_config 74 | }, 75 | "actions": actions, 76 | "data": data, 77 | # "pushSpeakerEvents": True 78 | } 79 | 80 | if type(languages) == list and len(languages) > 0: 81 | body['languages'] = languages 82 | elif type(languages) != list: 83 | raise TypeError("languages should be a list of string") 84 | 85 | if timezone != None: 86 | if type(timezone) == str: 87 | body["timezone"] = timezone 88 | elif type(timezone) != str: 89 | raise TypeError('timezone should be of type string') 90 | 91 | return self.validateAndConnectToEndpoint(body) 92 | 93 | @initialize_api_client 94 | def stop(self, connection_id): 95 | body = dict() 96 | body["operation"] = "stop" 97 | body["connectionId"] = connection_id 98 | 99 | 100 | if connection_id == None: 101 | raise ValueError('ConnectionId is invalid, Please enter a valid connectionId to stop') 102 | 103 | return self.telephony_api_rest.connect_to_endpoint(body) 104 | -------------------------------------------------------------------------------- /symbl/telephony_api/TelephonyValidators.py: -------------------------------------------------------------------------------- 1 | def isEmpty(value): 2 | return value == None or len(value) == 0 3 | 4 | def validateActions(actions): 5 | invalidActions = [] 6 | for action in actions: 7 | if isEmpty(action["invokeOn"]) and isEmpty(action["name"]): 8 | invalidActions.append(action) 9 | 10 | if len(invalidActions) > 0 : 11 | raise ValueError({'message': "Invalid actions detected. Count: ${invalidActions.length}", "actions": invalidActions}) 12 | 13 | def validateEndpoint(endpoint): 14 | if isEmpty(endpoint["type"]): 15 | raise ValueError("endpoint type is required.") 16 | 17 | if endpoint["type"].lower() == "pstn": 18 | if isEmpty(endpoint["phoneNumber"]): 19 | raise ValueError("phoneNumber is required when type = 'pstn'.") 20 | 21 | elif endpoint["type"].lower() == "sip": 22 | if isEmpty(endpoint["uri"]): 23 | raise ValueError("uri is required when type = 'sip'.") 24 | 25 | else: 26 | raise ValueError("endpoint.type = '${endpoint.type}' is not valid. Supported types are ['pstn' , 'sip']") -------------------------------------------------------------------------------- /symbl/telephony_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/symbl/telephony_api/__init__.py -------------------------------------------------------------------------------- /symbl/utils/Decorators.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Logger import Log 2 | from symbl.utils.Threads import Thread 3 | 4 | def wrap_keyboard_interrupt(function): 5 | def wrapper(*args, **kw): 6 | try: 7 | function(*args, **kw) 8 | except KeyboardInterrupt: 9 | Log.getInstance().info("Closing all connections") 10 | self = args[0] 11 | if hasattr(self, "connection"): 12 | self.connection.sock.close() 13 | Thread.getInstance().stop_all_threads() 14 | 15 | return wrapper -------------------------------------------------------------------------------- /symbl/utils/Helper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from symbl import AuthenticationToken 3 | import datetime 4 | 5 | DATE_FORMAT = '%Y-%m-%d %H:%M:%S' 6 | 7 | def correct_boolean_values(dictionary: dict): 8 | for key in dictionary: 9 | if dictionary[key] == True and type(dictionary[key]) == bool: 10 | dictionary[key] = "true" 11 | elif dictionary[key] == False and type(dictionary[key]) == bool: 12 | dictionary[key] = "false" 13 | return dictionary 14 | 15 | def insert_valid_boolean_values(dictionary: dict): 16 | for key in dictionary: 17 | if type(dictionary[key]) == dict: 18 | for dict_key in dictionary[key]: 19 | if dictionary[key][dict_key] == "true": 20 | dictionary[key][dict_key] = True 21 | elif dictionary[key][dict_key] == "false": 22 | dictionary[key][dict_key] = False 23 | elif dictionary[key] == "true": 24 | dictionary[key] = True 25 | elif dictionary[key] == "false": 26 | dictionary[key] = False 27 | return dictionary 28 | 29 | def dictionary_to_valid_json(dictionary: dict): 30 | new_dictionary = dict() 31 | for key in dictionary.keys(): 32 | new_key = ''.join(['_'+i.lower() if i.isupper() else i for i in key]).lstrip('_') 33 | if type(dictionary[key]) == list or type(dictionary[key]) == dict: 34 | new_dictionary[new_key] = json.dumps(dictionary[key]) 35 | else: 36 | new_dictionary[new_key] = dictionary[key] 37 | 38 | return new_dictionary 39 | 40 | def initialize_api_client(function): 41 | def wrapper(*args, **kw): 42 | credentials = None 43 | 44 | if 'credentials' in kw: 45 | credentials = kw['credentials'] 46 | 47 | AuthenticationToken.get_api_client(credentials) 48 | 49 | return function(*args, **kw) 50 | 51 | return wrapper 52 | 53 | #verify date format 54 | def verify_date(date): 55 | try: 56 | datetime.datetime.strptime(date, '%Y-%m-%d') 57 | return True 58 | except ValueError: 59 | return False 60 | 61 | #Parse date format 62 | def deserialize_date(strISODateString): 63 | """Deserializes string to date. 64 | :param strISODateString: str. 65 | :return: date. 66 | """ 67 | try: 68 | from datetime import datetime 69 | return datetime.strptime(strISODateString, DATE_FORMAT) 70 | except ImportError: 71 | return strISODateString 72 | except ValueError: 73 | raise Exception( 74 | status=0, 75 | reason="Failed to parse `{0}` as date object".format(strISODateString) 76 | ) 77 | 78 | #This function will remove the None values from the API response, and will also parse the date format 79 | def parse_entity_response(api_response): 80 | api_response=api_response.entities 81 | count = len(api_response) 82 | entity_response=[] 83 | 84 | keys = ['custom_type','end','message_refs','start','type','text','value'] 85 | 86 | for obj in range(0,count): 87 | entity_res = dict() 88 | for key in keys: 89 | val = getattr(api_response[obj],key) 90 | if val is not None: 91 | if key=='value': 92 | if verify_date(val): 93 | val = "".join([val," 00:00:00"]) 94 | val = deserialize_date(val) 95 | entity_res[key]=val 96 | entity_response.append(entity_res) 97 | 98 | return dict(entities=entity_response) 99 | 100 | # Using copy because we support Python 3.4 101 | def merge_two_dicts(x, y): 102 | z = x.copy() 103 | z.update(y) 104 | return z 105 | -------------------------------------------------------------------------------- /symbl/utils/Logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class Log(): 4 | 5 | __instance = None 6 | __logger = None 7 | 8 | @staticmethod 9 | def getInstance(): 10 | if Log.__instance == None: 11 | return Log() 12 | 13 | return Log.__instance 14 | 15 | def __init__(self): 16 | 17 | logging.basicConfig(format='%(asctime)s - %(name)s - %(process)s - %(levelname)s - %(message)s', level=logging.NOTSET) 18 | 19 | if Log.__instance != None: 20 | raise Exception("Can not instantiate more than once!") 21 | 22 | # Create a custom logger 23 | logger = logging.getLogger('symbl') 24 | 25 | # Create handlers 26 | c_handler = logging.StreamHandler() 27 | c_handler.setLevel(logging.NOTSET) 28 | 29 | self.__logger = logger 30 | 31 | def set_level(self, level): 32 | self.__logger.setLevel(level) 33 | 34 | def info(self, message: str, data=None): 35 | if data != None: 36 | self.__logger.info("{}, {}".format(message, data)) 37 | else: 38 | self.__logger.info(message) 39 | 40 | def debug(self, data, *args): 41 | if args != None and len(args) > 0: 42 | self.__logger.debug(data, args[0]) 43 | else: 44 | self.__logger.debug(data) 45 | 46 | def warning(self, data, *args): 47 | if args != None and len(args) > 0: 48 | self.__logger.warning(data, args[0]) 49 | else: 50 | self.__logger.warning(data) 51 | 52 | def error(self, data, *args): 53 | if args != None and len(args) > 0: 54 | self.__logger.error(data, args[0]) 55 | else: 56 | self.__logger.error(data) 57 | -------------------------------------------------------------------------------- /symbl/utils/Threads.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class Thread(): 4 | 5 | __instance = None 6 | threads = [] 7 | 8 | @staticmethod 9 | def getInstance(): 10 | if Thread.__instance == None: 11 | return Thread() 12 | 13 | return Thread.__instance 14 | 15 | def __init__(self): 16 | if Thread.__instance != None: 17 | raise Exception("Can not instantiate more than once!") 18 | else: 19 | Thread.__instance = self 20 | 21 | 22 | def start_on_thread(self, target, *args): 23 | thread = threading.Thread(target=target, args=(*args,)) 24 | self.threads.append(thread) 25 | thread.start() 26 | 27 | def stop_all_threads(self): 28 | for thread in self.threads: 29 | thread.join() -------------------------------------------------------------------------------- /symbl/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from symbl.utils.Decorators import wrap_keyboard_interrupt 2 | from symbl.utils.Threads import Thread 3 | from symbl.utils.Logger import Log -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symblai/symbl-python-sdk/24480b4cd5343bced555a9fdfc74400260df3993/tests/__init__.py -------------------------------------------------------------------------------- /tests/audio_test.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from symbl.async_api.Audio import Audio, correct_boolean_values 4 | import unittest 5 | from unittest.mock import Mock, patch 6 | 7 | from types import SimpleNamespace 8 | 9 | 10 | class AudioTest(unittest.TestCase): 11 | 12 | def test_correct_boolean_values(self): 13 | test_dict = {'a': True, 'b': 'true', 'c': 'hello'} 14 | expected_dict = {'a': 'true', 'b': 'true', 'c': 'hello'} 15 | self.assertDictEqual(correct_boolean_values(test_dict), expected_dict) 16 | 17 | def test_process_file_should_fail_if_no_file_path(self): 18 | audio_class = Audio() 19 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")): 20 | self.assertRaises(TypeError, audio_class.process_file) 21 | self.assertRaises(ValueError, audio_class.process_file, None) 22 | 23 | def test_process_file_should_succeed_given_valid_path(self): 24 | demo_response = SimpleNamespace( 25 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 26 | demo_job_response = SimpleNamespace( 27 | **{"job_id": "demo job id", "status": "completed"}) 28 | mock_open = mock.mock_open(read_data=bytes([1, 2, 3, 4, 5, 6, 7, 8])) 29 | audio_class = Audio() 30 | 31 | with patch('builtins.open', mock_open), patch("symbl.AuthenticationToken.get_api_client", 32 | Mock(return_value="None")), patch("symbl_rest.AsyncApi.add_audio", 33 | Mock(return_value=demo_response)), patch( 34 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 35 | "time.sleep", Mock(return_value=None) 36 | ): 37 | self.assertEqual(demo_response.conversation_id, audio_class.process_file( 38 | 'abcd').get_conversation_id()) 39 | 40 | def test_process_url_should_succeed_given_valid_url(self): 41 | demo_response = SimpleNamespace( 42 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 43 | demo_job_response = SimpleNamespace( 44 | **{"job_id": "demo job id", "status": "completed"}) 45 | audio_class = Audio() 46 | 47 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch( 48 | "symbl_rest.AsyncApi.add_audio_url", Mock(return_value=demo_response)), patch( 49 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 50 | "time.sleep", Mock(return_value=None) 51 | ): 52 | self.assertEqual(demo_response.conversation_id, audio_class.process_url( 53 | {'url':'abcd'}).get_conversation_id()) 54 | 55 | def test_append_file_should_succeed_given_valid_path(self): 56 | demo_response = SimpleNamespace( 57 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 58 | demo_job_response = SimpleNamespace( 59 | **{"job_id": "demo job id", "status": "completed"}) 60 | mock_open = mock.mock_open(read_data=bytes([1, 2, 3, 4, 5, 6, 7, 8])) 61 | audio_class = Audio() 62 | 63 | with patch('builtins.open', mock_open), patch("symbl.AuthenticationToken.get_api_client", 64 | Mock(return_value="None")), patch( 65 | "symbl_rest.AsyncApi.append_audio", Mock(return_value=demo_response)), patch( 66 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 67 | "time.sleep", Mock(return_value=None) 68 | ): 69 | self.assertEqual(demo_response.conversation_id, 70 | audio_class.append_file('abcd', 'conversationId').get_conversation_id()) 71 | 72 | def test_append_url_should_succeed_given_valid_url(self): 73 | demo_response = SimpleNamespace( 74 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 75 | demo_job_response = SimpleNamespace( 76 | **{"job_id": "demo job id", "status": "completed"}) 77 | audio_class = Audio() 78 | 79 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch( 80 | "symbl_rest.AsyncApi.append_audio_url", Mock(return_value=demo_response)), patch( 81 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 82 | "time.sleep", Mock(return_value=None) 83 | ): 84 | self.assertEqual(demo_response.conversation_id, 85 | audio_class.append_url({'url':'abcd'}, 'conversationId').get_conversation_id()) 86 | 87 | 88 | if __name__ == '__main__': 89 | unittest.main() 90 | -------------------------------------------------------------------------------- /tests/authentication_token_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import Mock, patch 3 | 4 | import symbl_rest 5 | from symbl import AuthenticationToken 6 | from symbl_rest import Configuration, ApiClient 7 | 8 | from types import SimpleNamespace 9 | 10 | 11 | class AuthenticationTokenTest(unittest.TestCase): 12 | 13 | def test_get_access_token_should_succeed_given_valid_token(self): 14 | demo_response = SimpleNamespace(**{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlFVUTRNemhDUVVWQk1rTkJNemszUTBNMlFVVTRRekkyUmpWQ056VTJRelUxUTBVeE5EZzFNUSJ9.eyJodHRwczovL3BsYXRmb3JtLnN5bWJsLmFpL3VzZXJJZCI6IjYyOTg2MTI4NTIwNjQyNTYiLCJpc3MiOiJodHRwczovL2RpcmVjdC1wbGF0Zm9ybS5hdXRoMC5jb20vIiwic3ViIjoiWGNaYnA2MVk1dHJRVWEzVXhOZzkxc3MwSzdXRHN4UVdAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vcGxhdGZvcm0ucmFtbWVyLmFpIiwiaWF0IjoxNjIyODA5NzU4LCJleHAiOjE2MjI4OTYxNTgsImF6cCI6IlhjWmJwNjFZNXRyUVVhM1V4Tmc5MXNzMEs3V0RzeFFXIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.IXdme9NTdaiQFEUIgyfUtKPa8SYMXahcJ8mYRVi5gER4ntqqxeMoSBuIiZJNzwfOfsxZ-HO--azKsGokqJWtuZ3BMeW2OpucFyV1TGbbcYcXWrZwPSlMmcsfXMUlXGxCf7dnfmM48pUNTnpmS6jKpGyAPK4TCaHutSva0wWK93laB2c-ueM7UZ03u-onMkPMykg7_QqStx82ogaanSX-XUHNI7SuODy8strTIpW2DKzOgvg9S6PzDC3zvDyD-sdfXtYsByn8R94CxcGQ9kUs8AF_hOlwUdp_Apq2U57naoXPd3aQx4rRtPl52_2GU6CeGAK7IgI26WWCgcHAe3mLow","expires_in":76254}) 15 | string_value = "demo_value" 16 | with patch("symbl_rest.AuthenticationApi.generate_token", Mock(return_value=demo_response)), patch("configparser.ConfigParser.get", Mock(return_value=string_value)): 17 | self.assertEqual(demo_response.access_token, AuthenticationToken.get_access_token()) 18 | 19 | def test_get_api_header_should_succeed_given_valid_token(self): 20 | demo_response = SimpleNamespace(**{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlFVUTRNemhDUVVWQk1rTkJNemszUTBNMlFVVTRRekkyUmpWQ056VTJRelUxUTBVeE5EZzFNUSJ9.eyJodHRwczovL3BsYXRmb3JtLnN5bWJsLmFpL3VzZXJJZCI6IjYyOTg2MTI4NTIwNjQyNTYiLCJpc3MiOiJodHRwczovL2RpcmVjdC1wbGF0Zm9ybS5hdXRoMC5jb20vIiwic3ViIjoiWGNaYnA2MVk1dHJRVWEzVXhOZzkxc3MwSzdXRHN4UVdAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vcGxhdGZvcm0ucmFtbWVyLmFpIiwiaWF0IjoxNjIyODA5NzU4LCJleHAiOjE2MjI4OTYxNTgsImF6cCI6IlhjWmJwNjFZNXRyUVVhM1V4Tmc5MXNzMEs3V0RzeFFXIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.IXdme9NTdaiQFEUIgyfUtKPa8SYMXahcJ8mYRVi5gER4ntqqxeMoSBuIiZJNzwfOfsxZ-HO--azKsGokqJWtuZ3BMeW2OpucFyV1TGbbcYcXWrZwPSlMmcsfXMUlXGxCf7dnfmM48pUNTnpmS6jKpGyAPK4TCaHutSva0wWK93laB2c-ueM7UZ03u-onMkPMykg7_QqStx82ogaanSX-XUHNI7SuODy8strTIpW2DKzOgvg9S6PzDC3zvDyD-sdfXtYsByn8R94CxcGQ9kUs8AF_hOlwUdp_Apq2U57naoXPd3aQx4rRtPl52_2GU6CeGAK7IgI26WWCgcHAe3mLow","expires_in":76254}) 21 | string_value = "demo_value" 22 | with patch("symbl_rest.AuthenticationApi.generate_token", Mock(return_value=demo_response)), patch("configparser.ConfigParser.get", Mock(return_value=string_value)): 23 | self.assertEqual({'x-api-key': demo_response.access_token}, AuthenticationToken.get_api_header()) 24 | 25 | def test_get_api_client_should_succeed_given_valid_token(self): 26 | demo_response = SimpleNamespace(**{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlFVUTRNemhDUVVWQk1rTkJNemszUTBNMlFVVTRRekkyUmpWQ056VTJRelUxUTBVeE5EZzFNUSJ9.eyJodHRwczovL3BsYXRmb3JtLnN5bWJsLmFpL3VzZXJJZCI6IjYyOTg2MTI4NTIwNjQyNTYiLCJpc3MiOiJodHRwczovL2RpcmVjdC1wbGF0Zm9ybS5hdXRoMC5jb20vIiwic3ViIjoiWGNaYnA2MVk1dHJRVWEzVXhOZzkxc3MwSzdXRHN4UVdAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vcGxhdGZvcm0ucmFtbWVyLmFpIiwiaWF0IjoxNjIyODA5NzU4LCJleHAiOjE2MjI4OTYxNTgsImF6cCI6IlhjWmJwNjFZNXRyUVVhM1V4Tmc5MXNzMEs3V0RzeFFXIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.IXdme9NTdaiQFEUIgyfUtKPa8SYMXahcJ8mYRVi5gER4ntqqxeMoSBuIiZJNzwfOfsxZ-HO--azKsGokqJWtuZ3BMeW2OpucFyV1TGbbcYcXWrZwPSlMmcsfXMUlXGxCf7dnfmM48pUNTnpmS6jKpGyAPK4TCaHutSva0wWK93laB2c-ueM7UZ03u-onMkPMykg7_QqStx82ogaanSX-XUHNI7SuODy8strTIpW2DKzOgvg9S6PzDC3zvDyD-sdfXtYsByn8R94CxcGQ9kUs8AF_hOlwUdp_Apq2U57naoXPd3aQx4rRtPl52_2GU6CeGAK7IgI26WWCgcHAe3mLow","expires_in":76254}) 27 | string_value = "demo_value" 28 | configuration_instance = Configuration() 29 | configuration_instance.api_key['x-api-key'] = demo_response.access_token 30 | with patch("symbl_rest.AuthenticationApi.generate_token", Mock(return_value=demo_response)), patch("configparser.ConfigParser.get", Mock(return_value=string_value)): 31 | returnValue = AuthenticationToken.get_api_client() 32 | self.assertIsNotNone(returnValue) 33 | self.assertIsInstance(returnValue, symbl_rest.ApiClient) 34 | self.assertEqual(returnValue.configuration.api_key, configuration_instance.api_key) 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /tests/conversations_api_test.py: -------------------------------------------------------------------------------- 1 | from symbl.conversations_api.ConversationsApi import ConversationsApi 2 | 3 | import unittest 4 | from unittest.mock import Mock, patch 5 | 6 | from types import SimpleNamespace 7 | 8 | 9 | class ConversationsApiTest(unittest.TestCase): 10 | 11 | def test_get_messages_should_succeed_given_valid_conversation_id(self): 12 | demo_response = SimpleNamespace(**{"messages": [{"id": "6125275260649472", "text": "Hey, is this a question?", "from": { 13 | }, "startTime": "2021-05-27T13:19:25.270Z", "endTime": "2021-05-27T13:19:26.770Z", "conversationId": "6549352073920512", "phrases": []}]}) 14 | conversations_api = ConversationsApi() 15 | 16 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_messages_by_conversation_id", Mock(return_value=demo_response)): 17 | self.assertEqual( 18 | demo_response, conversations_api.get_messages("6549352073920512")) 19 | 20 | def test_get_action_items_should_succeed_given_valid_conversation_id(self): 21 | demo_response = SimpleNamespace(**{"actionItems": [{"id": "4755377456414720", "text": "Steve needs to complete the analysis by next Monday.", "type": "action_item", "score": 0.9765610554943914, "messageIds": ["5590459222065152"], "entities":[{"type": "daterange", "text": "by next monday", "offset": 39, "end": "2021-06-07"}, {"type": "person", "text": "Steve", "offset": 0, "value": { 22 | "assignee": True, "id": "98d11b11-4a0a-4bdd-b2e1-d50238f605d6", "name": "Steve", "userId": "Steve@example.com"}}], "phrases": [], "from":{"id": "98d11b11-4a0a-4bdd-b2e1-d50238f605d6", "name": "Steve", "userId": "Steve@example.com"}, "definitive": True, "assignee": {"id": "98d11b11-4a0a-4bdd-b2e1-d50238f605d6", "name": "Steve", "email": "Steve@example.com"}}]}) 23 | conversations_api = ConversationsApi() 24 | 25 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_action_items_by_conversation_id", Mock(return_value=demo_response)): 26 | self.assertEqual( 27 | demo_response, conversations_api.get_action_items("6549352073920512")) 28 | 29 | def test_get_follow_ups_should_succeed_given_valid_conversation_id(self): 30 | demo_response = SimpleNamespace(**{"followUps": [{"id": "4529863081852928", "text": "Steve needs to follow up about this next Monday.", "type": "follow_up", "score": 1, "messageIds": ["5399026486738944"], "entities":[{"type": "date", "text": "next monday", "offset": 38, "value": "2021-06-07"}, {"type": "person", "text": "Steve", "offset": 0, "value": { 31 | "assignee": True, "id": "e2219a6c-ec5e-4412-94d7-46f565ba5eb7", "name": "Steve", "userId": "Steve@example.com"}}], "phrases": [], "from":{"id": "e2219a6c-ec5e-4412-94d7-46f565ba5eb7", "name": "Steve", "userId": "Steve@example.com"}, "definitive": True, "assignee": {"id": "e2219a6c-ec5e-4412-94d7-46f565ba5eb7", "name": "Steve", "email": "Steve@example.com"}}]}) 32 | conversations_api = ConversationsApi() 33 | 34 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_follow_ups_by_conversation_id", Mock(return_value=demo_response)): 35 | self.assertEqual( 36 | demo_response, conversations_api.get_follow_ups("6549352073920512")) 37 | 38 | def test_get_members_should_succeed_given_valid_conversation_id(self): 39 | demo_response = SimpleNamespace( 40 | **{"members": [{"id": "25f497bd-2800-4e2b-bc71-e143a8f71d6b", "name": "Steve", "email": "steve@abccorp.com"}]}) 41 | conversations_api = ConversationsApi() 42 | 43 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_members_by_conversation_id", Mock(return_value=demo_response)): 44 | self.assertEqual( 45 | demo_response, conversations_api.get_members("6549352073920512")) 46 | 47 | def test_get_questions_should_succeed_given_valid_conversation_id(self): 48 | demo_response = SimpleNamespace(**{"questions": [{"id": "4784246146203648", "text": "Will you be reviewint the analysis by next Monday?", "type": "question", "score": 0.9833490590134427, "messageIds": [ 49 | "4580678118146048"], "from":{"id": "ca0fcd23-dc63-4d42-894a-43ca84650f6f", "name": "Steve", "userId": "Steve@example.com"}}]}) 50 | conversations_api = ConversationsApi() 51 | 52 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_questions_by_conversation_id", Mock(return_value=demo_response)): 53 | self.assertEqual( 54 | demo_response, conversations_api.get_questions("6549352073920512")) 55 | 56 | def test_get_topics_should_succeed_given_valid_conversation_id(self): 57 | demo_response = SimpleNamespace(**{"topics": [{"id": "5506508885327872", "text": "video", "type": "topic", 58 | "score": 0.064, "messageIds": ["5823121492803584", "4673825691140096"], "parentRefs":[]}]}) 59 | conversations_api = ConversationsApi() 60 | 61 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_topics_by_conversation_id", Mock(return_value=demo_response)): 62 | self.assertEqual( 63 | demo_response, conversations_api.get_topics("6549352073920512")) 64 | 65 | def test_get_conversation_should_succeed_given_valid_conversation_id(self): 66 | demo_response = SimpleNamespace(**{"id": "5102778889273344", "type": "meeting", "name": "TestingTextAPI", "startTime": "2021-07-27T07:11:04.304Z", 67 | "endTime": "2021-07-27T07:13:19.184Z", "members": [{"id": "461dd687-4341-48a5-9f3a-7f6019d6378c", "name": "John", "email": "john@example.com"}]}) 68 | conversations_api = ConversationsApi() 69 | 70 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_conversation_by_conversation_id", Mock(return_value=demo_response)): 71 | self.assertEqual( 72 | demo_response, conversations_api.get_conversation("6549352073920512")) 73 | 74 | def test_get_trackers_should_succeed_given_valid_conversation_id(self): 75 | demo_response = SimpleNamespace(**{"id": "5243525907087360", "name": "text_tracker", "matches": [{"messageRefs": [{"id": "5867975128121344", "text": "I don't know which platform is it, but I came to know that platform.", "offset": 19}, { 76 | "id": "6328818106105856", "text": "So this is a live demo that we are trying to give very we are going to show how the platform detects various insights can do transcription in real time and also the different topics of discussions, which would be generated after the call is over, and they will be an email that will be sent to the inbox.", "offset": 84}], "type": "vocabulary", "value": "platform", "insightRefs": []}]}) 77 | conversations_api = ConversationsApi() 78 | 79 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_trackers_by_conversation_id", Mock(return_value=demo_response)): 80 | self.assertEqual( 81 | demo_response, conversations_api.get_trackers("6549352073920512")) 82 | 83 | def test_get_entities_should_succeed_given_valid_conversation_id(self): 84 | demo_response = SimpleNamespace(**{"entities": [{"custom_type": "None", "type": "person", "value": "richard holmes", "text": "richard holmes", "end": "None", "start": "None", "messageRefs": [ 85 | {"id": "5895830172073984", "text": "We need to have the meeting today, and we're going to talk about how to run a product strategy Workshop is by Richard Holmes.", "offset": 111}]}]}) 86 | conversations_api = ConversationsApi() 87 | 88 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_entities_by_conversation_id", Mock(return_value=demo_response)), patch("symbl.utils.Helper.parse_entity_response", Mock(return_value=demo_response)): 89 | self.assertEqual( 90 | demo_response, conversations_api.get_entities("6549352073920512")) 91 | 92 | def test_get_analytics_should_succeed_given_valid_conversation_id(self): 93 | demo_response = SimpleNamespace(**{"metrics": [{"type": "total_silence", "percent": 0, "seconds": 0}, {"type": "total_talk_time", "percent": 100, "seconds": 134.88}, {"type": "total_overlap", "percent": 0, "seconds": 0}], "members": [ 94 | {"id": "461dd687-4341-48a5-9f3a-7f6019d6378c", "name": "John", "userId": "john@example.com", "pace": {"wpm": 124}, "talkTime": {"percentage": 100, "seconds": 134.88}, "listenTime": {"percentage": 0, "seconds": 0}, "overlap": {}}]}) 95 | conversations_api = ConversationsApi() 96 | 97 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_analytics_by_conversation_id", Mock(return_value=demo_response)): 98 | self.assertEqual( 99 | demo_response, conversations_api.get_analytics("6549352073920512")) 100 | 101 | def test_put_members_should_succeed_given_valid_conversation_id(self): 102 | demo_response = SimpleNamespace( 103 | **{"message": "Member with id: 1234567890 for conversationId: 6549352073920512 updated successfully! The update should be reflected in all messages and insights along with this conversation"}) 104 | conversations_api = ConversationsApi() 105 | 106 | param = {"id": "1234567890", 107 | "email": "john@example.in", 108 | "name": "john"} 109 | 110 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.put_members_information_by_members_id", Mock(return_value=demo_response)): 111 | self.assertEqual(demo_response, conversations_api.put_members( 112 | "6549352073920512", "1234567890", param)) 113 | 114 | def test_put_speakers_events_should_succeed_given_valid_conversation_id(self): 115 | demo_response = SimpleNamespace( 116 | **{"message": "Speaker events associated for conversationId: 6549352073920512 successfully! The update should be reflected in all messages and insights along with this conversation"}) 117 | conversations_api = ConversationsApi() 118 | 119 | payload_streaming = { 120 | "speakerEvents": [ 121 | { 122 | "type": "started_speaking", 123 | "user": { 124 | "id": "1234567890", 125 | "name": "john", 126 | "email": "john@example.com" 127 | }, 128 | "offset": { 129 | "seconds": 52, 130 | "nanos": 5000000000 131 | } 132 | } 133 | ] 134 | } 135 | 136 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.put_speakers_event_by_conversation_id", Mock(return_value=demo_response)): 137 | self.assertEqual(demo_response, conversations_api.put_speakers_events( 138 | "6549352073920512", payload_streaming)) 139 | 140 | def test_delete_conversation_should_succeed_given_valid_conversation_id(self): 141 | demo_response = SimpleNamespace( 142 | **{'message': 'successfully deleted the conversation'}) 143 | conversations_api = ConversationsApi() 144 | 145 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.delete_conversation_by_conversation_id", Mock(return_value=demo_response)): 146 | self.assertEqual( 147 | demo_response, conversations_api.delete_conversation("6549352073920512")) 148 | 149 | def test_get_formatted_transcript_should_succeed_given_valid_conversation_id(self): 150 | demo_response = SimpleNamespace( 151 | **{'transcript': {'content_type': 'text/markdown', 'payload': 'Natalia: Let us talk about India Australia'}}) 152 | conversations_api = ConversationsApi() 153 | 154 | payload = { 155 | 'contentType': 'text/markdown', 156 | # 'contentType': 'text/srt', 157 | 'createParagraphs': "true", 158 | 'phrases': { 159 | 'highlightOnlyInsightKeyPhrases': "true", 160 | 'highlightAllKeyPhrases': "true" 161 | }, 162 | 'showSpeakerSeparation': "true" 163 | } 164 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConversationsApi.get_formatted_transcript_by_conversation_id", Mock(return_value=demo_response)): 165 | self.assertEqual( 166 | demo_response, conversations_api.get_formatted_transcript("6549352073920512", parameters=payload)) 167 | 168 | 169 | if __name__ == '__main__': 170 | unittest.main() 171 | -------------------------------------------------------------------------------- /tests/conversations_test.py: -------------------------------------------------------------------------------- 1 | from symbl.Conversations import Conversation 2 | 3 | import unittest 4 | from unittest.mock import Mock, patch 5 | 6 | from types import SimpleNamespace 7 | 8 | 9 | class ConversationsTest(unittest.TestCase): 10 | 11 | def test_get_messages_should_succeed_given_valid_conversation_id(self): 12 | demo_response = SimpleNamespace(**{"messages":[{"id":"6125275260649472","text":"Hey, is this a question?","from":{},"startTime":"2021-05-27T13:19:25.270Z","endTime":"2021-05-27T13:19:26.770Z","conversationId":"6549352073920512","phrases":[]}]}) 13 | conversations_object = Conversation("6549352073920512") 14 | 15 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_messages", Mock(return_value=demo_response)): 16 | self.assertEqual(demo_response, conversations_object.get_messages()) 17 | 18 | def test_get_action_items_should_succeed_given_valid_conversation_id(self): 19 | demo_response = SimpleNamespace(**{"actionItems":[{"id":"4755377456414720","text":"Steve needs to complete the analysis by next Monday.","type":"action_item","score":0.9765610554943914,"messageIds":["5590459222065152"],"entities":[{"type":"daterange","text":"by next monday","offset":39,"end":"2021-06-07"},{"type":"person","text":"Steve","offset":0,"value":{"assignee":True,"id":"98d11b11-4a0a-4bdd-b2e1-d50238f605d6","name":"Steve","userId":"Steve@example.com"}}],"phrases":[],"from":{"id":"98d11b11-4a0a-4bdd-b2e1-d50238f605d6","name":"Steve","userId":"Steve@example.com"},"definitive":True,"assignee":{"id":"98d11b11-4a0a-4bdd-b2e1-d50238f605d6","name":"Steve","email":"Steve@example.com"}}]}) 20 | conversations_object = Conversation("6549352073920512") 21 | 22 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_action_items", Mock(return_value=demo_response)): 23 | self.assertEqual(demo_response, conversations_object.get_action_items()) 24 | 25 | def test_get_follow_ups_should_succeed_given_valid_conversation_id(self): 26 | demo_response = SimpleNamespace(**{"followUps":[{"id":"4529863081852928","text":"Steve needs to follow up about this next Monday.","type":"follow_up","score":1,"messageIds":["5399026486738944"],"entities":[{"type":"date","text":"next monday","offset":38,"value":"2021-06-07"},{"type":"person","text":"Steve","offset":0,"value":{"assignee":True,"id":"e2219a6c-ec5e-4412-94d7-46f565ba5eb7","name":"Steve","userId":"Steve@example.com"}}],"phrases":[],"from":{"id":"e2219a6c-ec5e-4412-94d7-46f565ba5eb7","name":"Steve","userId":"Steve@example.com"},"definitive":True,"assignee":{"id":"e2219a6c-ec5e-4412-94d7-46f565ba5eb7","name":"Steve","email":"Steve@example.com"}}]}) 27 | conversations_object = Conversation("6549352073920512") 28 | 29 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_follow_ups", Mock(return_value=demo_response)): 30 | self.assertEqual(demo_response, conversations_object.get_follow_ups()) 31 | 32 | def test_get_members_should_succeed_given_valid_conversation_id(self): 33 | demo_response = SimpleNamespace(**{"members":[{"id":"25f497bd-2800-4e2b-bc71-e143a8f71d6b","name":"Steve","email":"steve@abccorp.com"}]}) 34 | conversations_object = Conversation("6549352073920512") 35 | 36 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_members", Mock(return_value=demo_response)): 37 | self.assertEqual(demo_response, conversations_object.get_members()) 38 | 39 | def test_get_questions_should_succeed_given_valid_conversation_id(self): 40 | demo_response = SimpleNamespace(**{"questions":[{"id":"4784246146203648","text":"Will you be reviewint the analysis by next Monday?","type":"question","score":0.9833490590134427,"messageIds":["4580678118146048"],"from":{"id":"ca0fcd23-dc63-4d42-894a-43ca84650f6f","name":"Steve","userId":"Steve@example.com"}}]}) 41 | conversations_object = Conversation("6549352073920512") 42 | 43 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_questions", Mock(return_value=demo_response)): 44 | self.assertEqual(demo_response, conversations_object.get_questions()) 45 | 46 | def test_get_topics_should_succeed_given_valid_conversation_id(self): 47 | demo_response = SimpleNamespace(**{"topics":[{"id":"5506508885327872","text":"video","type":"topic","score":0.064,"messageIds":["5823121492803584","4673825691140096"],"parentRefs":[]}]}) 48 | conversations_object = Conversation("6549352073920512") 49 | 50 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_topics", Mock(return_value=demo_response)): 51 | self.assertEqual(demo_response, conversations_object.get_topics()) 52 | 53 | def test_get_conversation_should_succeed_given_valid_conversation_id(self): 54 | demo_response = SimpleNamespace(**{"id": "5102778889273344","type": "meeting","name": "TestingTextAPI","startTime": "2021-07-27T07:11:04.304Z","endTime": "2021-07-27T07:13:19.184Z","members": [{"id": "461dd687-4341-48a5-9f3a-7f6019d6378c","name": "John","email": "john@example.com"}]}) 55 | conversations_object = Conversation("6549352073920512") 56 | 57 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_conversation", Mock(return_value=demo_response)): 58 | self.assertEqual(demo_response, conversations_object.get_conversation()) 59 | 60 | def test_get_trackers_should_succeed_given_valid_conversation_id(self): 61 | demo_response = SimpleNamespace(**{ "id": "5243525907087360", "name": "text_tracker", "matches": [ { "messageRefs": [ { "id": "5867975128121344", "text": "I don't know which platform is it, but I came to know that platform.", "offset": 19 }, { "id": "6328818106105856", "text": "So this is a live demo that we are trying to give very we are going to show how the platform detects various insights can do transcription in real time and also the different topics of discussions, which would be generated after the call is over, and they will be an email that will be sent to the inbox.", "offset": 84 } ], "type": "vocabulary", "value": "platform", "insightRefs": [] } ] }) 62 | conversations_object = Conversation("6549352073920512") 63 | 64 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_trackers", Mock(return_value=demo_response)): 65 | self.assertEqual(demo_response, conversations_object.get_trackers()) 66 | 67 | def test_get_entities_should_succeed_given_valid_conversation_id(self): 68 | demo_response = SimpleNamespace(**{"entities":[{"custom_type":"None","type":"person","value":"richard holmes","text":"richard holmes","end":"None","start":"None","messageRefs":[{"id":"5895830172073984","text":"We need to have the meeting today, and we're going to talk about how to run a product strategy Workshop is by Richard Holmes.","offset":111}]}]}) 69 | conversations_object = Conversation("6549352073920512") 70 | 71 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_entities", Mock(return_value=demo_response)): 72 | self.assertEqual(demo_response, conversations_object.get_entities()) 73 | 74 | def test_get_analytics_should_succeed_given_valid_conversation_id(self): 75 | demo_response = SimpleNamespace(**{"metrics": [{"type": "total_silence","percent": 0,"seconds": 0},{"type": "total_talk_time","percent": 100,"seconds": 134.88},{"type": "total_overlap","percent": 0,"seconds": 0}],"members": [{"id": "461dd687-4341-48a5-9f3a-7f6019d6378c","name": "John","userId": "john@example.com","pace": {"wpm": 124},"talkTime": {"percentage": 100,"seconds": 134.88},"listenTime": {"percentage": 0,"seconds": 0},"overlap": {}}]}) 76 | conversations_object = Conversation("6549352073920512") 77 | 78 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_analytics", Mock(return_value=demo_response)): 79 | self.assertEqual(demo_response, conversations_object.get_analytics()) 80 | 81 | def test_put_members_should_succeed_given_valid_conversation_id(self): 82 | demo_response = SimpleNamespace( 83 | **{"message": "Member with id: 1234567890 for conversationId: 6549352073920512 updated successfully! The update should be reflected in all messages and insights along with this conversation"}) 84 | conversations_object = Conversation("6549352073920512") 85 | 86 | param = {"id": "1234567890", 87 | "email": "john@example.in", 88 | "name": "john"} 89 | 90 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.put_members", Mock(return_value=demo_response)): 91 | self.assertEqual(demo_response, conversations_object.put_members("1234567890", param)) 92 | 93 | def test_put_speakers_events_should_succeed_given_valid_conversation_id(self): 94 | demo_response = SimpleNamespace( 95 | **{"message": "Speaker events associated for conversationId: 6549352073920512 successfully! The update should be reflected in all messages and insights along with this conversation"}) 96 | conversations_object = Conversation("6549352073920512") 97 | 98 | payload_streaming = { 99 | "speakerEvents": [ 100 | { 101 | "type": "started_speaking", 102 | "user": { 103 | "id": "1234567890", 104 | "name": "john", 105 | "email": "john@example.com" 106 | }, 107 | "offset": { 108 | "seconds": 52, 109 | "nanos": 5000000000 110 | } 111 | } 112 | ] 113 | } 114 | 115 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.put_speakers_events", Mock(return_value=demo_response)): 116 | self.assertEqual(demo_response, conversations_object.put_speakers_events( payload_streaming)) 117 | 118 | def test_delete_conversation_should_succeed_given_valid_conversation_id(self): 119 | demo_response = SimpleNamespace( 120 | **{'message': 'successfully deleted the conversation'}) 121 | conversations_object = Conversation("6549352073920512") 122 | 123 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.delete_conversation", Mock(return_value=demo_response)): 124 | self.assertEqual( 125 | demo_response, conversations_object.delete_conversation()) 126 | 127 | def test_get_formatted_transcript_should_succeed_given_valid_conversation_id(self): 128 | demo_response = SimpleNamespace( 129 | **{'transcript': {'content_type': 'text/markdown', 'payload': 'Natalia: Let us talk about India Australia'}}) 130 | conversations_object = Conversation("6549352073920512") 131 | 132 | payload = { 133 | 'contentType': 'text/markdown', 134 | # 'contentType': 'text/srt', 135 | 'createParagraphs': "true", 136 | 'phrases': { 137 | 'highlightOnlyInsightKeyPhrases': "true", 138 | 'highlightAllKeyPhrases': "true" 139 | }, 140 | 'showSpeakerSeparation': "true" 141 | } 142 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl.ConversationsApi.get_formatted_transcript", Mock(return_value=demo_response)): 143 | self.assertEqual( 144 | demo_response, conversations_object.get_formatted_transcript( parameters=payload)) 145 | 146 | 147 | if __name__ == '__main__': 148 | unittest.main() 149 | -------------------------------------------------------------------------------- /tests/telephony_test.py: -------------------------------------------------------------------------------- 1 | 2 | from symbl.telephony_api.TelephonyApi import TelephonyApi 3 | import unittest 4 | from unittest.mock import Mock, patch 5 | 6 | from types import SimpleNamespace 7 | 8 | 9 | class TelephonyTest(unittest.TestCase): 10 | 11 | def test_start_pstn_should_succeed_given_valid_phone(self): 12 | demo_response = SimpleNamespace(**{"event_url":"https://api.symbl.ai/v1/event/7025ecfa-a050-40aa-aa2d-0628b6d097b5","result_web_socket_url":"wss://api.symbl.ai/events/7025ecfa-a050-40aa-aa2d-0628b6d097b5","conversation_id":"5219424513556480","connection_id":"7025ecfa-a050-40aa-aa2d-0628b6d097b5"}) 13 | demo_actions = [{"invokeOn": "stop", "name": "sendSummaryEmail", "parameters": { "emails": [ "example@example.com" ]}}] 14 | telephony_class = TelephonyApi() 15 | 16 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConnectionToEndpointApi.connect_to_endpoint", Mock(return_value=demo_response)): 17 | self.assertEqual(demo_response.conversation_id, telephony_class.start_pstn('demophone', actions=demo_actions).conversation.get_conversation_id()) 18 | 19 | def test_start_sip_should_succeed_given_valid_sip(self): 20 | demo_response = SimpleNamespace(**{"event_url":"https://api.symbl.ai/v1/event/7025ecfa-a050-40aa-aa2d-0628b6d097b5","result_web_socket_url":"wss://api.symbl.ai/events/7025ecfa-a050-40aa-aa2d-0628b6d097b5","conversation_id":"5219424513556480","connection_id":"7025ecfa-a050-40aa-aa2d-0628b6d097b5"}) 21 | telephony_class = TelephonyApi() 22 | 23 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConnectionToEndpointApi.connect_to_endpoint", Mock(return_value=demo_response)): 24 | self.assertEqual(demo_response.conversation_id, telephony_class.start_sip('demophone').conversation.get_conversation_id()) 25 | 26 | # def test_start_sip_should_succeed_given_valid_sip(self): 27 | # demo_response = SimpleNamespace(**{"event_url":"https://api.symbl.ai/v1/event/7025ecfa-a050-40aa-aa2d-0628b6d097b5","result_web_socket_url":"wss://api.symbl.ai/events/7025ecfa-a050-40aa-aa2d-0628b6d097b5","conversation_id":"5219424513556480","connection_id":"7025ecfa-a050-40aa-aa2d-0628b6d097b5"}) 28 | # telephony_class = TelephonyApi() 29 | 30 | # with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch("symbl_rest.ConnectionToEndpointApi.connect_to_endpoint", Mock(return_value=demo_response)): 31 | # self.assertEqual(demo_response.connection_id, telephony_class.stop('demophone')) 32 | 33 | if __name__ == '__main__': 34 | unittest.main() 35 | -------------------------------------------------------------------------------- /tests/text_test.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from symbl.async_api.Text import Text, correct_boolean_values 4 | import unittest 5 | from unittest.mock import Mock, patch 6 | 7 | from types import SimpleNamespace 8 | 9 | 10 | class TextTest(unittest.TestCase): 11 | 12 | def test_correct_boolean_values(self): 13 | test_dict = {'a': True, 'b': 'true', 'c': 'hello'} 14 | expected_dict = {'a': 'true', 'b': 'true', 'c': 'hello'} 15 | self.assertDictEqual(correct_boolean_values(test_dict), expected_dict) 16 | 17 | def test_process_should_fail_if_no_file_path(self): 18 | text_class = Text() 19 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")): 20 | self.assertRaises(TypeError, text_class.process) 21 | self.assertRaises(ValueError, text_class.process, None) 22 | 23 | def test_process_file_should_succeed_given_valid_path(self): 24 | demo_response = SimpleNamespace( 25 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 26 | demo_job_response = SimpleNamespace( 27 | **{"job_id": "demo job id", "status": "completed"}) 28 | mock_open = mock.mock_open(read_data=bytes([1, 2, 3, 4, 5, 6, 7, 8])) 29 | text_class = Text() 30 | 31 | with patch('builtins.open', mock_open), patch("symbl.AuthenticationToken.get_api_client", 32 | Mock(return_value="None")), patch("symbl_rest.AsyncApi.add_text", 33 | Mock(return_value=demo_response)), patch( 34 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch("time.sleep", 35 | Mock(return_value=None)): 36 | self.assertEqual(demo_response.conversation_id, text_class.process( 37 | 'abcd').get_conversation_id()) 38 | 39 | def test_append_should_succeed_given_valid_path(self): 40 | demo_response = SimpleNamespace( 41 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 42 | demo_job_response = SimpleNamespace( 43 | **{"job_id": "demo job id", "status": "completed"}) 44 | text_class = Text() 45 | 46 | with patch("symbl.AuthenticationToken.get_api_client", 47 | Mock(return_value="None")), patch( 48 | "symbl_rest.AsyncApi.append_text", Mock(return_value=demo_response)), patch( 49 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 50 | "time.sleep", Mock(return_value=None) 51 | ): 52 | self.assertEqual(demo_response.conversation_id, 53 | text_class.append('abcd', 'conversationId').get_conversation_id()) 54 | 55 | if __name__ == '__main__': 56 | unittest.main() 57 | -------------------------------------------------------------------------------- /tests/video_test.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from symbl.async_api.Video import Video, correct_boolean_values 4 | import unittest 5 | from unittest.mock import Mock, patch 6 | 7 | from types import SimpleNamespace 8 | 9 | 10 | class VideoTest(unittest.TestCase): 11 | 12 | def test_correct_boolean_values(self): 13 | test_dict = {'a': True, 'b': 'true', 'c': 'hello'} 14 | expected_dict = {'a': 'true', 'b': 'true', 'c': 'hello'} 15 | self.assertDictEqual(correct_boolean_values(test_dict), expected_dict) 16 | 17 | def test_process_file_should_fail_if_no_file_path(self): 18 | video_class = Video() 19 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")): 20 | self.assertRaises(TypeError, video_class.process_file) 21 | self.assertRaises(ValueError, video_class.process_file, None) 22 | 23 | def test_process_file_should_succeed_given_valid_path(self): 24 | demo_response = SimpleNamespace( 25 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 26 | demo_job_response = SimpleNamespace( 27 | **{"job_id": "demo job id", "status": "completed"}) 28 | mock_open = mock.mock_open(read_data=bytes([1, 2, 3, 4, 5, 6, 7, 8])) 29 | video_class = Video() 30 | 31 | with patch('builtins.open', mock_open), patch("symbl.AuthenticationToken.get_api_client", 32 | Mock(return_value="None")), patch("symbl_rest.AsyncApi.add_video", 33 | Mock(return_value=demo_response)), patch( 34 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch("time.sleep", 35 | Mock(return_value=None)): 36 | self.assertEqual(demo_response.conversation_id, video_class.process_file( 37 | 'abcd').get_conversation_id()) 38 | 39 | def test_process_url_should_succeed_given_valid_url(self): 40 | demo_response = SimpleNamespace( 41 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 42 | demo_job_response = SimpleNamespace( 43 | **{"job_id": "demo job id", "status": "completed"}) 44 | video_class = Video() 45 | 46 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch( 47 | "symbl_rest.AsyncApi.add_video_url", Mock(return_value=demo_response)), patch( 48 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 49 | "time.sleep", Mock(return_value=None) 50 | ): 51 | self.assertEqual(demo_response.conversation_id, video_class.process_url( 52 | {'url':'abcd'}).get_conversation_id()) 53 | 54 | def test_append_file_should_succeed_given_valid_path(self): 55 | demo_response = SimpleNamespace( 56 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 57 | demo_job_response = SimpleNamespace( 58 | **{"job_id": "demo job id", "status": "completed"}) 59 | mock_open = mock.mock_open(read_data=bytes([1, 2, 3, 4, 5, 6, 7, 8])) 60 | video_class = Video() 61 | 62 | with patch('builtins.open', mock_open), patch("symbl.AuthenticationToken.get_api_client", 63 | Mock(return_value="None")), patch( 64 | "symbl_rest.AsyncApi.append_video", Mock(return_value=demo_response)), patch( 65 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 66 | "time.sleep", Mock(return_value=None) 67 | ): 68 | self.assertEqual(demo_response.conversation_id, 69 | video_class.append_file('abcd', 'conversationId').get_conversation_id()) 70 | 71 | def test_append_url_should_succeed_given_valid_url(self): 72 | demo_response = SimpleNamespace( 73 | **{"job_id": "demo job id", "conversation_id": "conversationId"}) 74 | demo_job_response = SimpleNamespace( 75 | **{"job_id": "demo job id", "status": "completed"}) 76 | video_class = Video() 77 | 78 | with patch("symbl.AuthenticationToken.get_api_client", Mock(return_value="None")), patch( 79 | "symbl_rest.AsyncApi.append_video_url", Mock(return_value=demo_response)), patch( 80 | "symbl_rest.JobsApi.get_job_status", Mock(return_value=demo_job_response)), patch( 81 | "time.sleep", Mock(return_value=None) 82 | ): 83 | self.assertEqual(demo_response.conversation_id, 84 | video_class.append_url({'url':'abcd'}, 'conversationId').get_conversation_id()) 85 | 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | --------------------------------------------------------------------------------