Compare commits

...
4 Commits
Author SHA1 Message Date
shihaam 8fbf22a29b add proxy 2026-03-24 11:15:00 +05:00
shihaam 004130d2a6 update docs 2026-03-24 10:23:47 +05:00
shihaam 7af571e55a follow system theme 2026-03-24 10:20:50 +05:00
shihaam cda6557e42 add retry on api fail 2026-03-24 10:16:30 +05:00
6 changed files with 149 additions and 6 deletions
+6 -2
View File
@@ -1,12 +1,16 @@
# E-Dir # E-Dir
A simple web frontend for Dhiraagu directory lookup. A better web frontend for [Dhiraagu E-Directory](https://www.dhiraagu.com.mv/e-directory).
## Features ## Features
- Phone number lookup via Dhiraagu E-Dir API - Phone number lookup via Dhiraagu E-Dir API
- Material Design UI - Tap-to-call with `tel:` links
- Search history stored in LocalStorage
- Accepts country code prefix (+960)
- QR code generation to share contact numbers
- Automatic dark/light theme (follows system preference) - Automatic dark/light theme (follows system preference)
- No Recaptcha required
## Usage ## Usage
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
from flask import Flask, request, Response
from curl_cffi import requests as curl_requests
app = Flask(__name__)
BASE_URL = "https://app-production.dhiraagu.com.mv"
def proxy_request(target_url: str) -> Response:
headers = {"Host": "app-production.dhiraagu.com.mv"}
for h in ["Authorization", "Content-Type", "Accept"]:
if h in request.headers:
headers[h] = request.headers[h]
resp = curl_requests.request(
method=request.method,
url=target_url,
headers=headers,
data=request.get_data() or None,
impersonate="chrome",
timeout=30,
)
return Response(resp.content, resp.status_code, {"Content-Type": resp.headers.get("content-type", "application/json")})
@app.route("/<int:subscriber_id>")
def subscriber_lookup(subscriber_id: int):
return proxy_request(f"{BASE_URL}/io/v1/info/subscribers/{subscriber_id}/dir")
@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def catch_all(path: str):
return proxy_request(f"{BASE_URL}/{path}")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
+2
View File
@@ -0,0 +1,2 @@
flask>=3.0.0
curl_cffi>=0.6.0
+2 -1
View File
@@ -41,7 +41,8 @@
<!-- Additional SEO --> <!-- Additional SEO -->
<meta name="application-name" content="E-Dir"> <meta name="application-name" content="E-Dir">
<meta name="theme-color" content="#ff8c00"> <meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#f5f5f5" media="(prefers-color-scheme: light)">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+51 -3
View File
@@ -105,7 +105,7 @@ function renderTable(filteredHistory = null) {
if (entry.status === 'not_found') { if (entry.status === 'not_found') {
resultHtml = getStatusBadge(entry.status); resultHtml = getStatusBadge(entry.status);
} else if (entry.status === 'error') { } else if (entry.status === 'error') {
resultHtml = `${escapeHtml(entry.result)}<div style="margin-top: 8px;">${getStatusBadge(entry.status)}</div>`; resultHtml = `${escapeHtml(entry.result)}<div style="margin-top: 8px;">${getStatusBadge(entry.status)}<button class="retry-btn" data-phone="${escapeHtml(entry.phone)}" data-id="${entry.id}" title="Retry"><span class="material-icons">refresh</span></button></div>`;
} else { } else {
resultHtml = escapeHtml(entry.result); resultHtml = escapeHtml(entry.result);
} }
@@ -135,6 +135,10 @@ function renderTable(filteredHistory = null) {
resultCell.classList.add('copyable'); resultCell.classList.add('copyable');
resultCell.addEventListener('click', () => copyToClipboard(entry.result)); resultCell.addEventListener('click', () => copyToClipboard(entry.result));
} }
const retryBtn = row.querySelector('.retry-btn');
if (retryBtn) {
retryBtn.addEventListener('click', () => retrySearch(entry.id, entry.phone, retryBtn));
}
historyTableBody.appendChild(row); historyTableBody.appendChild(row);
// Mobile table row // Mobile table row
@@ -162,6 +166,10 @@ function renderTable(filteredHistory = null) {
mobileResultCell.classList.add('copyable'); mobileResultCell.classList.add('copyable');
mobileResultCell.addEventListener('click', () => copyToClipboard(entry.result)); mobileResultCell.addEventListener('click', () => copyToClipboard(entry.result));
} }
const mobileRetryBtn = mobileRow.querySelector('.retry-btn');
if (mobileRetryBtn) {
mobileRetryBtn.addEventListener('click', () => retrySearch(entry.id, entry.phone, mobileRetryBtn));
}
mobileTableBody.appendChild(mobileRow); mobileTableBody.appendChild(mobileRow);
}); });
} }
@@ -250,16 +258,56 @@ async function copyToClipboard(text) {
} }
} }
async function retrySearch(entryId, phoneNumber, button) {
button.classList.add('loading');
let apiNumber = phoneNumber;
if (/^\+960\d{7}$/.test(phoneNumber)) {
apiNumber = phoneNumber.slice(4);
} else if (/^960\d{7}$/.test(phoneNumber)) {
apiNumber = phoneNumber.slice(3);
}
try {
const response = await fetch(`https://dhiraagu-edir-proxy.shihaam.me/${encodeURIComponent(apiNumber)}`);
const data = await response.json();
const entryIndex = searchHistory.findIndex(e => e.id === entryId);
if (entryIndex === -1) return;
if (response.ok && data && data.dirEnquiryEntry) {
if (data.dirEnquiryEntry === 'Number not found') {
searchHistory[entryIndex].result = 'Number not found';
searchHistory[entryIndex].status = 'not_found';
} else {
searchHistory[entryIndex].result = data.dirEnquiryEntry;
searchHistory[entryIndex].status = 'success';
}
} else {
searchHistory[entryIndex].result = 'No results found';
searchHistory[entryIndex].status = 'error';
}
searchHistory[entryIndex].timestamp = Date.now();
saveHistory();
renderTable();
showToast('Retry successful', 'success');
} catch (error) {
button.classList.remove('loading');
showToast('Retry failed', 'error');
}
}
function showQrCode(phone) { function showQrCode(phone) {
const telUrl = `tel:${phone}`; const telUrl = `tel:${phone}`;
qrTitle.textContent = phone; qrTitle.textContent = phone;
qrContainer.innerHTML = ''; qrContainer.innerHTML = '';
const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches;
new QRCode(qrContainer, { new QRCode(qrContainer, {
text: telUrl, text: telUrl,
width: 200, width: 200,
height: 200, height: 200,
colorDark: '#ff8c00', colorDark: isLightMode ? '#e07800' : '#ff8c00',
colorLight: '#111111', colorLight: isLightMode ? '#ffffff' : '#111111',
correctLevel: QRCode.CorrectLevel.H correctLevel: QRCode.CorrectLevel.H
}); });
qrModal.classList.add('show'); qrModal.classList.add('show');
+48
View File
@@ -17,6 +17,21 @@
--error: #ff3333; --error: #ff3333;
} }
@media (prefers-color-scheme: light) {
:root {
--orange: #e07800;
--bg-primary: #f5f5f5;
--bg-secondary: #ffffff;
--bg-card: #ffffff;
--text-primary: #1b1b1b;
--text-secondary: #808080;
--border: #e0e0e0;
--border-hover: #e07800;
--success: #00a844;
--error: #d32f2f;
}
}
body { body {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
background-color: var(--bg-primary); background-color: var(--bg-primary);
@@ -689,6 +704,39 @@ td.phone-number {
border-color: var(--error); border-color: var(--error);
} }
.retry-btn {
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
background-color: transparent;
border: 2px solid var(--orange);
color: var(--orange);
cursor: pointer;
transition: all 0.15s ease;
vertical-align: middle;
margin-left: 8px;
}
.retry-btn:hover {
background-color: var(--orange);
color: white;
}
.retry-btn .material-icons {
font-size: 18px;
}
.retry-btn.loading {
pointer-events: none;
opacity: 0.6;
}
.retry-btn.loading .material-icons {
animation: spin 0.8s linear infinite;
}
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 80px 20px; padding: 80px 20px;