Ouija
=====
Python relay/proxy server and library to build reliable encrypted TCP/UDP tunnels with entropy control for TCP traffic
|pypi|
.. |pypi| image:: https://badge.fury.io/py/ouija.svg
:target: https://badge.fury.io/py/ouija
:alt: pypi version
Features
--------
* Easy to install, configure and use
* TCP/UDP tunneling
* Pluggable traffic ciphers
* Pluggable traffic entropy control
Hides TCP traffic in encrypted TCP/UDP tunnel between relay and proxy servers
.. image:: https://raw.githubusercontent.com/neurophant/ouija/main/ouija.png
:alt: TCP/UDP tunneling
:width: 800
Requirements
------------
* Python 3.11
* pbjson 1.18.0
* cryptography 41.0.2
* numpy 1.25.2
Install
-------
.. code-block:: bash
python3.11 -m venv .env
source .env/bin/activate
pip install ouija
Usage
-----
Generate cipher_key/token secrets:
.. code-block:: bash
ouija_secret
Run relay/proxy server:
.. code-block:: bash
ouija <config.json>
tcp-relay.json - TCP relay server - HTTP/HTTPS proxy server interface with TCP connectors:
.. code-block:: json
{
"protocol": "TCP",
"mode": "RELAY",
"debug": true,
"monitor": true,
"relay_host": "127.0.0.1",
"relay_port": 9000,
"proxy_host": "127.0.0.1",
"proxy_port": 50000,
"cipher_key": "bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=",
"entropy_rate": 5,
"token": "395f249c-343a-4f92-9129-68c6d83b5f55",
"serving_timeout": 20.0,
"tcp_buffer": 1024,
"tcp_timeout": 1.0,
"message_timeout": 5.0
}
tcp-proxy.json - TCP-relayed proxy server:
.. code-block:: json
{
"protocol": "TCP",
"mode": "PROXY",
"debug": true,
"monitor": true,
"proxy_host": "0.0.0.0",
"proxy_port": 50000,
"cipher_key": "bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=",
"entropy_rate": 5,
"token": "395f249c-343a-4f92-9129-68c6d83b5f55",
"serving_timeout": 20.0,
"tcp_buffer": 1024,
"tcp_timeout": 1.0,
"message_timeout": 5.0
}
udp-relay.json - UDP relay server - HTTP/HTTPS proxy server interface with UDP connectors:
.. code-block:: json
{
"protocol": "UDP",
"mode": "RELAY",
"debug": true,
"monitor": true,
"relay_host": "127.0.0.1",
"relay_port": 9000,
"proxy_host": "127.0.0.1",
"proxy_port": 50000,
"cipher_key": "bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=",
"entropy_rate": 5,
"token": "395f249c-343a-4f92-9129-68c6d83b5f55",
"serving_timeout": 20.0,
"tcp_buffer": 1024,
"tcp_timeout": 1.0,
"udp_min_payload": 512,
"udp_max_payload": 1024,
"udp_timeout": 2.0,
"udp_retries": 5,
"udp_capacity": 10000,
"udp_resend_sleep": 0.25
}
udp-proxy.json - UDP-relayed proxy server:
.. code-block:: json
{
"protocol": "UDP",
"mode": "PROXY",
"debug": true,
"monitor": true,
"proxy_host": "0.0.0.0",
"proxy_port": 50000,
"cipher_key": "bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=",
"entropy_rate": 5,
"token": "395f249c-343a-4f92-9129-68c6d83b5f55",
"serving_timeout": 20.0,
"tcp_buffer": 1024,
"tcp_timeout": 1.0,
"udp_min_payload": 512,
"udp_max_payload": 1024,
"udp_timeout": 2.0,
"udp_retries": 5,
"udp_capacity": 10000,
"udp_resend_sleep": 0.25
}
Relay and proxy setup configuration with supervisord - `ouija-config <https://github.com/neurophant/ouija-config>`_
Cipher and entropy
------------------
* cipher_key - FernetCipher key - use ouija_secret to generate key
* entropy_rate - SimpleEntropy rate, when rate=N every Nth byte will be generated and payload size will increase, rate=5 means 20% traffic overhead
Protocols
---------
* Stream - TCP
* Datagram - UDP
Entities
--------
* Cipher - cipher implementation - FernetCipher out of the box
* Entropy - entropy control implementation - SimpleEntropy out of the box
* Tuning - relay/proxy and connector/link interaction settings
* Relay - HTTPS proxy server interface
* Connector - relay connector, which communicates with proxy link
* Proxy - proxy server, which gets requests from relay and sends back responses from remote servers
* Link - proxy link with relay connector
Tuning - TCP
------------
* cipher - cipher instance, if None then no encryption will be applied
* entropy - entropy instance, if None then no entropy control will be applied
* token - your secret token - UUID4 or anything else - use ouija_secret to generate token
* serving_timeout - timeout for serve/resend workers, 2X for handlers, seconds
* tcp_buffer - TCP buffer size, bytes
* tcp_timeout - TCP awaiting timeout, seconds
* message_timeout - TCP service message timeout, seconds
Tuning - UDP
------------
* cipher - cipher instance, if None then no encryption will be applied
* entropy - entropy instance, if None then no entropy control will be applied
* token - your secret token - UUID4 or anything else - use ouija_secret to generate token
* serving_timeout - timeout for serve/resend workers, 2X for handlers, seconds
* tcp_buffer - TCP buffer size, bytes
* tcp_timeout - TCP awaiting timeout, seconds
* udp_min_payload - UDP min payload size, bytes
* udp_max_payload - UDP max payload size, bytes
* udp_timeout - UDP awaiting timeout, seconds
* udp_retries - UDP max retry count per interaction
* udp_capacity - UDP send/receive buffer capacity - max packet count
* udp_resend_sleep - UDP resend sleep between retries, seconds
Library usage
-------------
stream-relay.py - TCP relay server - HTTP/HTTPS proxy server interface with TCP connectors:
.. code-block:: python
import asyncio
import logging
from ouija import StreamRelay as Relay, StreamTuning as Tuning, Telemetry, SimpleEntropy, FernetCipher
async def main() -> None:
tuning = Tuning(
cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),
entropy=SimpleEntropy(rate=5),
token='395f249c-343a-4f92-9129-68c6d83b5f55',
serving_timeout=20.0,
tcp_buffer=1024,
tcp_timeout=1.0,
message_timeout=5.0,
)
relay = Relay(
telemetry=Telemetry(),
tuning=tuning,
relay_host='127.0.0.1',
relay_port=9000,
proxy_host='127.0.0.1',
proxy_port=50000,
)
asyncio.create_task(relay.debug())
await relay.serve()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_forever()
stream-proxy.py - TCP-relayed proxy server:
.. code-block:: python
import asyncio
import logging
from ouija import StreamProxy as Proxy, Telemetry, StreamTuning as Tuning, SimpleEntropy, FernetCipher
async def main() -> None:
tuning = Tuning(
cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),
entropy=SimpleEntropy(rate=5),
token='395f249c-343a-4f92-9129-68c6d83b5f55',
serving_timeout=20.0,
tcp_buffer=1024,
tcp_timeout=1.0,
message_timeout=5.0,
)
proxy = Proxy(
telemetry=Telemetry(),
tuning=tuning,
proxy_host='0.0.0.0',
proxy_port=50000,
)
asyncio.create_task(proxy.debug())
await proxy.serve()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_forever()
datagram-relay.py - UDP relay server - HTTPS proxy server interface with UDP connectors:
.. code-block:: python
import asyncio
import logging
from ouija import DatagramRelay as Relay, DatagramTuning as Tuning, Telemetry, SimpleEntropy, FernetCipher
async def main() -> None:
tuning = Tuning(
cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),
entropy=SimpleEntropy(rate=5),
token='395f249c-343a-4f92-9129-68c6d83b5f55',
serving_timeout=20.0,
tcp_buffer=1024,
tcp_timeout=1.0,
udp_min_payload=512,
udp_max_payload=1024,
udp_timeout=2.0,
udp_retries=5,
udp_capacity=10000,
udp_resend_sleep=0.25,
)
relay = Relay(
telemetry=Telemetry(),
tuning=tuning,
relay_host='127.0.0.1',
relay_port=9000,
proxy_host='127.0.0.1',
proxy_port=50000,
)
asyncio.create_task(relay.debug())
await relay.serve()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_forever()
datagram-proxy.py - UDP-relayed proxy server:
.. code-block:: python
import asyncio
import logging
from ouija import DatagramProxy as Proxy, Telemetry, DatagramTuning as Tuning, SimpleEntropy, FernetCipher
async def main() -> None:
tuning = Tuning(
cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),
entropy=SimpleEntropy(rate=5),
token='395f249c-343a-4f92-9129-68c6d83b5f55',
serving_timeout=20.0,
tcp_buffer=1024,
tcp_timeout=1.0,
udp_min_payload=512,
udp_max_payload=1024,
udp_timeout=2.0,
udp_retries=5,
udp_capacity=10000,
udp_resend_sleep=0.25,
)
proxy = Proxy(
telemetry=Telemetry(),
tuning=tuning,
proxy_host='0.0.0.0',
proxy_port=50000,
)
asyncio.create_task(proxy.debug())
await proxy.serve()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_forever()
Tests
-----
.. code-block:: bash
pytest --cov-report html:htmlcov --cov=ouija tests/
Raw data
{
"_id": null,
"home_page": "https://github.com/neurophant/ouija/",
"name": "ouija",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "asyncio http https tcp udp proxy tunnel relay network encrypted cipher security censorship entropy",
"author": "Anton Smolin",
"author_email": "smolin.anton@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/46/73/00c4798ec9623919dd61e1a7bffc32ca3b03faabdde8d48a5552928d7952/ouija-1.3.1.tar.gz",
"platform": null,
"description": "Ouija\n=====\n\nPython relay/proxy server and library to build reliable encrypted TCP/UDP tunnels with entropy control for TCP traffic\n\n|pypi|\n\n.. |pypi| image:: https://badge.fury.io/py/ouija.svg\n :target: https://badge.fury.io/py/ouija\n :alt: pypi version\n\nFeatures\n--------\n\n* Easy to install, configure and use\n* TCP/UDP tunneling\n* Pluggable traffic ciphers\n* Pluggable traffic entropy control\n\nHides TCP traffic in encrypted TCP/UDP tunnel between relay and proxy servers\n\n.. image:: https://raw.githubusercontent.com/neurophant/ouija/main/ouija.png\n :alt: TCP/UDP tunneling\n :width: 800\n\nRequirements\n------------\n\n* Python 3.11\n* pbjson 1.18.0\n* cryptography 41.0.2\n* numpy 1.25.2\n\nInstall\n-------\n\n.. code-block:: bash\n\n python3.11 -m venv .env\n source .env/bin/activate\n pip install ouija\n\nUsage\n-----\n\nGenerate cipher_key/token secrets:\n\n.. code-block:: bash\n\n ouija_secret\n\nRun relay/proxy server:\n\n.. code-block:: bash\n\n ouija <config.json>\n\ntcp-relay.json - TCP relay server - HTTP/HTTPS proxy server interface with TCP connectors:\n\n.. code-block:: json\n\n {\n \"protocol\": \"TCP\",\n \"mode\": \"RELAY\",\n \"debug\": true,\n \"monitor\": true,\n \"relay_host\": \"127.0.0.1\",\n \"relay_port\": 9000,\n \"proxy_host\": \"127.0.0.1\",\n \"proxy_port\": 50000,\n \"cipher_key\": \"bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=\",\n \"entropy_rate\": 5,\n \"token\": \"395f249c-343a-4f92-9129-68c6d83b5f55\",\n \"serving_timeout\": 20.0,\n \"tcp_buffer\": 1024,\n \"tcp_timeout\": 1.0,\n \"message_timeout\": 5.0\n }\n\ntcp-proxy.json - TCP-relayed proxy server:\n\n.. code-block:: json\n\n {\n \"protocol\": \"TCP\",\n \"mode\": \"PROXY\",\n \"debug\": true,\n \"monitor\": true,\n \"proxy_host\": \"0.0.0.0\",\n \"proxy_port\": 50000,\n \"cipher_key\": \"bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=\",\n \"entropy_rate\": 5,\n \"token\": \"395f249c-343a-4f92-9129-68c6d83b5f55\",\n \"serving_timeout\": 20.0,\n \"tcp_buffer\": 1024,\n \"tcp_timeout\": 1.0,\n \"message_timeout\": 5.0\n }\n\nudp-relay.json - UDP relay server - HTTP/HTTPS proxy server interface with UDP connectors:\n\n.. code-block:: json\n\n {\n \"protocol\": \"UDP\",\n \"mode\": \"RELAY\",\n \"debug\": true,\n \"monitor\": true,\n \"relay_host\": \"127.0.0.1\",\n \"relay_port\": 9000,\n \"proxy_host\": \"127.0.0.1\",\n \"proxy_port\": 50000,\n \"cipher_key\": \"bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=\",\n \"entropy_rate\": 5,\n \"token\": \"395f249c-343a-4f92-9129-68c6d83b5f55\",\n \"serving_timeout\": 20.0,\n \"tcp_buffer\": 1024,\n \"tcp_timeout\": 1.0,\n \"udp_min_payload\": 512,\n \"udp_max_payload\": 1024,\n \"udp_timeout\": 2.0,\n \"udp_retries\": 5,\n \"udp_capacity\": 10000,\n \"udp_resend_sleep\": 0.25\n }\n\nudp-proxy.json - UDP-relayed proxy server:\n\n.. code-block:: json\n\n {\n \"protocol\": \"UDP\",\n \"mode\": \"PROXY\",\n \"debug\": true,\n \"monitor\": true,\n \"proxy_host\": \"0.0.0.0\",\n \"proxy_port\": 50000,\n \"cipher_key\": \"bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI=\",\n \"entropy_rate\": 5,\n \"token\": \"395f249c-343a-4f92-9129-68c6d83b5f55\",\n \"serving_timeout\": 20.0,\n \"tcp_buffer\": 1024,\n \"tcp_timeout\": 1.0,\n \"udp_min_payload\": 512,\n \"udp_max_payload\": 1024,\n \"udp_timeout\": 2.0,\n \"udp_retries\": 5,\n \"udp_capacity\": 10000,\n \"udp_resend_sleep\": 0.25\n }\n\nRelay and proxy setup configuration with supervisord - `ouija-config <https://github.com/neurophant/ouija-config>`_\n\nCipher and entropy\n------------------\n\n* cipher_key - FernetCipher key - use ouija_secret to generate key\n* entropy_rate - SimpleEntropy rate, when rate=N every Nth byte will be generated and payload size will increase, rate=5 means 20% traffic overhead\n\nProtocols\n---------\n\n* Stream - TCP\n* Datagram - UDP\n\nEntities\n--------\n\n* Cipher - cipher implementation - FernetCipher out of the box\n* Entropy - entropy control implementation - SimpleEntropy out of the box\n* Tuning - relay/proxy and connector/link interaction settings\n* Relay - HTTPS proxy server interface\n* Connector - relay connector, which communicates with proxy link\n* Proxy - proxy server, which gets requests from relay and sends back responses from remote servers\n* Link - proxy link with relay connector\n\nTuning - TCP\n------------\n\n* cipher - cipher instance, if None then no encryption will be applied\n* entropy - entropy instance, if None then no entropy control will be applied\n* token - your secret token - UUID4 or anything else - use ouija_secret to generate token\n* serving_timeout - timeout for serve/resend workers, 2X for handlers, seconds\n* tcp_buffer - TCP buffer size, bytes\n* tcp_timeout - TCP awaiting timeout, seconds\n* message_timeout - TCP service message timeout, seconds\n\nTuning - UDP\n------------\n\n* cipher - cipher instance, if None then no encryption will be applied\n* entropy - entropy instance, if None then no entropy control will be applied\n* token - your secret token - UUID4 or anything else - use ouija_secret to generate token\n* serving_timeout - timeout for serve/resend workers, 2X for handlers, seconds\n* tcp_buffer - TCP buffer size, bytes\n* tcp_timeout - TCP awaiting timeout, seconds\n* udp_min_payload - UDP min payload size, bytes\n* udp_max_payload - UDP max payload size, bytes\n* udp_timeout - UDP awaiting timeout, seconds\n* udp_retries - UDP max retry count per interaction\n* udp_capacity - UDP send/receive buffer capacity - max packet count\n* udp_resend_sleep - UDP resend sleep between retries, seconds\n\nLibrary usage\n-------------\n\nstream-relay.py - TCP relay server - HTTP/HTTPS proxy server interface with TCP connectors:\n\n.. code-block:: python\n\n import asyncio\n import logging\n\n from ouija import StreamRelay as Relay, StreamTuning as Tuning, Telemetry, SimpleEntropy, FernetCipher\n\n\n async def main() -> None:\n tuning = Tuning(\n cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),\n entropy=SimpleEntropy(rate=5),\n token='395f249c-343a-4f92-9129-68c6d83b5f55',\n serving_timeout=20.0,\n tcp_buffer=1024,\n tcp_timeout=1.0,\n message_timeout=5.0,\n )\n relay = Relay(\n telemetry=Telemetry(),\n tuning=tuning,\n relay_host='127.0.0.1',\n relay_port=9000,\n proxy_host='127.0.0.1',\n proxy_port=50000,\n )\n asyncio.create_task(relay.debug())\n await relay.serve()\n\n\n if __name__ == '__main__':\n loop = asyncio.get_event_loop()\n loop.run_until_complete(main())\n loop.run_forever()\n\nstream-proxy.py - TCP-relayed proxy server:\n\n.. code-block:: python\n\n import asyncio\n import logging\n\n from ouija import StreamProxy as Proxy, Telemetry, StreamTuning as Tuning, SimpleEntropy, FernetCipher\n\n\n async def main() -> None:\n tuning = Tuning(\n cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),\n entropy=SimpleEntropy(rate=5),\n token='395f249c-343a-4f92-9129-68c6d83b5f55',\n serving_timeout=20.0,\n tcp_buffer=1024,\n tcp_timeout=1.0,\n message_timeout=5.0,\n )\n proxy = Proxy(\n telemetry=Telemetry(),\n tuning=tuning,\n proxy_host='0.0.0.0',\n proxy_port=50000,\n )\n asyncio.create_task(proxy.debug())\n await proxy.serve()\n\n\n if __name__ == '__main__':\n loop = asyncio.get_event_loop()\n loop.run_until_complete(main())\n loop.run_forever()\n\ndatagram-relay.py - UDP relay server - HTTPS proxy server interface with UDP connectors:\n\n.. code-block:: python\n\n import asyncio\n import logging\n\n from ouija import DatagramRelay as Relay, DatagramTuning as Tuning, Telemetry, SimpleEntropy, FernetCipher\n\n\n async def main() -> None:\n tuning = Tuning(\n cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),\n entropy=SimpleEntropy(rate=5),\n token='395f249c-343a-4f92-9129-68c6d83b5f55',\n serving_timeout=20.0,\n tcp_buffer=1024,\n tcp_timeout=1.0,\n udp_min_payload=512,\n udp_max_payload=1024,\n udp_timeout=2.0,\n udp_retries=5,\n udp_capacity=10000,\n udp_resend_sleep=0.25,\n )\n relay = Relay(\n telemetry=Telemetry(),\n tuning=tuning,\n relay_host='127.0.0.1',\n relay_port=9000,\n proxy_host='127.0.0.1',\n proxy_port=50000,\n )\n asyncio.create_task(relay.debug())\n await relay.serve()\n\n\n if __name__ == '__main__':\n loop = asyncio.get_event_loop()\n loop.run_until_complete(main())\n loop.run_forever()\n\ndatagram-proxy.py - UDP-relayed proxy server:\n\n.. code-block:: python\n\n import asyncio\n import logging\n\n from ouija import DatagramProxy as Proxy, Telemetry, DatagramTuning as Tuning, SimpleEntropy, FernetCipher\n\n\n async def main() -> None:\n tuning = Tuning(\n cipher=FernetCipher(key='bdDmN4VexpDvTrs6gw8xTzaFvIBobFg1Cx2McFB1RmI='),\n entropy=SimpleEntropy(rate=5),\n token='395f249c-343a-4f92-9129-68c6d83b5f55',\n serving_timeout=20.0,\n tcp_buffer=1024,\n tcp_timeout=1.0,\n udp_min_payload=512,\n udp_max_payload=1024,\n udp_timeout=2.0,\n udp_retries=5,\n udp_capacity=10000,\n udp_resend_sleep=0.25,\n )\n proxy = Proxy(\n telemetry=Telemetry(),\n tuning=tuning,\n proxy_host='0.0.0.0',\n proxy_port=50000,\n )\n asyncio.create_task(proxy.debug())\n await proxy.serve()\n\n\n if __name__ == '__main__':\n loop = asyncio.get_event_loop()\n loop.run_until_complete(main())\n loop.run_forever()\n\nTests\n-----\n\n.. code-block:: bash\n\n pytest --cov-report html:htmlcov --cov=ouija tests/\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python relay/proxy server and library to build reliable encrypted TCP/UDP tunnels with entropy control for TCP traffic",
"version": "1.3.1",
"project_urls": {
"Homepage": "https://github.com/neurophant/ouija/"
},
"split_keywords": [
"asyncio",
"http",
"https",
"tcp",
"udp",
"proxy",
"tunnel",
"relay",
"network",
"encrypted",
"cipher",
"security",
"censorship",
"entropy"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2358f59c78bff705b071cfb659bb0b268d9ac1ddedbbb6695142d37386c12c72",
"md5": "9a381602531e512c1f8f192b8d2294a5",
"sha256": "ac8d9c87700f581d4867131137da0cd4f6553f53fd1946364d36e1d018b917c1"
},
"downloads": -1,
"filename": "ouija-1.3.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "9a381602531e512c1f8f192b8d2294a5",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 18944,
"upload_time": "2023-08-26T16:48:10",
"upload_time_iso_8601": "2023-08-26T16:48:10.126384Z",
"url": "https://files.pythonhosted.org/packages/23/58/f59c78bff705b071cfb659bb0b268d9ac1ddedbbb6695142d37386c12c72/ouija-1.3.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "467300c4798ec9623919dd61e1a7bffc32ca3b03faabdde8d48a5552928d7952",
"md5": "cc4cf5ca46a8252f8f166d5cc8559100",
"sha256": "8277b6724364377a4bafd108c3c92953ce4a3c21fcee1595460515f81d395324"
},
"downloads": -1,
"filename": "ouija-1.3.1.tar.gz",
"has_sig": false,
"md5_digest": "cc4cf5ca46a8252f8f166d5cc8559100",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 22595,
"upload_time": "2023-08-26T16:48:12",
"upload_time_iso_8601": "2023-08-26T16:48:12.181189Z",
"url": "https://files.pythonhosted.org/packages/46/73/00c4798ec9623919dd61e1a7bffc32ca3b03faabdde8d48a5552928d7952/ouija-1.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-08-26 16:48:12",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "neurophant",
"github_project": "ouija",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [],
"lcname": "ouija"
}