├── requirements.txt ├── setup.cfg ├── .travis.yml ├── .gitignore ├── LICENSE ├── README.md ├── tests.py ├── setup.py └── odoa.py /requirements.txt: -------------------------------------------------------------------------------- 1 | httpx==0.23.0 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.7" 5 | - "3.8" 6 | 7 | install: 8 | - pip install -r requirements.txt 9 | - pip install coverage 10 | - pip install codecov 11 | 12 | # Command to run tests 13 | script: 14 | - coverage run tests.py 15 | 16 | # Run coverage.io 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) 19 | - codecov 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | venv 5 | .vscode 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Pycharm 11 | .idea/* 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Adiyat Mubarak 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python Quran ODOA 2 | ================= 3 | [![Build Status](https://travis-ci.org/Keda87/python-quran-odoa.svg?branch=master)](https://travis-ci.org/Keda87/python-quran-odoa) 4 | [![pypi](https://badge.fury.io/py/python-quran-odoa.svg)](https://badge.fury.io/py/python-quran-odoa) 5 | [![codecov](https://codecov.io/gh/Keda87/python-quran-odoa/branch/master/graph/badge.svg)](https://codecov.io/gh/Keda87/python-quran-odoa) 6 | 7 | Python library to display random ayah within quran surah including the translation. 8 | Currently supports only Bahasa Indonesia and English. 9 | 10 | This library is part of supporting ODOA (One Day One Ayat) campaign and using datasource from [quranjson](https://github.com/semarketir/quranjson). 11 | 12 | 13 | Prerequisite: 14 | ------------- 15 | - Python v3.6.+ 16 | 17 | Installation: 18 | ------------- 19 | 20 | **Pip:** 21 | 22 | ```bash 23 | $ pip install python-quran-odoa 24 | ``` 25 | 26 | **Usage:** 27 | 28 | ```python 29 | from odoa import ODOA 30 | 31 | o = ODOA() 32 | 33 | # by default the translation using bahasa indonesia, 34 | # pass `lang='en'` if you want english translation. 35 | surah = await o.get_random_surah() # odoa.get_random_surah(lang='en') 36 | 37 | surah.ayah 38 | surah.desc 39 | surah.translate 40 | surah.sound 41 | ``` 42 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import unittest 3 | from unittest import mock 4 | 5 | from odoa import ODOA, ODOAException 6 | 7 | LOOP = asyncio.get_event_loop() 8 | 9 | 10 | class ODOATest(unittest.TestCase): 11 | 12 | def setUp(self) -> None: 13 | self.odoa = ODOA() 14 | 15 | def test_get_surah(self): 16 | coro = self.odoa.get_random_surah() 17 | surah = LOOP.run_until_complete(coro) 18 | self.assertIsNotNone(surah) 19 | 20 | def test_get_surah_english(self): 21 | coro = self.odoa.get_random_surah(lang='en') 22 | surah = LOOP.run_until_complete(coro) 23 | self.assertIsNotNone(surah) 24 | 25 | def test_not_supported_language(self): 26 | with self.assertRaises(ODOAException): 27 | coro = self.odoa.get_random_surah('fr') 28 | LOOP.run_until_complete(coro) 29 | 30 | # TODO: fix this test. 31 | # def test_exception_handling_getting_surah(self): 32 | # with mock.patch('odoa.get_random_surah') as execMock: 33 | # execMock.side_effect = IOError(mock.Mock(), 'Error') 34 | # with self.assertRaises(ODOAException): 35 | # coro = self.odoa.get_random_surah() 36 | # LOOP.run_until_complete(coro) 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Always prefer setuptools over distutils 2 | from setuptools import setup, find_packages 3 | # To use a consistent encoding 4 | import os 5 | from codecs import open 6 | 7 | here = os.path.abspath(os.path.dirname(__file__)) 8 | 9 | with open(os.path.join(here, 'requirements.txt')) as f: 10 | dependencies = [dep.strip() for dep in f.readlines()] 11 | 12 | setup( 13 | name='python-quran-odoa', 14 | py_modules=['odoa'], 15 | 16 | install_requires=dependencies, 17 | 18 | # Versions should comply with PEP440. For a discussion on single-sourcing 19 | # the version across setup.py and the project code, see 20 | # https://packaging.python.org/en/latest/single_source_version.html 21 | version='2.0.0', 22 | 23 | description='Library to get random ayah within quran including the translation.', 24 | long_description='Library to get random ayah within quran including the translation.', 25 | 26 | # The project's main homepage. 27 | url='https://github.com/Keda87/python-quran-odoa', 28 | 29 | # Author details 30 | author='Keda87', 31 | author_email='adiyatmubarak@gmail.com', 32 | 33 | # Choose your license 34 | license='MIT', 35 | 36 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 37 | classifiers=[ 38 | # How mature is this project? Common values are 39 | # 5 - Production/Stable 40 | 'Development Status :: 5 - Production/Stable', 41 | 42 | # Indicate who your project is intended for 43 | 'Intended Audience :: Developers', 44 | 'Topic :: Internet', 45 | 'Topic :: Software Development', 46 | 'Topic :: Software Development :: Libraries', 47 | 48 | # Pick your license as you wish (should match "license" above) 49 | 'License :: OSI Approved :: MIT License', 50 | 51 | # Specify the Python versions you support here. In particular, ensure 52 | # that you indicate whether you support Python 2, Python 3 or both. 53 | 'Programming Language :: Python :: 3', 54 | ], 55 | 56 | # What does your project relate to? 57 | keywords='development quran', 58 | 59 | # You can just specify the packages manually here if your project is 60 | # simple. Or you can use find_packages(). 61 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 62 | ) 63 | -------------------------------------------------------------------------------- /odoa.py: -------------------------------------------------------------------------------- 1 | """ 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Adiyat Mubarak 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import random 25 | 26 | import httpx 27 | from httpx import Response 28 | 29 | 30 | class ODOAException(Exception): 31 | pass 32 | 33 | 34 | class Quran(object): 35 | __slots__ = ['ayah', 'desc', 'translate', 'sound'] 36 | 37 | def __init__(self, ayah: str, desc: str, translate: str, sound: str): 38 | self.ayah = ayah 39 | self.desc = desc 40 | self.translate = translate 41 | self.sound = sound 42 | 43 | def __repr__(self): 44 | return f'<{self.__class__.__name__}: {self.desc}>' 45 | 46 | 47 | class ODOA(object): 48 | __slots__ = ['__TOTAL_SURAH', '__BASE_API', '__SUPPORTED_LANGUAGES'] 49 | 50 | def __init__(self) -> None: 51 | self.__TOTAL_SURAH = 114 # https://en.wikipedia.org/wiki/List_of_surahs_in_the_Quran 52 | self.__BASE_API = 'https://raw.githubusercontent.com/Keda87/quranjson/master/source' 53 | self.__SUPPORTED_LANGUAGES = ['id', 'en'] 54 | 55 | async def get_random_surah(self, lang: str = 'id') -> Quran: 56 | if lang not in self.__SUPPORTED_LANGUAGES: 57 | message = 'Currently your selected language not supported yet.' 58 | raise ODOAException(message) 59 | 60 | rand_surah = random.randint(1, self.__TOTAL_SURAH) 61 | surah_url = f'{self.__BASE_API}/surah/surah_{rand_surah}.json' 62 | try: 63 | response = await self.__fetch(surah_url) 64 | data = response.json() 65 | except IOError: 66 | raise ODOAException 67 | else: 68 | random_ayah = random.randint(1, int(data.get('count'))) 69 | ayah_key = f'verse_{random_ayah}' 70 | ayah = data['verse'][ayah_key] 71 | surah_index = data.get('index') 72 | surah_name = data.get('name') 73 | 74 | translation = await self.__get_translation(surah_index, ayah_key, lang) 75 | sound = self.__get_sound(surah_index, random_ayah) 76 | desc = f'{surah_name}:{random_ayah}' 77 | return Quran(ayah, desc, translation, sound) 78 | 79 | async def __get_translation(self, surah: int, ayah, lang: str) -> str: 80 | url = f'{self.__BASE_API}/translations/{lang}/{lang}_translation_{int(surah)}.json' 81 | try: 82 | response = await self.__fetch(url) 83 | data = response.json() 84 | return data['verse'][ayah] 85 | except ODOAException as e: 86 | raise e 87 | 88 | def __get_sound(self, surah: int, ayah: int) -> str: 89 | format_ayah = str(ayah).zfill(3) 90 | return f'{self.__BASE_API}/sounds/{surah}/{format_ayah}.mp3' 91 | 92 | @staticmethod 93 | async def __fetch(url: str) -> Response: 94 | async with httpx.AsyncClient() as client: 95 | return await client.get(url) 96 | 97 | def __repr__(self): 98 | return f'<{self.__class__.__name__}>' 99 | --------------------------------------------------------------------------------