├── docs ├── images │ ├── actions.png │ ├── basic_chat_flowchart.png │ ├── establish_connection.png │ └── complete_omegle_flowchart.png └── documentation.rst ├── python_omegle ├── exceptions.py ├── __init__.py ├── chatevent.py ├── randomchat.py ├── _abstractchat.py ├── spyeechat.py ├── interestschat.py └── _common.py ├── tests ├── all_tests.py ├── common.py ├── spyee_chat_test.py ├── random_chat_test.py └── interests_chat_test.py ├── setup.py ├── LICENSE.txt ├── CHANGELOG.rst └── README.rst /docs/images/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coal0/python-omegle/HEAD/docs/images/actions.png -------------------------------------------------------------------------------- /docs/images/basic_chat_flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coal0/python-omegle/HEAD/docs/images/basic_chat_flowchart.png -------------------------------------------------------------------------------- /docs/images/establish_connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coal0/python-omegle/HEAD/docs/images/establish_connection.png -------------------------------------------------------------------------------- /docs/images/complete_omegle_flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coal0/python-omegle/HEAD/docs/images/complete_omegle_flowchart.png -------------------------------------------------------------------------------- /python_omegle/exceptions.py: -------------------------------------------------------------------------------- 1 | __all__ = ["PythonOmegleException"] 2 | 3 | 4 | class PythonOmegleException(Exception): 5 | pass 6 | -------------------------------------------------------------------------------- /tests/all_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from interests_chat_test import InterestsChatTest 4 | from random_chat_test import RandomChatTest 5 | from spyee_chat_test import SpyeeChatTest 6 | 7 | if __name__ == "__main__": 8 | unittest.main() 9 | -------------------------------------------------------------------------------- /python_omegle/__init__.py: -------------------------------------------------------------------------------- 1 | from python_omegle.chatevent import ChatEvent 2 | from python_omegle.exceptions import PythonOmegleException 3 | from python_omegle.interestschat import InterestsChat 4 | from python_omegle.randomchat import RandomChat 5 | from python_omegle.spyeechat import SpyeeChat 6 | -------------------------------------------------------------------------------- /python_omegle/chatevent.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | __all__ = ["ChatEvent"] 4 | 5 | 6 | class ChatEvent(enum.Enum): 7 | """Represents an enumeration of chat events. 8 | 9 | Please refer to the documentation for an elaborate description 10 | of the Python-Omegle protocol. 11 | """ 12 | 13 | CHAT_READY = 1 14 | CHAT_WAITING = 2 15 | CHAT_ENDED = 3 16 | GOT_SERVER_NOTICE = 4 17 | GOT_MESSAGE = 5 18 | PARTNER_STARTED_TYPING = 6 19 | PARTNER_STOPPED_TYPING = 7 20 | -------------------------------------------------------------------------------- /tests/common.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from python_omegle.chatevent import ChatEvent 5 | 6 | 7 | def make_events_json(*events): 8 | EVENT_ENUM_TO_STRINGS = { 9 | ChatEvent.CHAT_READY: "connected", 10 | ChatEvent.CHAT_WAITING: "waiting", 11 | ChatEvent.CHAT_ENDED: "strangerDisconnected", 12 | ChatEvent.GOT_MESSAGE: "gotMessage", 13 | ChatEvent.PARTNER_STARTED_TYPING: "typing", 14 | ChatEvent.PARTNER_STOPPED_TYPING: "stoppedTyping", 15 | ChatEvent.GOT_SERVER_NOTICE: "serverMessage" 16 | } 17 | 18 | events_json = [(EVENT_ENUM_TO_STRINGS[event], argument) for event, argument in events] 19 | # Spyee chat expects a question 20 | events_json.append(("question", "dummy: question")) 21 | # Interests chat expects common interests 22 | events_json.append(("commonLikes", "dummy: common likes")) 23 | return events_json 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | with open("README.rst") as file: 7 | long_description = file.read() 8 | 9 | 10 | setup( 11 | name="python-omegle", 12 | version="1.1.6", 13 | url="https://github.com/coal0/python-omegle", 14 | 15 | author="coal0", 16 | author_email="daniel_x3@protonmail.com", 17 | 18 | description="A simple Python API for the Omegle text chat service", 19 | long_description=long_description, 20 | long_description_content_type="text/x-rst", 21 | 22 | keywords="omegle chat webchat api", 23 | classifiers=( 24 | "Development Status :: 5 - Production/Stable", 25 | "Intended Audience :: Developers", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | "Programming Language :: Python :: 3 :: Only", 29 | "Topic :: Communications :: Chat" 30 | ), 31 | 32 | packages=["python_omegle"], 33 | install_requires=["requests"], 34 | ) 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.1.6 (July 14th, 2018) 5 | ----------------------- 6 | 7 | Changed 8 | ~~~~~~~ 9 | 10 | * Updated README.rst to include some information about running the tests. 11 | * (Conclude this absolute mess of an evening) 12 | * Made effort to adhere to Semantic Versioning. 13 | 14 | 15 | 1.0.6 (July 14th, 2018) 16 | ----------------------- 17 | 18 | Changed 19 | ~~~~~~~ 20 | 21 | * Changed ``InterestsChat.__str__()``'s return value to be more accurate. 22 | 23 | 24 | 1.0.5 (July 14th, 2018) 25 | ----------------------- 26 | 27 | Added 28 | ~~~~~ 29 | 30 | * Added missing ``.__str__()`` and ``.__repr__()`` magic methods. 31 | 32 | 33 | 1.0.4 (July 14th, 2018) 34 | ----------------------- 35 | 36 | Added 37 | ~~~~~ 38 | 39 | * Added basic tests for the various chat types. 40 | * Added a 'known issue' on why the typing system may seem to be broken. 41 | * All chat classes now have custom ``.__str__()`` and ``.__repr__()`` methods. 42 | 43 | Changed 44 | ~~~~~~~ 45 | 46 | * Various cosmetical changes. 47 | * Expanded type checking to inspect interest types and container length. 48 | 49 | Fixed 50 | ~~~~~ 51 | 52 | * Due to an incorrect argument to ``range()``, the random ID was 9 characters 53 | long, instead of the required 8. Random IDs are now 8 characters long. 54 | * Fixed a bug where the ``.start()`` call would hang indefinitely because of 55 | an incorrect parameter (a random ID containing illegal characters). 56 | * The ``_AbstractChat`` constructor used to assign the language attribute 57 | privately, avoiding type and value checks by doing so. The attribute is now 58 | correctly assigned through the property setter. 59 | * A logical error with the underlying flag system, preventing chat events from 60 | being handled in a consistent manner, has been fixed. 61 | 62 | 63 | 1.0.3 (July 11th, 2018) 64 | ----------------------- 65 | 66 | Changed 67 | ~~~~~~~ 68 | 69 | * Improved classifiers, now exclusively Python 3 70 | 71 | 72 | 1.0.2 (June 30th, 2018) 73 | ----------------------- 74 | 75 | Added 76 | ~~~~~ 77 | 78 | * Added source code 79 | * Added documentation 80 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python-Omegle 2 | ============= 3 | 4 | Python-Omegle is an unofficial Python API for the Omegle text chat service. 5 | 6 | Features include: 7 | 8 | * A high level, object-oriented API; 9 | * An interface to three different types of chats; 10 | * Support for over 90 languages; 11 | * Simple library design with great extensibility. 12 | 13 | Python-Omegle was originally written in Python 3.6, and is confirmed to 14 | work on Python 3.6, but likely works on all Python 3 releases. 15 | 16 | Getting Started 17 | --------------- 18 | 19 | Requirements 20 | ~~~~~~~~~~~~ 21 | 22 | Python-Omegle relies on `the Requests library`_ to function properly. 23 | If you don't have Requests installed yet, ``pip`` will install it 24 | automatically. Alternatively, you can `install it yourself`_. 25 | 26 | Installing 27 | ~~~~~~~~~~ 28 | 29 | Installing Python-Omegle is very easy! Simply run: 30 | 31 | .. code:: shell 32 | 33 | pip install python-omegle 34 | 35 | ... or for a ``pip3`` install: 36 | 37 | .. code:: shell 38 | 39 | pip3 install python-omegle 40 | 41 | Running the tests 42 | ~~~~~~~~~~~~~~~~~ 43 | 44 | Some basic tests can be found in the 'tests' directory. Each chat type has its 45 | own class and therefore its own testing file. Let's test one of the classes: 46 | 47 | .. code:: shell 48 | 49 | cd tests/ 50 | python3 random_chat_test.py 51 | 52 | If you wish to test all classes at once, simply run the following: 53 | 54 | .. code:: shell 55 | 56 | python3 all_tests.py 57 | 58 | | 59 | 60 | Documentation 61 | ------------- 62 | 63 | Once you've installed Python-Omegle, it's time to take a look at 64 | `the documentation`_, where you will find a step by step tutorial, 65 | some example programs and a complete API reference. 66 | 67 | | 68 | 69 | License 70 | ------- 71 | 72 | Python-Omegle is licensed under the MIT license. The full license 73 | text can be found `here`_. 74 | 75 | | 76 | 77 | Disclaimer 78 | ---------- 79 | 80 | I can in no way be held responsible for your actions with regards to Python-Omegle. 81 | Python-Omegle exists for educational purposes only. Use this tool responsibly. 82 | Do not use it to engage in illegal activities, and do not use it to spam the Omegle 83 | website. Also take a look at the `Omegle terms of service`_, at the bottom of the page. 84 | 85 | 86 | .. _`the Requests library`: https://requests.readthedocs.io/en/master/ 87 | 88 | .. _`install it yourself`: https://requests.readthedocs.io/en/master/user/install/ 89 | 90 | .. _`the documentation`: docs/documentation.rst 91 | 92 | .. _`here`: LICENSE.txt 93 | 94 | .. _`Omegle terms of service`: https://www.omegle.com/ 95 | -------------------------------------------------------------------------------- /tests/spyee_chat_test.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import unittest 3 | import unittest.mock 4 | 5 | import sys 6 | sys.path.insert(0, "../") 7 | 8 | from python_omegle.spyeechat import SpyeeChat 9 | from python_omegle.chatevent import ChatEvent 10 | from python_omegle.exceptions import PythonOmegleException 11 | from python_omegle._common import _SERVER_POOL 12 | 13 | from common import make_events_json 14 | 15 | 16 | class SpyeeChatTest(unittest.TestCase): 17 | def test_constructor(self): 18 | SpyeeChat() 19 | SpyeeChat("fr") 20 | with self.assertRaises(TypeError): 21 | # Not a str instance 22 | SpyeeChat(None) 23 | with self.assertRaises(ValueError): 24 | # Not in ISO 639-1 or ISO 639-2 25 | SpyeeChat("french") 26 | 27 | def test_initial_default_private_attributes(self): 28 | chat = SpyeeChat() 29 | 30 | # _language 31 | self.assertEqual(chat._language, "en") 32 | 33 | # _server_url 34 | self.assertIn(chat._server_url, _SERVER_POOL) 35 | 36 | # _events 37 | self.assertIsInstance(chat._events, queue.Queue) 38 | self.assertEqual(chat._events.qsize(), 0) 39 | 40 | # _random_id 41 | self.assertIsInstance(chat._random_id, str) 42 | self.assertEqual(len(chat._random_id), 8) 43 | 44 | # _chat_id 45 | self.assertIsNone(chat._chat_id) 46 | 47 | # _chat_ready_flag 48 | self.assertFalse(chat._chat_ready_flag) 49 | 50 | def test_property_getters(self): 51 | # With default language 52 | chat1 = SpyeeChat() 53 | self.assertEqual(chat1.language, "en") 54 | 55 | # With custom language 56 | chat2 = SpyeeChat("zh") 57 | self.assertEqual(chat2.language, "zh") 58 | 59 | def test_property_setters(self): 60 | # With default language 61 | chat1 = SpyeeChat() 62 | chat1.language = "nl" 63 | self.assertEqual(chat1.language, "nl") 64 | 65 | # With custom language 66 | chat2 = SpyeeChat("de") 67 | chat2.language = "es" 68 | self.assertEqual(chat2.language, "es") 69 | 70 | def test_simulate_events(self): 71 | def simulate(*events, chat): 72 | events_json = make_events_json(*events) 73 | return chat._classify_events_and_add_to_queue(events_json) 74 | 75 | chat = SpyeeChat() 76 | 77 | # CHAT_READY with dummy question 78 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 79 | event, argument = chat.get_event() 80 | self.assertEqual(event, ChatEvent.CHAT_READY) 81 | self.assertEqual(argument, "dummy: question") 82 | 83 | # CHAT_ENDED 84 | simulate((ChatEvent.CHAT_ENDED, None), chat=chat) 85 | event, argument = chat.get_event() 86 | self.assertEqual(event, ChatEvent.CHAT_ENDED) 87 | self.assertEqual(argument, None) 88 | 89 | # GOT_MESSAGE 90 | # Suppress the initial CHAT_READY event (see test_ready_flag()) 91 | chat._chat_ready_flag = True 92 | simulate((ChatEvent.GOT_MESSAGE, "Hello, World!"), chat=chat) 93 | event, argument = chat.get_event() 94 | self.assertEqual(event, ChatEvent.GOT_MESSAGE) 95 | self.assertEqual(argument, "Hello, World!") 96 | 97 | # Assert that order is maintained 98 | simulate((ChatEvent.CHAT_WAITING, None), chat=chat) 99 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 100 | event1, _ = chat.get_event() 101 | event2, _ = chat.get_event() 102 | self.assertEqual(event1, ChatEvent.CHAT_WAITING) 103 | self.assertEqual(event2, ChatEvent.CHAT_READY) 104 | 105 | def test_ready_flag(self): 106 | def simulate(*events, chat): 107 | events_json = make_events_json(*events) 108 | return chat._classify_events_and_add_to_queue(events_json) 109 | 110 | chat = SpyeeChat() 111 | 112 | # CHAT_READY 113 | self.assertFalse(chat._chat_ready_flag) 114 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 115 | self.assertTrue(chat._chat_ready_flag) 116 | 117 | # CHAT_ENDED 118 | simulate((ChatEvent.CHAT_ENDED, None), chat=chat) 119 | self.assertFalse(chat._chat_ready_flag) 120 | 121 | # GOT_MESSAGE 122 | simulate((ChatEvent.GOT_MESSAGE, ""), chat=chat) 123 | self.assertTrue(chat._chat_ready_flag) 124 | 125 | if __name__ == "__main__": 126 | unittest.main() 127 | -------------------------------------------------------------------------------- /tests/random_chat_test.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import unittest 3 | import unittest.mock 4 | 5 | import sys 6 | sys.path.insert(0, "../") 7 | 8 | from python_omegle.randomchat import RandomChat 9 | from python_omegle.chatevent import ChatEvent 10 | from python_omegle.exceptions import PythonOmegleException 11 | from python_omegle._common import _SERVER_POOL 12 | 13 | from common import make_events_json 14 | 15 | 16 | class RandomChatTest(unittest.TestCase): 17 | def test_constructor(self): 18 | RandomChat() 19 | # Custom language 20 | RandomChat("fr") 21 | with self.assertRaises(TypeError): 22 | # Not a str instance 23 | RandomChat(None) 24 | with self.assertRaises(ValueError): 25 | # Not in ISO 639-1 or ISO 639-2 26 | RandomChat("french") 27 | 28 | def test_initial_default_private_attributes(self): 29 | chat = RandomChat() 30 | 31 | # _language 32 | self.assertEqual(chat._language, "en") 33 | 34 | # _server_url 35 | self.assertIn(chat._server_url, _SERVER_POOL) 36 | 37 | # _events 38 | self.assertIsInstance(chat._events, queue.Queue) 39 | self.assertEqual(chat._events.qsize(), 0) 40 | 41 | # _random_id 42 | self.assertIsInstance(chat._random_id, str) 43 | self.assertEqual(len(chat._random_id), 8) 44 | 45 | # _chat_id 46 | self.assertIsNone(chat._chat_id) 47 | 48 | # _chat_ready_flag 49 | self.assertFalse(chat._chat_ready_flag) 50 | 51 | def test_property_getters(self): 52 | # With default language 53 | chat1 = RandomChat() 54 | self.assertEqual(chat1.language, "en") 55 | 56 | # With custom language 57 | chat2 = RandomChat("zh") 58 | self.assertEqual(chat2.language, "zh") 59 | 60 | def test_property_setters(self): 61 | # With default language 62 | chat1 = RandomChat() 63 | chat1.language = "nl" 64 | self.assertEqual(chat1.language, "nl") 65 | 66 | # With custom language 67 | chat2 = RandomChat("de") 68 | chat2.language = "es" 69 | self.assertEqual(chat2.language, "es") 70 | 71 | def test_simulate_events(self): 72 | def simulate(*events, chat): 73 | events_json = make_events_json(*events) 74 | return chat._classify_events_and_add_to_queue(events_json) 75 | 76 | chat = RandomChat() 77 | 78 | # CHAT_READY 79 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 80 | event, argument = chat.get_event() 81 | self.assertEqual(event, ChatEvent.CHAT_READY) 82 | self.assertEqual(argument, None) 83 | 84 | # CHAT_ENDED 85 | simulate((ChatEvent.CHAT_ENDED, None), chat=chat) 86 | event, argument = chat.get_event() 87 | self.assertEqual(event, ChatEvent.CHAT_ENDED) 88 | self.assertEqual(argument, None) 89 | 90 | # GOT_MESSAGE 91 | # Suppress the initial CHAT_READY event (see test_ready_flag()) 92 | chat._chat_ready_flag = True 93 | simulate((ChatEvent.GOT_MESSAGE, "Hello, World!"), chat=chat) 94 | event, argument = chat.get_event() 95 | self.assertEqual(event, ChatEvent.GOT_MESSAGE) 96 | self.assertEqual(argument, "Hello, World!") 97 | 98 | # Assert that order is maintained 99 | simulate((ChatEvent.CHAT_WAITING, None), chat=chat) 100 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 101 | event1, _ = chat.get_event() 102 | event2, _ = chat.get_event() 103 | self.assertEqual(event1, ChatEvent.CHAT_WAITING) 104 | self.assertEqual(event2, ChatEvent.CHAT_READY) 105 | 106 | def test_ready_flag(self): 107 | def simulate(*events, chat): 108 | events_json = make_events_json(*events) 109 | return chat._classify_events_and_add_to_queue(events_json) 110 | 111 | chat = RandomChat() 112 | 113 | # CHAT_READY 114 | self.assertFalse(chat._chat_ready_flag) 115 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 116 | self.assertTrue(chat._chat_ready_flag) 117 | 118 | # CHAT_ENDED 119 | simulate((ChatEvent.CHAT_ENDED, None), chat=chat) 120 | self.assertFalse(chat._chat_ready_flag) 121 | 122 | # GOT_MESSAGE 123 | simulate((ChatEvent.GOT_MESSAGE, ""), chat=chat) 124 | self.assertTrue(chat._chat_ready_flag) 125 | 126 | if __name__ == "__main__": 127 | unittest.main() 128 | -------------------------------------------------------------------------------- /python_omegle/randomchat.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from python_omegle._abstractchat import _AbstractChat 4 | from python_omegle._common import ( 5 | requests, # patched 6 | _START_URL, 7 | _validate_status_code 8 | ) 9 | from python_omegle.chatevent import ChatEvent 10 | from python_omegle.exceptions import PythonOmegleException 11 | 12 | 13 | class RandomChat(_AbstractChat): 14 | """Represents a chat with a randomly picked partner. 15 | 16 | Please refer to the documentation for an elaborate description 17 | of the Python-Omegle protocol. 18 | """ 19 | 20 | def __init__(self, language="en"): 21 | """Constructor. 22 | 23 | Arguments: 24 | - language = "en" (str): The language in which to converse. 25 | This must be a language code defined in ISO 639-1. The 26 | languages Cebuano (ceb) and Filipino (fil) are also 27 | supported, but only defined in ISO 639-2. Some languages 28 | supported on the Omegle website are not supported because 29 | they are ambiguous. 30 | 31 | Return: 32 | - No return value. 33 | """ 34 | super().__init__(language=language) 35 | 36 | def start(self): 37 | """Start looking for a partner. 38 | 39 | Ask the server to start looking for a partner. Ideally, the 40 | server returns a client ID. If the client ID is obtained 41 | successfully, add initial events to the event queue and return. 42 | 43 | Raise: 44 | - PythonOmegleException if the response's status code is not 45 | 200. 46 | 47 | - PythonOmegleException if the response does not include a 48 | client ID. 49 | 50 | Return: 51 | - No return value. 52 | """ 53 | response = requests.get( 54 | self._server_url + _START_URL.format( 55 | self._random_id, # randid 56 | self.language # lang 57 | ) 58 | ) 59 | _validate_status_code(response=response) 60 | json_data = json.loads(response.text) 61 | 62 | try: 63 | self._chat_id = json_data["clientID"] 64 | except KeyError: 65 | raise PythonOmegleException("Failed to get chat ID.") 66 | 67 | try: 68 | events_json = json_data["events"] 69 | except KeyError: 70 | return 71 | self._classify_events_and_add_to_queue(events_json=events_json) 72 | 73 | def _classify_events_and_add_to_queue(self, events_json): 74 | for event in events_json: 75 | event_type = event[0] 76 | 77 | if event_type == "connected": 78 | self._chat_ready_flag = True 79 | self._events.put((ChatEvent.CHAT_READY, None)) 80 | 81 | elif event_type == "waiting": 82 | self._events.put((ChatEvent.CHAT_WAITING, None)) 83 | 84 | elif event_type == "typing": 85 | if not self._chat_ready_flag: 86 | # Simulate a 'chat ready' event 87 | self._events.put((ChatEvent.CHAT_READY, None)) 88 | self._chat_ready_flag = True 89 | self._events.put((ChatEvent.PARTNER_STARTED_TYPING, None)) 90 | 91 | elif event_type == "stoppedTyping": 92 | # TODO Check flag here too? 93 | self._events.put((ChatEvent.PARTNER_STOPPED_TYPING, None)) 94 | 95 | elif event_type == "gotMessage": 96 | if not self._chat_ready_flag: 97 | # Simulate a 'chat ready' event 98 | self._events.put((ChatEvent.CHAT_READY, None)) 99 | self._chat_ready_flag = True 100 | message = event[1] 101 | self._events.put((ChatEvent.GOT_MESSAGE, message)) 102 | 103 | elif event_type == "strangerDisconnected": 104 | if not self._chat_ready_flag: 105 | # Simulate a 'chat ready' event 106 | self._events.put((ChatEvent.CHAT_READY, None)) 107 | self._events.put((ChatEvent.CHAT_ENDED, None)) 108 | self._chat_ready_flag = False 109 | 110 | elif event_type == "serverMessage": 111 | # TODO Check flag here too? 112 | notice = event[1] 113 | self._events.put((ChatEvent.GOT_SERVER_NOTICE, notice)) 114 | 115 | elif event_type == "identDigests": 116 | # Included after a partner was found, may be ignored. 117 | pass 118 | 119 | elif event_type == "recaptchaRequired": 120 | raise PythonOmegleException("ReCAPTCHA check required.") 121 | 122 | def __repr__(self): 123 | return 'RandomChat(language=\"{}\")'.format(self.language) 124 | 125 | def __str__(self): 126 | # I'll regret this later... 127 | connected = "yes" if self._chat_ready_flag else "no" 128 | return "Random chat (language: {}, connected: {})".format( 129 | self.language, connected 130 | ) 131 | -------------------------------------------------------------------------------- /python_omegle/_abstractchat.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import json 3 | import queue 4 | import random 5 | 6 | from python_omegle._common import ( 7 | requests, # patched 8 | _SERVER_POOL, 9 | _generate_random_id_string, 10 | _check_message_type, 11 | _check_language_type_and_value, 12 | _SEND_URL, 13 | _STOPPED_TYPING_URL, 14 | _TYPING_URL, 15 | _DISCONNECT_URL, 16 | _EVENTS_URL 17 | ) 18 | 19 | __all__ = [] 20 | 21 | 22 | class _AbstractChat(metaclass=abc.ABCMeta): 23 | def __init__(self, language): 24 | """Constructor. 25 | 26 | Arguments: 27 | - language (str): The language in which the client wants to 28 | converse. This must be a language code defined in ISO 29 | 639-1. The languages Cebuano (ceb) and Filipino (fil) are 30 | also supported, but are defined in ISO 639-2. Some 31 | languages supported on the Omegle website are not 32 | supported here because the language codes are ambiguous. 33 | 34 | Return: 35 | - No return value. 36 | """ 37 | self.language = language 38 | self._server_url = random.choice(_SERVER_POOL) 39 | self._events = queue.Queue() 40 | self._random_id = _generate_random_id_string() 41 | self._chat_id = None 42 | self._chat_ready_flag = False 43 | 44 | @abc.abstractmethod 45 | def start(self): 46 | raise NotImplementedError("Method must be implemented in a subclass.") 47 | 48 | def get_event(self): 49 | """Return the latest event. 50 | 51 | Try to return an event from the queue. If the queue is empty, 52 | make a request and add the new events to the queue. If there are 53 | no new events to be added to the queue, continue making requests 54 | until a new event occurs (or the connection is closed). 55 | 56 | Return: 57 | - (tuple) An event, argument tuple. If there is no argument 58 | associated with the event, the argument value will be 59 | None. 60 | """ 61 | try: 62 | return self._events.get_nowait() 63 | except queue.Empty: 64 | pass 65 | 66 | # If we get here, it means there are no events left on the 67 | # queue, so we must ask the server if there are any new events. 68 | events_json = self._get_new_events() 69 | self._classify_events_and_add_to_queue(events_json=events_json) 70 | # We now have a guarantee that there is at least one event ready 71 | # to be processed. 72 | return self._events.get() 73 | 74 | def send(self, message): 75 | """Send a message to the partner. 76 | 77 | Arguments: 78 | - message (str): The message to send. 79 | 80 | Raise: 81 | - PythonOmegleException if the HTTP request fails. 82 | 83 | Return: 84 | - No return value. 85 | """ 86 | _check_message_type(message=message) 87 | response = requests.post( 88 | self._server_url + _SEND_URL, 89 | data={"id": self._chat_id, "msg": message} 90 | ) 91 | 92 | def disconnect(self): 93 | """Disconnect from the chat (leave). 94 | 95 | Raise: 96 | - PythonOmegleException if the HTTP request fails. 97 | 98 | Return: 99 | - No return value. 100 | """ 101 | response = requests.post( 102 | self._server_url + _DISCONNECT_URL, 103 | data={"id": self._chat_id} 104 | ) 105 | self._chat_ready_flag = False 106 | 107 | def start_typing(self): 108 | """Tell the server that the client has started typing. 109 | 110 | Raise: 111 | - PythonOmegleException if the HTTP request fails. 112 | 113 | Return: 114 | - No return value. 115 | """ 116 | response = requests.post( 117 | self._server_url + _TYPING_URL, 118 | data={"id": self._chat_id} 119 | ) 120 | 121 | def stop_typing(self): 122 | """Tell the server that the client has stopped typing. 123 | 124 | Raise: 125 | - PythonOmegleException if the HTTP request fails. 126 | 127 | Return: 128 | - No return value. 129 | """ 130 | response = requests.post( 131 | self._server_url + _STOPPED_TYPING_URL, 132 | data={"id": self._chat_id} 133 | ) 134 | 135 | @property 136 | def language(self): 137 | return self._language 138 | 139 | @language.setter 140 | def language(self, language): 141 | _check_language_type_and_value(language=language) 142 | self._language = language 143 | 144 | def _get_new_events(self): 145 | while True: 146 | response = requests.post( 147 | self._server_url + _EVENTS_URL, 148 | data={"id": self._chat_id} 149 | ) 150 | 151 | json_data = json.loads(response.text) 152 | if json_data not in (None, []): 153 | # NOTE: Not using implicit boolean comparison here, 154 | # because we only want to compare to 'null' and the 155 | # empty array. 156 | return json_data 157 | -------------------------------------------------------------------------------- /python_omegle/spyeechat.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from python_omegle._abstractchat import _AbstractChat 4 | from python_omegle._common import ( 5 | requests, # patched 6 | _validate_status_code, 7 | _START_SPY_URL 8 | ) 9 | from python_omegle.chatevent import ChatEvent 10 | from python_omegle.exceptions import PythonOmegleException 11 | 12 | 13 | class SpyeeChat(_AbstractChat): 14 | """Represents a spyee chat. 15 | 16 | Please refer to the documentation for an elaborate description 17 | of the Python-Omegle protocol. 18 | """ 19 | 20 | def __init__(self, language="en"): 21 | """Constructor. 22 | 23 | Arguments: 24 | - language = "en" (str): The language in which to converse. 25 | This must be a language code defined in ISO 639-1. The 26 | languages Cebuano (ceb) and Filipino (fil) are also 27 | supported, but only defined in ISO 639-2. Some languages 28 | supported on the Omegle website are not supported because 29 | they are ambiguous. 30 | 31 | Return: 32 | - No return value. 33 | """ 34 | super().__init__(language=language) 35 | 36 | def start(self): 37 | """Start looking for a partner. 38 | 39 | Ask the server to start looking for a question, and a partner to 40 | to discuss the question with. Ideally, the server returns a 41 | client ID. If the client ID is obtained 42 | 43 | Raise: 44 | - PythonOmegleException if the response's status code is not 45 | 200. 46 | 47 | - PythonOmegleException if the response does not include a 48 | client ID. 49 | 50 | Return: 51 | - No return value. 52 | """ 53 | response = requests.get( 54 | self._server_url + _START_SPY_URL.format( 55 | self._random_id, # randid 56 | self.language, # lang 57 | ) 58 | ) 59 | _validate_status_code(response=response) 60 | json_data = json.loads(response.text) 61 | 62 | try: 63 | self._chat_id = json_data["clientID"] 64 | except KeyError: 65 | raise PythonOmegleException("Failed to start chat, try again.") 66 | 67 | try: 68 | events_json = json_data["events"] 69 | except KeyError: 70 | return 71 | self._classify_events_and_add_to_queue(events_json=events_json) 72 | 73 | def _classify_events_and_add_to_queue(self, events_json): 74 | """Classify the events and push them to the event queue. 75 | 76 | Please refer to the documentation for an elaborate description 77 | of the Python-Omegle protocol. 78 | """ 79 | for event in events_json: 80 | event_type = event[0] 81 | 82 | if event_type == "connected": 83 | self._chat_ready_flag = True 84 | for event_ in events_json: 85 | if event_[0] == "question": 86 | question = event_[1] 87 | self._events.put((ChatEvent.CHAT_READY, question)) 88 | 89 | elif event_type == "waiting": 90 | self._events.put((ChatEvent.CHAT_WAITING, None)) 91 | 92 | elif event_type == "typing": 93 | if not self._chat_ready_flag: 94 | # Simulate a 'chat ready' event 95 | self._events.put((ChatEvent.CHAT_READY, None)) 96 | self._chat_ready_flag = True 97 | self._events.put((ChatEvent.PARTNER_STARTED_TYPING, None)) 98 | 99 | elif event_type == "stoppedTyping": 100 | # TODO Check flag here too? 101 | self._events.put((ChatEvent.PARTNER_STOPPED_TYPING, None)) 102 | 103 | elif event_type == "gotMessage": 104 | if not self._chat_ready_flag: 105 | # Simulate a 'chat ready' event 106 | self._events.put((ChatEvent.CHAT_READY, None)) 107 | self._chat_ready_flag = True 108 | message = event[1] 109 | self._events.put((ChatEvent.GOT_MESSAGE, message)) 110 | 111 | elif event_type == "strangerDisconnected": 112 | if not self._chat_ready_flag: 113 | # Simulate a 'chat ready' event 114 | self._events.put((ChatEvent.CHAT_READY, None)) 115 | self._chat_ready_flag = True 116 | self._events.put((ChatEvent.CHAT_ENDED, None)) 117 | self._chat_ready_flag = False 118 | 119 | elif event_type == "serverMessage": 120 | # TODO Check flag here too? 121 | notice = event[1] 122 | self._events.put((ChatEvent.GOT_SERVER_NOTICE, notice)) 123 | 124 | elif event_type == "identDigests": 125 | # Included after a partner was found, may be ignored. 126 | pass 127 | 128 | elif event_type == "recaptchaRequired": 129 | raise PythonOmegleException("ReCAPTCHA check required.") 130 | 131 | def __repr__(self): 132 | return 'SpyeeChat(language=\"{}\")'.format(self.language) 133 | 134 | def __str__(self): 135 | # I'll regret this later... 136 | connected = "yes" if self._chat_ready_flag else "no" 137 | return "Spyee chat (language: {}, connected: {})".format( 138 | self.language, connected 139 | ) 140 | -------------------------------------------------------------------------------- /tests/interests_chat_test.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import unittest 3 | import unittest.mock 4 | 5 | import sys 6 | sys.path.insert(0, "../") 7 | 8 | from python_omegle.interestschat import InterestsChat 9 | from python_omegle.chatevent import ChatEvent 10 | from python_omegle.exceptions import PythonOmegleException 11 | from python_omegle._common import _SERVER_POOL 12 | 13 | from common import make_events_json 14 | 15 | 16 | class InterestsChatTest(unittest.TestCase): 17 | def test_constructor(self): 18 | InterestsChat(["foo"]) 19 | InterestsChat(["foo"], "fr") 20 | 21 | interests_not_list = r"Interests must be a list." 22 | with self.assertRaisesRegex(TypeError, interests_not_list): 23 | # Interests not a list instance 24 | InterestsChat(None) 25 | 26 | interests_elements_not_str = r"All interests must be strings." 27 | with self.assertRaisesRegex(TypeError, interests_elements_not_str): 28 | # Interest is not a str instance 29 | InterestsChat([42]) 30 | 31 | with self.assertRaises(ValueError): 32 | # Interests list is empty 33 | InterestsChat([]) 34 | 35 | language_not_str = r"Language must be a string." 36 | with self.assertRaisesRegex(TypeError, language_not_str): 37 | # Language not a str instance 38 | InterestsChat(["foo"], None) 39 | 40 | with self.assertRaises(ValueError): 41 | # Not in ISO 639-1 or ISO 639-2 42 | InterestsChat(["foo"], "french") 43 | 44 | def test_initial_default_private_attributes(self): 45 | chat = InterestsChat(["foo"]) 46 | 47 | # _language 48 | self.assertEqual(chat._language, "en") 49 | 50 | # _interests 51 | self.assertEqual(chat._interests, ["foo"]) 52 | 53 | # _server_url 54 | self.assertIn(chat._server_url, _SERVER_POOL) 55 | 56 | # _events 57 | self.assertIsInstance(chat._events, queue.Queue) 58 | self.assertEqual(chat._events.qsize(), 0) 59 | 60 | # _random_id 61 | self.assertIsInstance(chat._random_id, str) 62 | self.assertEqual(len(chat._random_id), 8) 63 | 64 | # _chat_id 65 | self.assertIsNone(chat._chat_id) 66 | 67 | # _chat_ready_flag 68 | self.assertFalse(chat._chat_ready_flag) 69 | 70 | def test_property_getters(self): 71 | # Interests 72 | chat1 = InterestsChat(["foo"]) 73 | self.assertEqual(chat1.interests, ["foo"]) 74 | 75 | # With default language 76 | chat2 = InterestsChat(["foo"]) 77 | self.assertEqual(chat2.language, "en") 78 | 79 | # With custom language 80 | chat3 = InterestsChat(["foo"], "zh") 81 | self.assertEqual(chat3.language, "zh") 82 | 83 | def test_property_setters(self): 84 | # Interests 85 | chat1 = InterestsChat(["foo"]) 86 | chat1.interests.append("bar") 87 | self.assertEqual(chat1.interests, ["foo", "bar"]) 88 | 89 | # With default language 90 | chat1 = InterestsChat(["foo"]) 91 | chat1.language = "nl" 92 | self.assertEqual(chat1.language, "nl") 93 | 94 | # With custom language 95 | chat2 = InterestsChat(["foo"], "de") 96 | chat2.language = "es" 97 | self.assertEqual(chat2.language, "es") 98 | 99 | def test_simulate_events(self): 100 | def simulate(*events, chat): 101 | events_json = make_events_json(*events) 102 | return chat._classify_events_and_add_to_queue(events_json) 103 | 104 | chat = InterestsChat(["foo"]) 105 | 106 | # CHAT_READY 107 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 108 | event, argument = chat.get_event() 109 | self.assertEqual(event, ChatEvent.CHAT_READY) 110 | self.assertEqual(argument, "dummy: common likes") 111 | 112 | # CHAT_ENDED 113 | simulate((ChatEvent.CHAT_ENDED, None), chat=chat) 114 | event, argument = chat.get_event() 115 | self.assertEqual(event, ChatEvent.CHAT_ENDED) 116 | self.assertEqual(argument, None) 117 | 118 | # GOT_MESSAGE 119 | # Suppress the initial CHAT_READY event (see test_ready_flag()) 120 | chat._chat_ready_flag = True 121 | simulate((ChatEvent.GOT_MESSAGE, "Hello, World!"), chat=chat) 122 | event, argument = chat.get_event() 123 | self.assertEqual(event, ChatEvent.GOT_MESSAGE) 124 | self.assertEqual(argument, "Hello, World!") 125 | 126 | # Assert that order is maintained 127 | simulate((ChatEvent.CHAT_WAITING, None), chat=chat) 128 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 129 | event1, _ = chat.get_event() 130 | event2, _ = chat.get_event() 131 | self.assertEqual(event1, ChatEvent.CHAT_WAITING) 132 | self.assertEqual(event2, ChatEvent.CHAT_READY) 133 | 134 | def test_ready_flag(self): 135 | def simulate(*events, chat): 136 | events_json = make_events_json(*events) 137 | return chat._classify_events_and_add_to_queue(events_json) 138 | 139 | chat = InterestsChat(["foo"]) 140 | 141 | # CHAT_READY 142 | self.assertFalse(chat._chat_ready_flag) 143 | simulate((ChatEvent.CHAT_READY, None), chat=chat) 144 | self.assertTrue(chat._chat_ready_flag) 145 | 146 | # CHAT_ENDED 147 | simulate((ChatEvent.CHAT_ENDED, None), chat=chat) 148 | self.assertFalse(chat._chat_ready_flag) 149 | 150 | # GOT_MESSAGE 151 | simulate((ChatEvent.GOT_MESSAGE, ""), chat=chat) 152 | self.assertTrue(chat._chat_ready_flag) 153 | 154 | if __name__ == "__main__": 155 | unittest.main() 156 | -------------------------------------------------------------------------------- /python_omegle/interestschat.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from python_omegle._abstractchat import _AbstractChat 4 | from python_omegle._common import ( 5 | requests, # patched 6 | _START_INTERESTS_URL, 7 | _validate_status_code, 8 | _check_interests_type 9 | ) 10 | from python_omegle.chatevent import ChatEvent 11 | from python_omegle.exceptions import PythonOmegleException 12 | 13 | 14 | class InterestsChat(_AbstractChat): 15 | """Represents a chat with a partner with common interests. 16 | 17 | Please refer to the documentation for an elaborate description 18 | of the Python-Omegle protocol. 19 | """ 20 | 21 | def __init__(self, interests, language="en"): 22 | """Constructor. 23 | 24 | Arguments: 25 | - interests (list): A list of interests. The elements must 26 | be strings. 27 | 28 | - language = "en" (str): The language in which to converse. 29 | This must be a language code defined in ISO 639-1. The 30 | languages Cebuano (ceb) and Filipino (fil) are also 31 | supported, but only defined in ISO 639-2. Some languages 32 | supported on the Omegle website are not supported because 33 | they are ambiguous. 34 | 35 | Return: 36 | - No return value. 37 | """ 38 | super().__init__(language=language) 39 | self.interests = interests 40 | 41 | def start(self): 42 | """Start looking for a partner. 43 | 44 | Ask the server to start looking for a partner with common 45 | interests. Ideally, the server returns a client ID. If the 46 | client ID is obtained successfully, add initial events to the 47 | event queue and return. 48 | 49 | Raise: 50 | - PythonOmegleException if the response's status code is not 51 | 200. 52 | 53 | - PythonOmegleException if the response does not include a 54 | client ID. 55 | 56 | Return: 57 | - No return value. 58 | """ 59 | response = requests.get( 60 | self._server_url + _START_INTERESTS_URL.format( 61 | self._random_id, # randid 62 | self.language, # lang 63 | json.dumps(self.interests) # topics 64 | ) 65 | ) 66 | 67 | _validate_status_code(response=response) 68 | json_data = json.loads(response.text) 69 | 70 | try: 71 | self._chat_id = json_data["clientID"] 72 | except KeyError: 73 | raise PythonOmegleException("Failed to get chat ID.") 74 | 75 | try: 76 | events_json = json_data["events"] 77 | except KeyError: 78 | return 79 | self._classify_events_and_add_to_queue(events_json=events_json) 80 | 81 | def _classify_events_and_add_to_queue(self, events_json): 82 | """Classify the events and push them to the event queue. 83 | 84 | Please refer to the documentation for an elaborate description 85 | of the Python-Omegle protocol. 86 | """ 87 | for event in events_json: 88 | event_type = event[0] 89 | 90 | if event_type == "connected": 91 | self._chat_ready_flag = True 92 | for event_ in events_json: 93 | if event_[0] == "commonLikes": 94 | common_interests = event_[1] 95 | self._events.put((ChatEvent.CHAT_READY, common_interests)) 96 | 97 | elif event_type == "waiting": 98 | self._events.put((ChatEvent.CHAT_WAITING, None)) 99 | 100 | elif event_type == "typing": 101 | if not self._chat_ready_flag: 102 | # Simulate a 'chat ready' event 103 | self._events.put((ChatEvent.CHAT_READY, None)) 104 | self._chat_ready_flag = True 105 | self._events.put((ChatEvent.PARTNER_STARTED_TYPING, None)) 106 | 107 | elif event_type == "stoppedTyping": 108 | # TODO Check flag here too? 109 | self._events.put((ChatEvent.PARTNER_STOPPED_TYPING, None)) 110 | 111 | elif event_type == "gotMessage": 112 | if not self._chat_ready_flag: 113 | # Simulate a 'chat ready' event 114 | self._events.put((ChatEvent.CHAT_READY, None)) 115 | self._chat_ready_flag = True 116 | message = event[1] 117 | self._events.put((ChatEvent.GOT_MESSAGE, message)) 118 | 119 | elif event_type == "strangerDisconnected": 120 | if not self._chat_ready_flag: 121 | # Simulate a 'chat ready' event 122 | self._events.put((ChatEvent.CHAT_READY, None)) 123 | self._events.put((ChatEvent.CHAT_ENDED, None)) 124 | self._chat_ready_flag = False 125 | 126 | elif event_type == "serverMessage": 127 | # TODO Check flag here too? 128 | notice = event[1] 129 | self._events.put((ChatEvent.GOT_SERVER_NOTICE, notice)) 130 | 131 | elif event_type == "identDigests": 132 | # Included after a partner was found, may be ignored. 133 | pass 134 | 135 | elif event_type == "recaptchaRequired": 136 | raise PythonOmegleException("ReCAPTCHA check required.") 137 | 138 | @property 139 | def interests(self): 140 | return self._interests 141 | 142 | @interests.setter 143 | def interests(self, interests): 144 | _check_interests_type(interests=interests) 145 | self._interests = interests 146 | 147 | def __repr__(self): 148 | return 'InterestsChat(interests={}, language=\"{}\")'.format( 149 | self.interests, self.language 150 | ) 151 | 152 | def __str__(self): 153 | # I'll regret this later... 154 | connected = "yes" if self._chat_ready_flag else "no" 155 | return "Interests-based chat (interests: {}, language: {}, connected: {})".format( 156 | self.interests, self.language, connected 157 | ) 158 | -------------------------------------------------------------------------------- /python_omegle/_common.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import requests 4 | 5 | from python_omegle.exceptions import PythonOmegleException 6 | 7 | _SERVER_POOL = tuple("https://front{}.omegle.com".format(n) for n in range(1, 33)) 8 | 9 | _START_URL = "/start?caps=recaptcha2,t&rcs=1&\ 10 | firstevents=1&randid={}&lang={}" # GET randid, lang 11 | _START_INTERESTS_URL = _START_URL + "&topics={}" # GET randid, lang, topics 12 | _START_SPY_URL = _START_URL + "&wantsspy=1" # GET randid, lang 13 | _SEND_URL = "/send" # POST id, msg 14 | _DISCONNECT_URL = "/disconnect" # POST id 15 | _TYPING_URL = "/typing" # POST id 16 | _STOPPED_TYPING_URL = "/stoppedtyping" # POST id 17 | _EVENTS_URL = "/events" # POST id 18 | 19 | _RANDOM_ID_POOL = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" 20 | 21 | # Language codes according to ISO 639-1 (+ ISO 639-2). 22 | # Only languages supported by the Google Translate widget are included 23 | # here. 24 | _LANGUAGE_CODES = ( 25 | "en", 26 | "af", 27 | "sq", 28 | "am", 29 | "ar", 30 | "hy", 31 | "az", 32 | "eu", 33 | "be", 34 | "bn", 35 | "bs", 36 | "bg", 37 | "ca", 38 | "ceb", # Cebuano, ISO 639-2 39 | "ny", 40 | "zh", # Simply listed as 'Chinese' 41 | "co", 42 | "hr", 43 | "cs", 44 | "da", 45 | "nl", 46 | "eo", 47 | "et", 48 | "fil", # Filipino, ISO 639-2 49 | "fi", 50 | "fr", 51 | # Frisian not implemented, ambiguous 52 | "gl", 53 | "ka", 54 | "de", 55 | "el", 56 | "gu", 57 | "ht", 58 | "ha", 59 | "he", 60 | "hi", 61 | # Hmong not implemented, ambiguous 62 | "hu", 63 | "is", 64 | "ig", 65 | "id", 66 | "ga", 67 | "it", 68 | "ja", 69 | "jv", 70 | "kn", 71 | "kk", 72 | "km", 73 | "ko", 74 | "ku", 75 | "ky", 76 | "lo", 77 | "la", 78 | "lv", 79 | "lt", 80 | "lb", 81 | "mk", 82 | "mg", 83 | "ms", 84 | "ml", 85 | "mt", 86 | "mi", 87 | "mr", 88 | "mn", 89 | "my", 90 | "ne", 91 | "no", 92 | "ps", 93 | "fa", 94 | "pl", 95 | "pt", 96 | "ro", 97 | "ru", 98 | "sm", 99 | "gd", 100 | "sr", 101 | "st", 102 | "sn", 103 | "sd", 104 | "si", 105 | "sk", 106 | "sl", 107 | "so", 108 | "es", 109 | "su", 110 | "sw", 111 | "sv", 112 | "tg", 113 | "ta", 114 | "te", 115 | "th", 116 | "tr", 117 | "uk", 118 | "ur", 119 | "uz", 120 | "vi", 121 | "cy", 122 | "xh", 123 | "yi", 124 | "yo", 125 | "zu" 126 | ) 127 | 128 | _MIN_QUESTION_LENGTH = 10 129 | _MAX_QUESTION_LENGTH = 200 130 | 131 | 132 | def _check_message_type(message): 133 | """Check if the argument is a string. 134 | If the argument is not a string, raise a TypeError. 135 | """ 136 | if not isinstance(message, str): 137 | raise TypeError("Message must be a string.") 138 | 139 | 140 | def _check_interests_type(interests): 141 | """Check if the argument is a valid list of interests. 142 | If the argument is not a list, raise a TypeError. 143 | If the list is empty, raise a ValueError. 144 | If any of the elements in the list is not a string, raise a TypeError. 145 | """ 146 | if not isinstance(interests, list): 147 | raise TypeError("Interests must be a list.") 148 | if not interests: 149 | raise ValueError("At least one interest must be provided.") 150 | if any(not isinstance(interest, str) for interest in interests): 151 | raise TypeError("All interests must be strings.") 152 | 153 | 154 | def _check_language_type_and_value(language): 155 | """Check if a language specification is valid. 156 | If the argument is not a string, raise a TypeError. 157 | If the argument is not a valid language code, that is, it is not 158 | defined in _LANGUAGE_CODES, raise a ValueError. 159 | """ 160 | if not isinstance(language, str): 161 | raise TypeError("Language must be a string.") 162 | elif language.lower() not in _LANGUAGE_CODES: 163 | raise ValueError( 164 | "Unknown language: '{}' ".format(language) + 165 | "(not defined or ambiguous in ISO 639-1 / ISO 639-2)." 166 | ) 167 | 168 | 169 | def _validate_status_code(response): 170 | """Check if the argument has a valid response code. 171 | If the status code is not 200, raise a PythonOmegleException. 172 | This function assumes the argument is a requests.Response instance. 173 | """ 174 | if response.status_code != requests.codes.ok: 175 | raise PythonOmegleException( 176 | "Request returned bad HTTP status code ({}).".format( 177 | response.status_code 178 | ) 179 | ) 180 | 181 | 182 | def _generate_random_id_string(): 183 | """Generate an 8-character random ID string. 184 | The ID returned consists of digits and uppercase ASCII letters. 185 | """ 186 | return "".join(random.choice(_RANDOM_ID_POOL) for _ in range(8)) 187 | 188 | 189 | def _make_safe_request(function): 190 | """Try to return function(). 191 | If a requests.RequestException is caught, raise a 192 | PythonOmegleException. 193 | """ 194 | try: 195 | return function() 196 | except requests.RequestException as exc: 197 | error_message = "HTTP request failed (reason: {}).".format(exc) 198 | raise PythonOmegleException(error_message) from None 199 | 200 | requests.get_ = requests.get 201 | requests.post_ = requests.post 202 | 203 | def _safe_requests_get(*args, **kwargs): 204 | return _make_safe_request(lambda: requests.get_(*args, **kwargs)) 205 | 206 | def _safe_requests_post(*args, **kwargs): 207 | return _make_safe_request(lambda: requests.post_(*args, **kwargs)) 208 | 209 | requests.get = _safe_requests_get 210 | requests.post = _safe_requests_post 211 | 212 | del _safe_requests_get 213 | del _safe_requests_post 214 | 215 | 216 | def _check_question_type_and_value(question): 217 | """Check if a question is valid. 218 | If the argument is not a string, raise a TypeError. 219 | If the argument is shorter than _MIN_QUESTION_LENGTH, or longer than 220 | _MAX_QUESTION_LENGTH, raise a ValueError. 221 | """ 222 | if not isinstance(question, str): 223 | raise TypeError("Question must be a string.") 224 | question_length = len(question.strip()) 225 | if question_length < _MIN_QUESTION_LENGTH: 226 | raise ValueError("Question must be at least 10 characters long.") 227 | elif question_length > _MAX_QUESTION_LENGTH: 228 | raise ValueError("Question can't be longer than 200 characters.") 229 | -------------------------------------------------------------------------------- /docs/documentation.rst: -------------------------------------------------------------------------------- 1 | Python-Omegle 2 | ============= 3 | 4 | Before you can write any code, it's essential to have at least a basic 5 | understanding of the Omegle protocol (the client-server-client protocol), and 6 | of the Python-Omegle protocol (this API). 7 | 8 | The Protocol: Omegle 9 | -------------------- 10 | 11 | Note: If you are only interested in the concrete Omegle protocol, skip right 12 | ahead to `How does Omegle work?`_. 13 | 14 | Note: This is not a complete description of the protocol. Because there is no 15 | official Omegle API, some of this information is the result of trial-and-error 16 | testing. To somewhat limit the amount of text in this file, some implementation 17 | details have been left out. 18 | 19 | A brief introduction to Omegle 20 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | Omegle is a popular online chatting service. The website first launched in 23 | 2009, and was an immediate success. [1]_ The website features both a video chat 24 | and a text chat mode. Despite attempts to stop spam, there are still quite a 25 | few bots starting automated chats. 26 | 27 | Omegle text chat allows one to chat with a randomly selected partner. 28 | Alternatively, you can chat with someone who shares your interests. In 2011, 29 | spy mode was introduced. In spy mode, you can either be the spy, and pose a 30 | question for two strangers to discuss, or be a spyee, and discuss a question 31 | with another stranger while someone else listens in. 32 | 33 | How do client-server-client chat services connect two clients? 34 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | Most HTTP-based client-server-client chat protocols work like this: 37 | 38 | 1. A client requests for the server to find a chat partner; 39 | 2. The server waits until it gets a request from another client; 40 | 3. The server relays messages between between the two, until either one of the 41 | clients disconnects. 42 | 43 | From the client's point of view, this looks something like this: 44 | 45 | .. image:: images/basic_chat_flowchart.png 46 | 47 | When a client asks the server to find a partner, the server generates a unique 48 | ID for that client. The server then returns a response with the client's ID, 49 | and a status. If at the time the request was made, no other clients were 50 | waiting to start a chat, the server tells the client to standby until further 51 | notice. Once a partner becomes available, the server tells the client it is 52 | connected. 53 | 54 | Here's a visualization of this procedure: 55 | 56 | .. image:: images/establish_connection.png 57 | 58 | The blue text next to each node indicates the status or action taken at that 59 | point. The dashed arrows next to C1 represent I/O operations. You can imagine 60 | similar operations for C2. 61 | 62 | How does the server do its job as a middleman? 63 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 64 | 65 | Once two clients are connected to one another, they can start exchanging 66 | messages. To interact with their partner, a client simply sends a request to 67 | the server, including the action they want to perform and their ID. The server 68 | remembers the partner the ID is tied to, and is able to effectively be a 69 | middleman: 70 | 71 | .. image:: images/actions.png 72 | 73 | The rightwards double arrow (⇒) and the rightwards double arrow with stroke 74 | (⇏) represent 'associated with' and 'not / no longer associated with', 75 | respectively. When the chat ends, the server disposes of the IDs so they may 76 | be reused. 77 | 78 | .. _`How does Omegle work?`: 79 | 80 | How does Omegle work? 81 | ~~~~~~~~~~~~~~~~~~~~~ 82 | 83 | In the previous sections, some details were intentionally left out, such as 84 | as how querying for a partner works exactly, and how embedding an ID works. 85 | That's because these details are specific to Omegle. 86 | 87 | This is the complete flowchart for generalized Omegle client-server 88 | interaction, as seen from the client's point of view: 89 | 90 | .. image:: images/complete_omegle_flowchart.png 91 | 92 | Don't be overwhelmed, it isn't all that complicated. Let's follow the path 93 | and add comments step by step. 94 | 95 | 1. The client creates a random ID. The random ID is 8 characters long and 96 | consists of uppercase letters and digits. 97 | 98 | 2. The client is now in a state where it can start a new chat. 99 | 100 | 3. The client sends a request to the server, asking it to look for a partner 101 | (Request: *Start ___ chat*). 102 | 103 | 4. If the server returns a client ID, go to 6. 104 | 105 | 5. If the server does not return a client ID, something went wrong. A common 106 | issue is server-side spam prevention (using ReCAPTCHA). If that is the 107 | case, the user is to solve a ReCAPTCHA test before continuing. If the 108 | issue has nothing to do with ReCAPTCHA, it may be a connection problem. 109 | The client simply retries until it obtains an ID. 110 | 111 | 6. Along with the client ID, the server has sent an initial set of events. 112 | 113 | 7. If one of the events signifies that a connection has been established, 114 | go to 9. 115 | 116 | 8. If there is no event that signifies a connection has been established, 117 | the client tells the user it is waiting for a partner, while continously 118 | checking in for potential new events (Request: *Query events*) until one 119 | is found. 120 | 121 | 9. At this point, a connection between the two clients has been established. 122 | 123 | 10. The client will make a request to the server to keep updated on the 124 | latest events (Request: *Query events*). 125 | 126 | 11. If there are no new events, go to 10. 127 | 128 | 12. The client processes the events and outputs data accordingly. 129 | 130 | 13. If any of the events signifies the end of the chat (i.e. the partner 131 | disconnected, or their connection timed out), go to 2. 132 | 133 | 14. Go to 10. 134 | 135 | This is the table of requests: 136 | 137 | .. _`Requests table`: 138 | 139 | +----------------------+-----------------+-------------+----------------------+--------------------+ 140 | | Requests table | 141 | +----------------------+-----------------+-------------+----------------------+--------------------+ 142 | | Description | URL [2]_ | HTTP method | Parameters | Static parameters | 143 | +======================+=================+=============+======================+====================+ 144 | | Server | omegle.com [3]_ | N/A | N/A | N/A | 145 | +----------------------+-----------------+-------------+----------------------+--------------------+ 146 | | Start random chat | /start? | GET | * randid | * caps: recaptcha2 | 147 | | | | | * lang | * rcs: 1 | 148 | | | | | | * firstevents: 1 | 149 | +----------------------+-----------------+-------------+----------------------+--------------------+ 150 | | Start interests chat | /start? | GET | * randid | * caps: recaptcha2 | 151 | | | | | * lang | * rcs: 1 | 152 | | | | | * topics | * firstevents: 1 | 153 | +----------------------+-----------------+-------------+----------------------+--------------------+ 154 | | Start spy chat | /start? | GET | * randid | * caps: recaptcha2 | 155 | | | | | * lang | * rcs: 1 | 156 | | | | | | * firstevents: 1 | 157 | | | | | | * wantspy: 1 | 158 | +----------------------+-----------------+-------------+----------------------+--------------------+ 159 | | Query events | /events | POST | * id | N/A | 160 | +----------------------+-----------------+-------------+----------------------+--------------------+ 161 | | Send message | /send | POST | * id | N/A | 162 | | | | | * msg | | 163 | +----------------------+-----------------+-------------+----------------------+--------------------+ 164 | | Disconnect | /disconnect | POST | * id | N/A | 165 | +----------------------+-----------------+-------------+----------------------+--------------------+ 166 | | Start typing | /typing | POST | * id | N/A | 167 | +----------------------+-----------------+-------------+----------------------+--------------------+ 168 | | Stop typing | /stoppedtyping | POST | * id | N/A | 169 | +----------------------+-----------------+-------------+----------------------+--------------------+ 170 | 171 | Things to note: 172 | 173 | * The *Start ___ chat* requests ask the server to look for a partner, but don't 174 | guarantee that one is immediately available. This must be confirmed by 175 | waiting for the right event (see below). 176 | * The static parameters are required by Omegle, but not part of the API. 177 | Python-Omegle takes care of these behind the scenes. 178 | 179 | Most client code will revolve around handling events (step 10). Therefore, the 180 | events events deserve some extra attention. There are 7 different events to 181 | take into account: [4]_ 182 | 183 | .. _`Events table`: 184 | 185 | +------------------------+----------+--------------------+ 186 | | Events table | 187 | +------------------------+----------+--------------------+ 188 | | Description | Argument | Notes | 189 | +========================+==========+====================+ 190 | | Chat ready | [5]_ | N/A | 191 | +------------------------+----------+--------------------+ 192 | | Chat ended | N/A | No further actions | 193 | | | | possible with this | 194 | | | | client ID. | 195 | +------------------------+----------+--------------------+ 196 | | Waiting for partner | N/A | Could be ignored, | 197 | | | | potentially useful | 198 | | | | for informing the | 199 | | | | end user. | 200 | +------------------------+----------+--------------------+ 201 | | Partner started typing | N/A | Could be ignored, | 202 | | | | potentially useful | 203 | | | | for informing the | 204 | | | | end user. | 205 | +------------------------+----------+--------------------+ 206 | | Partner stopped typing | N/A | Could be ignored, | 207 | | | | potentially useful | 208 | | | | for informing the | 209 | | | | end user. | 210 | +------------------------+----------+--------------------+ 211 | | Got message | Message | N/A | 212 | +------------------------+----------+--------------------+ 213 | | Got server notice | Notice | N/A | 214 | +------------------------+----------+--------------------+ 215 | 216 | | 217 | 218 | .. _`The Protocol: Python-Omegle`: 219 | 220 | The Protocol: Python-Omegle 221 | --------------------------- 222 | 223 | Of the four Omegle text chat types, Python-Omegle supports three. Each chat 224 | type is a class on its own. Each class instance can dispatch chats: start 225 | them, handle events, interact with the other party, and ultimately disconnect. 226 | 227 | Abstract chat 228 | ~~~~~~~~~~~~~ 229 | 230 | Location: `python_omegle/_abstractchat.py`_ 231 | 232 | The ``_AbstractChat`` class is an abstract type, of which other chats derive. 233 | It defines a number of attributes and methods which are the same across all 234 | chats: 235 | 236 | * An event queue, which holds the events in the order they were retrieved; 237 | * A way to start a new chat, using ``_AbstractChat.start()``; 238 | * A way to get an event from the queue, using ``_AbstractChat.get_event()``; 239 | * A way to send a message, using ``_AbstractChat.send()``; 240 | * A way to disconnect, using ``_AbstractChat.disconnect()``; 241 | * A way to tell the server the user started typing, using 242 | ``_AbstractChat.start_typing()``; 243 | * A way to tell the server the user stopped typing, using 244 | ``_AbstractChat.stop_typing()``; 245 | * A way to get and set the language option, which allows client to filter 246 | chats by language, using ``_AbstractChat.language``; 247 | * Other attributes intended for internal use. 248 | 249 | The API for these methods is identical for all chat types. The only 250 | exception to that rule is ``_AbstractChat.get_event()``. 251 | ``_AbstractChat.get_event()`` always returns an (event, argument) tuple, 252 | the first element of which is an enum value indicating the type of event, 253 | and the second element of which is a string, list or ``None``, depending on 254 | the argument included in the HTTP response. 255 | 256 | If the method is called and the event queue contains at least one event, 257 | the first event is returned. If the method is called, and the event queue 258 | is empty, the operation blocks until at least one event is retrieved. The 259 | event(s) are then put onto the queue, and a single event is returned from 260 | the queue. 261 | 262 | Chat event enum 263 | ~~~~~~~~~~~~~~~ 264 | 265 | The enum values for the various event types are part of the ``ChatEvent`` 266 | enum, located in `python_omegle/chatevent.py`_. The full list of enum values 267 | is: 268 | 269 | * ``ChatEvent.CHAT_READY`` 270 | * ``ChatEvent.CHAT_WAITING`` 271 | * ``ChatEvent.CHAT_ENDED`` 272 | * ``ChatEvent.GOT_SERVER_NOTICE`` 273 | * ``ChatEvent.GOT_MESSAGE`` 274 | * ``ChatEvent.PARTNER_STARTED_TYPING`` 275 | * ``ChatEvent.PARTNER_STOPPED_TYPING`` 276 | 277 | Exceptions 278 | ~~~~~~~~~~ 279 | 280 | Location: `python_omegle/exceptions.py`_ 281 | 282 | Currently, only one custom exception is defined: ``PythonOmegleException``. 283 | Trivial problems, such as passing an incorrect argument type or value, will 284 | raise a ``TypeError`` or ``ValueError``. ``PythonOmegleException`` is only 285 | raised for problems with the server response, or when a ReCAPTCHA check must 286 | be completed. 287 | 288 | Random chat 289 | ~~~~~~~~~~~ 290 | 291 | Location: `python_omegle/randomchat.py`_ 292 | 293 | A random chat is essentially a match with a randomly picked stranger. It is the 294 | least complicated of the classes: ``RandomChat.__init__()`` only accepts one 295 | argument: the language to converse in. This argument defaults to the string 296 | 'en' (= English). Note that many languages are supported, but you must supply a 297 | language code defined in `ISO 639-1`_. The language can be changed at runtime, 298 | but the change will only be applied the next time a chat is started. 299 | 300 | We now know enough about Python and Python-Omegle to write our first program. 301 | This simple script creates a chat instance, connects to a stranger, sends a 302 | message, and disconnects. 303 | 304 | .. code:: python 305 | 306 | from python_omegle.randomchat import RandomChat 307 | from python_omegle.chatevent import ChatEvent 308 | 309 | chat = RandomChat() 310 | chat.start() 311 | while True: 312 | event, argument = chat.get_event() 313 | if event == ChatEvent.CHAT_READY: 314 | break 315 | # Connected, let's send the message 316 | chat.send("Goodbye") 317 | chat.disconnect() 318 | 319 | If you run the script, chances are you're met with a ``PythonOmegleException``: 320 | 321 | .. code:: 322 | 323 | Traceback (most recent call last): 324 | ... 325 | raise PythonOmegleException("ReCAPTCHA check required.") 326 | python_omegle.exceptions.PythonOmegleException: ReCAPTCHA check required. 327 | 328 | To prevent people from starting automated chats, Omegle uses the 329 | `ReCAPTCHA protocol`_. After a period of inactivity on the website, the user 330 | must pass a ReCAPTCHA test. The user is then free to start chats, until they 331 | hit a cap. During the cooldown period, every time a new chat is started, a 332 | ReCAPTCHA must be solved. There are no plans to incorporate a workaround for 333 | the cap in this library. To be able to follow along with the tutorial, please 334 | go to the Omegle website, start a chat, pass a ReCAPTCHA test successfully, 335 | and then return. 336 | 337 | Want to be really annoying and just watch other people wait for you to say 338 | something? 339 | 340 | .. code:: python 341 | 342 | from python_omegle.randomchat import RandomChat 343 | from python_omegle.chatevent import ChatEvent 344 | 345 | chat = RandomChat() 346 | chat.start() 347 | 348 | while True: 349 | event, argument = chat.get_event() 350 | if event == ChatEvent.CHAT_READY: 351 | print("- Connected to a partner -") 352 | break 353 | elif event == ChatEvent.CHAT_WAITING: 354 | print("- Waiting for a partner -") 355 | 356 | while True: 357 | event, argument = chat.get_event() 358 | if event == ChatEvent.GOT_MESSAGE: 359 | message = argument 360 | print("Partner: {}".format(message)) 361 | elif event == ChatEvent.CHAT_ENDED: 362 | print("- Chat ended - ") 363 | break 364 | 365 | ---- 366 | 367 | Quick tip: The __init__.py file for Python-Omegle contains all the classes 368 | that are part of the API. There is no need to import from the modules 369 | themselves. For the remainder of this tutorial, objects will be imported from 370 | `python_omegle/__init__.py`_. 371 | 372 | ---- 373 | 374 | The examples are neat, but don't utilize the full power of the 375 | ``RandomChat`` class. This script does the same thing as above, in a loop, 376 | with additional event handling: 377 | 378 | .. code:: python 379 | 380 | from python_omegle import RandomChat 381 | from python_omegle import ChatEvent 382 | 383 | 384 | def chat_loop(chat): 385 | while True: 386 | # Start a new chat every time the old one ends 387 | print("- Starting chat -") 388 | chat.start() 389 | while True: 390 | event, argument = chat.get_event() 391 | if event == ChatEvent.CHAT_WAITING: 392 | print("- Waiting for a partner -") 393 | elif event == ChatEvent.CHAT_READY: 394 | print("- Connected to a partner -") 395 | break 396 | # Connected to a partner 397 | while True: 398 | event, argument = chat.get_event() 399 | if event == ChatEvent.GOT_SERVER_NOTICE: 400 | notice = argument 401 | print("- Server notice: {} -".format(notice)) 402 | elif event == ChatEvent.PARTNER_STARTED_TYPING: 403 | print("- Partner started typing -") 404 | elif event == ChatEvent.PARTNER_STOPPED_TYPING: 405 | print("- Partner stopped typing -") 406 | elif event == ChatEvent.GOT_MESSAGE: 407 | message = argument 408 | print("Partner: {}".format(message)) 409 | elif event == ChatEvent.CHAT_ENDED: 410 | print("- Chat ended -") 411 | break 412 | 413 | if __name__ == "__main__": 414 | chat = RandomChat() 415 | chat_loop(chat=chat) 416 | 417 | 418 | Interest-based chat 419 | ~~~~~~~~~~~~~~~~~~~ 420 | 421 | Tired of getting matched with bots? Picking the right topics minimizes the 422 | chance of connecting to bots. This should look familiar now: 423 | 424 | .. code:: python 425 | 426 | from python_omegle import InterestsChat 427 | from python_omegle import ChatEvent 428 | 429 | # We'll talk about foo and/or bar 430 | chat = InterestsChat(["foo", "bar"]) 431 | chat.start() 432 | while True: 433 | event, argument = chat.get_event() 434 | if event == ChatEvent.CHAT_READY: 435 | common_interests = argument 436 | print("- Connected, common interests: {} -".format(*common_interests)) 437 | break 438 | chat.send("Goodbye") 439 | chat.disconnect() 440 | 441 | One thing to note is that the argument for ``ChatEvent.CHAT_READY`` is not 442 | ``None``, as was the case with ``RandomChat``, but is a list of common 443 | interests. This list may contain all interests, or be a subset of what was 444 | passed to the constructor. 445 | 446 | Just like with ``RandomChat``, the language can be set in the 447 | constructor: 448 | 449 | .. code:: python 450 | 451 | # We'll talk about foo and/or bar in Spanish 452 | chat = InterestsChat(["foo", "bar"], language="es") 453 | # ... 454 | 455 | ... or modified dynamically using ``InterestsChat.language``: 456 | 457 | .. code:: python 458 | 459 | # We'll talk about foo and/or bar in Spanish 460 | chat = InterestsChat(["foo", "bar"], language="es") 461 | # Actually, we'll go with French 462 | chat.language = "fr" 463 | # ... 464 | 465 | And the same goes for the interests (``InterestsChat.interests``): 466 | 467 | .. code:: python 468 | 469 | # We'll talk about foo and/or bar 470 | chat = InterestsChat(["foo", "bar"]) 471 | # quux is interesting too 472 | chat.interests.append("quux") 473 | 474 | 475 | Spyee chat 476 | ~~~~~~~~~~ 477 | 478 | Lastly, there's spyee mode: 479 | 480 | .. code:: python 481 | 482 | from python_omegle import SpyeeChat 483 | from python_omegle import ChatEvent 484 | 485 | chat = SpyeeChat() 486 | chat.start() 487 | while True: 488 | event, argument = chat.get_event() 489 | if event == ChatEvent.CHAT_READY: 490 | question = argument 491 | print("- Discuss this question: {} -".format(question)) 492 | break 493 | chat.send("Goodbye") 494 | chat.disconnect() 495 | 496 | Note how the argument for ``ChatEvent.CHAT_READY`` is now the question posed by 497 | the spy. 498 | 499 | | 500 | 501 | Issues 502 | ------ 503 | 504 | 1. Two languages which are supported on the Omegle website but not defined in 505 | ISO 639-1 are Cebuano and Filipino, which are defined in `ISO 639-2`_ instead, 506 | and are the only supported languages with a three letter language code. Frisian 507 | and Hmong are not accepted, even though they're supported on the Omegle website 508 | and are part of ISO 639-1, because their language code is ambiguous. 509 | 510 | 2. After you receive a ``ChatEvent.PARTNER_STARTED_TYPING`` event, it's 511 | very likely that you will not receive a ``ChatEvent.PARTNER_STOPPED_TYPING`` 512 | event when your partner sends you a message. That's an intentional design choice 513 | in the Omegle protocol, not a bug. Because this may be seen as a quirk of the 514 | API, it's on the todo list to fix this and mock a 515 | ``ChatEvent.PARTNER_STOPPED_TYPING`` encounter. 516 | 517 | | 518 | 519 | Reference 520 | --------- 521 | 522 | XXX 523 | 524 | | 525 | 526 | ---- 527 | 528 | .. [1] `Welcome to the omegle blog!`_ (archive.org) 529 | 530 | .. [2] The URLs listed below have an implicit base URL of frontN.omegle.com (see the 531 | `Requests table`_ and [4]_). For example, the *Send* URL is actually 532 | frontN.omegle.com/send. 533 | 534 | .. [3] The server URL (listed as 'omegle.com') is actually any of 32 addresses. The URL is 535 | frontN.omegle.com, where N is a number 1 through 32. 536 | 537 | .. [4] There are other events, but they can be ignored, or are dealt with by Python-Omegle 538 | in an appropiate manner. For example, 'recaptchaRequired' is not listed, because 539 | when encountered, an exception is raised. It wouldn't make sense to expose these 540 | types of events. 541 | 542 | .. [5] The argument for the 'Connected' event depends on the type of chat. This event will 543 | be explained in detail in the respective sections. 544 | 545 | 546 | .. _`python_omegle/_abstractchat.py`: ../python_omegle/_abstractchat.py 547 | 548 | .. _`python_omegle/chatevent.py`: ../python_omegle/chatevent.py 549 | 550 | .. _`python_omegle/exceptions.py`: ../python_omegle/exceptions.py 551 | 552 | .. _`python_omegle/randomchat.py`: ../python_omegle/randomchat.py 553 | 554 | .. _`python_omegle/__init__.py`: ../python_omegle/__init__.py 555 | 556 | .. _`ISO 639-1`: https://en.wikipedia.org/wiki/ISO_639-1 557 | 558 | .. _`ISO 639-2`: https://en.wikipedia.org/wiki/ISO_639-2 559 | 560 | .. _`ReCAPTCHA protocol`: https://google.com/recaptcha/ 561 | 562 | .. _`Welcome to the omegle blog!`: https://web.archive.org/web/20090403052716/http://omegler.blogspot.com/2009/03/welcome-to-omegle-blog.html 563 | --------------------------------------------------------------------------------