Upload 3 files
Browse files- app.py +159 -0
- requirements.txt +4 -0
- style.css +28 -53
app.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
import random
|
| 5 |
+
import os
|
| 6 |
+
from bs4 import BeautifulSoup
|
| 7 |
+
from urllib.parse import quote
|
| 8 |
+
|
| 9 |
+
# Set page config for wide layout and title
|
| 10 |
+
st.set_page_config(page_title="MoodVerse", layout="wide")
|
| 11 |
+
|
| 12 |
+
# Load custom CSS
|
| 13 |
+
def load_css():
|
| 14 |
+
with open("style.css") as f:
|
| 15 |
+
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
|
| 16 |
+
|
| 17 |
+
load_css()
|
| 18 |
+
|
| 19 |
+
# Load mood-to-verse mapping from JSON
|
| 20 |
+
@st.cache_data
|
| 21 |
+
def load_mood_verses():
|
| 22 |
+
with open("mood_verses.json", "r", encoding="utf-8") as f:
|
| 23 |
+
return json.load(f)
|
| 24 |
+
|
| 25 |
+
# Fetch verse data from APIs
|
| 26 |
+
@st.cache_data(ttl=3600) # Cache for 1 hour
|
| 27 |
+
def fetch_verse_data(reference):
|
| 28 |
+
try:
|
| 29 |
+
surah, ayat = reference.split(":")
|
| 30 |
+
# Fetch from Al-Quran API for Arabic, English, Urdu translations, and audio
|
| 31 |
+
url = f"http://api.alquran.cloud/v1/ayah/{quote(reference)}"
|
| 32 |
+
response = requests.get(url, timeout=5)
|
| 33 |
+
response.raise_for_status()
|
| 34 |
+
data = response.json()["data"]
|
| 35 |
+
|
| 36 |
+
english_url = f"http://api.alquran.cloud/v1/ayah/{quote(reference)}/en.sahih"
|
| 37 |
+
urdu_url = f"http://api.alquran.cloud/v1/ayah/{quote(reference)}/ur.jalandhry"
|
| 38 |
+
english_data = requests.get(english_url, timeout=5).json()["data"]
|
| 39 |
+
urdu_data = requests.get(urdu_url, timeout=5).json()["data"]
|
| 40 |
+
|
| 41 |
+
audio_url = f"http://api.alquran.cloud/v1/ayah/{quote(reference)}/ar.alafasy"
|
| 42 |
+
audio_data = requests.get(audio_url, timeout=5).json()["data"]
|
| 43 |
+
|
| 44 |
+
# Fetch English Tafseer from quran-api (Maududi)
|
| 45 |
+
eng_tafseer_url = f"https://cdn.jsdelivr.net/gh/fawazahmed0/quran-api@1/editions/eng-maududi/{surah}/{ayat}.json"
|
| 46 |
+
eng_tafseer_response = requests.get(eng_tafseer_url, timeout=5)
|
| 47 |
+
eng_tafseer_data = eng_tafseer_response.json()
|
| 48 |
+
tafseer_english = eng_tafseer_data["text"]
|
| 49 |
+
|
| 50 |
+
# Fetch Urdu Tafseer from quran-api (Maududi)
|
| 51 |
+
urd_tafseer_url = f"https://cdn.jsdelivr.net/gh/fawazahmed0/quran-api@1/editions/urd-maududi/{surah}/{ayat}.json"
|
| 52 |
+
urd_tafseer_response = requests.get(urd_tafseer_url, timeout=5)
|
| 53 |
+
urd_tafseer_data = urd_tafseer_response.json()
|
| 54 |
+
tafseer_urdu = urd_tafseer_data["text"]
|
| 55 |
+
|
| 56 |
+
return {
|
| 57 |
+
"reference": data["surah"]["englishName"] + " (" + data["surah"]["name"] + "), Ayat " + str(data["numberInSurah"]),
|
| 58 |
+
"arabic": data["text"],
|
| 59 |
+
"english_translation": english_data["text"],
|
| 60 |
+
"urdu_translation": urdu_data["text"],
|
| 61 |
+
"english_tafseer": tafseer_english,
|
| 62 |
+
"urdu_tafseer": tafseer_urdu,
|
| 63 |
+
"audio": audio_data["audio"]
|
| 64 |
+
}
|
| 65 |
+
except Exception as e:
|
| 66 |
+
st.error(f"Failed to fetch verse data: {str(e)}")
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
# Main app
|
| 70 |
+
def main():
|
| 71 |
+
# Display logo and title
|
| 72 |
+
st.image("https://cdn.pixabay.com/photo/2017/03/27/13/59/quran-2178816_1280.png", width=200)
|
| 73 |
+
st.title("MoodVerse")
|
| 74 |
+
st.markdown("Discover peace and guidance from the Holy Quran. Select a mood to receive a random uplifting verse or dua, complete with Arabic text, translations in English and Urdu, and explanations (tafseer).")
|
| 75 |
+
|
| 76 |
+
# Sidebar for mood selection and language toggle
|
| 77 |
+
st.sidebar.header("Select a Mood")
|
| 78 |
+
moods = [
|
| 79 |
+
"Angry", "Happy", "Depressed", "Sick",
|
| 80 |
+
"Tired", "Frustrated", "Evil Eye",
|
| 81 |
+
"Indecisive", "Proud", "Aggressive",
|
| 82 |
+
"Lonely", "Guilty", "Hopeful"
|
| 83 |
+
]
|
| 84 |
+
selected_mood = st.sidebar.radio("Choose a mood", moods)
|
| 85 |
+
language = st.sidebar.selectbox("Language", ["Bilingual", "English", "Urdu"])
|
| 86 |
+
|
| 87 |
+
# Random mood button
|
| 88 |
+
if st.sidebar.button("Random Mood"):
|
| 89 |
+
selected_mood = random.choice(moods)
|
| 90 |
+
|
| 91 |
+
# Load mood-to-verse mapping
|
| 92 |
+
mood_verses = load_mood_verses()
|
| 93 |
+
verses = mood_verses.get(selected_mood.lower(), [])
|
| 94 |
+
|
| 95 |
+
if not verses:
|
| 96 |
+
st.warning("No verses found for this mood.")
|
| 97 |
+
return
|
| 98 |
+
|
| 99 |
+
# Generate random verse on mood selection or button press
|
| 100 |
+
if "selected_mood" not in st.session_state:
|
| 101 |
+
st.session_state.selected_mood = selected_mood
|
| 102 |
+
if "current_verse" not in st.session_state or st.session_state.selected_mood != selected_mood or st.button("Generate Another"):
|
| 103 |
+
st.session_state.current_verse = random.choice(verses)
|
| 104 |
+
st.session_state.selected_mood = selected_mood
|
| 105 |
+
|
| 106 |
+
verse_ref = st.session_state.current_verse
|
| 107 |
+
with st.spinner("Fetching verse data..."):
|
| 108 |
+
verse_data = fetch_verse_data(verse_ref)
|
| 109 |
+
|
| 110 |
+
if verse_data:
|
| 111 |
+
# Display verse
|
| 112 |
+
st.markdown(f"### {verse_data['reference']}")
|
| 113 |
+
st.markdown(f"<div class='arabic-text'>{verse_data['arabic']}</div>", unsafe_allow_html=True)
|
| 114 |
+
|
| 115 |
+
col1, col2 = st.columns(2)
|
| 116 |
+
if language in ["Bilingual", "English"]:
|
| 117 |
+
with col1:
|
| 118 |
+
st.markdown("<div class='english-translation'>**English Translation**</div>", unsafe_allow_html=True)
|
| 119 |
+
st.markdown(f"<div class='english-translation'>{verse_data['english_translation']}</div>", unsafe_allow_html=True)
|
| 120 |
+
st.markdown("<div class='english-tafseer'>**English Tafseer**</div>", unsafe_allow_html=True)
|
| 121 |
+
st.markdown(f"<div class='english-tafseer'>{verse_data['english_tafseer']}</div>", unsafe_allow_html=True)
|
| 122 |
+
if language in ["Bilingual", "Urdu"]:
|
| 123 |
+
with col2:
|
| 124 |
+
st.markdown("<div class='urdu-translation'>**اردو ترجمہ**</div>", unsafe_allow_html=True)
|
| 125 |
+
st.markdown(f"<div class='urdu-translation'>{verse_data['urdu_translation']}</div>", unsafe_allow_html=True)
|
| 126 |
+
st.markdown("<div class='urdu-tafseer'>**اردو تفسیر**</div>", unsafe_allow_html=True)
|
| 127 |
+
st.markdown(f"<div class='urdu-tafseer'>{verse_data['urdu_tafseer']}</div>", unsafe_allow_html=True)
|
| 128 |
+
|
| 129 |
+
# Audio playback
|
| 130 |
+
if verse_data["audio"]:
|
| 131 |
+
st.audio(verse_data["audio"], format="audio/mp3")
|
| 132 |
+
|
| 133 |
+
# Save verse button
|
| 134 |
+
if "saved_verses" not in st.session_state:
|
| 135 |
+
st.session_state.saved_verses = []
|
| 136 |
+
|
| 137 |
+
if st.button("Save this Verse"):
|
| 138 |
+
if len(st.session_state.saved_verses) < 10 and verse_data not in st.session_state.saved_verses:
|
| 139 |
+
st.session_state.saved_verses.append(verse_data)
|
| 140 |
+
elif len(st.session_state.saved_verses) >= 10:
|
| 141 |
+
st.warning("You can save up to 10 verses only.")
|
| 142 |
+
|
| 143 |
+
# Display saved verses in expander
|
| 144 |
+
with st.expander("Saved Verses"):
|
| 145 |
+
for i, saved in enumerate(st.session_state.saved_verses):
|
| 146 |
+
st.markdown(f"**Saved {i+1}: {saved['reference']}**")
|
| 147 |
+
if st.button(f"View {saved['reference']}", key=f"view_{i}"):
|
| 148 |
+
st.markdown(f"<div class='arabic-text'>{saved['arabic']}</div>", unsafe_allow_html=True)
|
| 149 |
+
st.write(saved["english_translation"])
|
| 150 |
+
st.write(saved["urdu_translation"])
|
| 151 |
+
st.write(saved["english_tafseer"])
|
| 152 |
+
st.write(saved["urdu_tafseer"])
|
| 153 |
+
|
| 154 |
+
# Footer
|
| 155 |
+
st.markdown("---")
|
| 156 |
+
st.markdown("Data sourced from Al-Quran API and Quran-API on GitHub. Built with Streamlit.")
|
| 157 |
+
|
| 158 |
+
if __name__ == "__main__":
|
| 159 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit==1.25.0
|
| 2 |
+
requests==2.31.0
|
| 3 |
+
beautifulsoup4==4.12.2
|
| 4 |
+
python-dotenv==1.0.0
|
style.css
CHANGED
|
@@ -1,65 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
.arabic-text {
|
| 2 |
-
font-family: 'Traditional Arabic', 'Scheherazade New', serif;
|
| 3 |
font-size: 24px;
|
| 4 |
-
text-align: right;
|
| 5 |
direction: rtl;
|
| 6 |
-
line-height: 2;
|
| 7 |
-
margin: 20px 0;
|
| 8 |
-
padding: 15px;
|
| 9 |
-
background-color: #f8f9fa;
|
| 10 |
-
border-radius: 10px;
|
| 11 |
-
border-right: 4px solid #2ca02c;
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
.urdu-text {
|
| 15 |
-
font-family: 'Jameel Noori Nastaleeq', 'Urdu Typesetting', serif;
|
| 16 |
-
font-size: 18px;
|
| 17 |
text-align: right;
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
font-size: 16px;
|
| 28 |
-
line-height: 1.6;
|
| 29 |
-
margin: 15px 0;
|
| 30 |
padding: 10px;
|
| 31 |
-
|
| 32 |
-
border-radius: 8px;
|
| 33 |
}
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
font-style: italic;
|
| 37 |
-
color: #555;
|
| 38 |
-
margin: 10px 0;
|
| 39 |
padding: 10px;
|
| 40 |
-
background-color: #f8f9fa;
|
| 41 |
-
border-left: 3px solid #1f77b4;
|
| 42 |
border-radius: 5px;
|
| 43 |
}
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
margin-top: 30px;
|
| 50 |
}
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
.urdu-text {
|
| 59 |
-
font-size: 16px;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.english-text {
|
| 63 |
-
font-size: 14px;
|
| 64 |
-
}
|
| 65 |
}
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: Arial, sans-serif;
|
| 3 |
+
background-color: #f0f8ff;
|
| 4 |
+
}
|
| 5 |
.arabic-text {
|
|
|
|
| 6 |
font-size: 24px;
|
|
|
|
| 7 |
direction: rtl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
text-align: right;
|
| 9 |
+
color: #2c3e50;
|
| 10 |
+
margin-bottom: 20px;
|
| 11 |
+
}
|
| 12 |
+
h3 {
|
| 13 |
+
color: #1a5f7a;
|
| 14 |
+
}
|
| 15 |
+
button {
|
| 16 |
+
background-color: #1a5f7a !important;
|
| 17 |
+
color: white !important;
|
| 18 |
}
|
| 19 |
+
.english-translation {
|
| 20 |
+
color: #007bff;
|
|
|
|
|
|
|
|
|
|
| 21 |
padding: 10px;
|
| 22 |
+
border-radius: 5px;
|
|
|
|
| 23 |
}
|
| 24 |
+
.english-tafseer {
|
| 25 |
+
color: #28a745;
|
|
|
|
|
|
|
|
|
|
| 26 |
padding: 10px;
|
|
|
|
|
|
|
| 27 |
border-radius: 5px;
|
| 28 |
}
|
| 29 |
+
.urdu-translation {
|
| 30 |
+
color: #dc3545;
|
| 31 |
+
padding: 10px;
|
| 32 |
+
border-radius: 5px;
|
| 33 |
+
direction: rtl;
|
|
|
|
| 34 |
}
|
| 35 |
+
.urdu-tafseer {
|
| 36 |
+
color: #ffc107;
|
| 37 |
+
padding: 10px;
|
| 38 |
+
border-radius: 5px;
|
| 39 |
+
direction: rtl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
}
|