-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathapp.py
More file actions
291 lines (251 loc) Β· 13.2 KB
/
app.py
File metadata and controls
291 lines (251 loc) Β· 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
import streamlit as st
import streamlit.components.v1 as components
import os
import uuid
from datetime import datetime
from urllib.parse import urlencode
from io import BytesIO
import pandas as pd
from utils.scheduler import jadwalkan_semua_post
from utils.oauth import get_access_token
st.set_page_config(page_title="WordPress Scheduler", layout="wide")
st.title("ποΈ WordPress Post Scheduler")
st.markdown("Aplikasi ini memungkinkan Anda menjadwalkan semua post dari file Excel ke WordPress.com secara otomatis.")
# -- Developer app checker -------------------------------------------------
def has_developer_app():
"""Detect whether the user already has a WordPress developer app.
Detection checks (in order):
- Environment variables `WORDPRESS_CLIENT_ID` and `WORDPRESS_CLIENT_SECRET`
- Presence of local files `client_id.txt` and `client_secret.txt` in project root
"""
# Env var check
env_id = os.environ.get("WORDPRESS_CLIENT_ID") or os.environ.get("WP_CLIENT_ID")
env_secret = os.environ.get("WORDPRESS_CLIENT_SECRET") or os.environ.get("WP_CLIENT_SECRET")
if env_id and env_secret:
return True
# File check
if os.path.exists("client_id.txt") and os.path.exists("client_secret.txt"):
try:
with open("client_id.txt", "r") as f:
cid = f.read().strip()
with open("client_secret.txt", "r") as f:
csec = f.read().strip()
if cid and csec:
return True
except Exception:
return False
return False
show_main = False
# Determine whether to show the main credentials form right away.
# We prefer rendering it immediately in the same run after the user clicks
# "Saya sudah punya" to avoid requiring a second click.
if has_developer_app() or st.session_state.get("_dev_app_checked"):
show_main = True
else:
st.header("Sebelum mulai β Buat App Pengembang WordPress")
st.write("Aplikasi ini membutuhkan WordPress Developer App (Client ID & Client Secret). Jika Anda belum membuatnya, silakan buat dulu.")
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("- Jika Anda sudah memiliki `Client ID` dan `Client Secret`, klik **'Saya sudah punya'** untuk melanjutkan.")
st.markdown("- Jika belum, klik **'Buat Developer App'** untuk membuka halaman pendaftaran app WordPress.")
with col2:
have_clicked = st.button("Saya sudah punya")
create_clicked = st.button("Buat Developer App")
if have_clicked:
st.session_state["_dev_app_checked"] = True
show_main = True
# Try to immediately rerun the script so the checker block is not
# rendered in the same run. If experimental_rerun is unavailable,
# stop the script β the next interaction will rerun.
try:
st.experimental_rerun()
except Exception:
st.stop()
if create_clicked:
# Open the WordPress developer app page in a new tab/window.
components.html(
"""
<a id="wpapp" href="https://developer.wordpress.com/apps/new" target="_blank" rel="noopener noreferrer"></a>
<script>
document.getElementById('wpapp').click();
</script>
""",
height=0,
)
# Also show the link in case redirect/JS is blocked
st.markdown("[Buka halaman pembuatan App WordPress di browser](https://developer.wordpress.com/apps/new)")
with st.expander("Manual: Cara membuat WordPress Developer App (langkah cepat)"):
st.markdown("Berikut langkah cepat untuk membuat Developer App di WordPress dan mendapatkan Client ID & Client Secret:")
st.markdown("1. Buka https://developer.wordpress.com/apps/new dan buat aplikasi baru.")
st.markdown("2. Isi nama aplikasi, deskripsi, dan website (opsional).")
st.markdown("3. Tambahkan Redirect URL yang diperlukan (misal: `https://web-scheduler.streamlit.app` untuk deployment, atau `http://localhost:8501` untuk pengujian lokal).")
st.markdown("4. Simpan aplikasi lalu salin **Client ID** dan **Client Secret**.")
st.markdown("5. Kembali ke halaman ini, klik **'Saya sudah punya'** dan masukkan Client ID/Secret pada form.")
st.markdown("6. (Opsional) Simpan nilai ke file `client_id.txt` dan `client_secret.txt` di folder project, atau set environment variables `WORDPRESS_CLIENT_ID` / `WORDPRESS_CLIENT_SECRET`.")
st.markdown("7. Untuk pengujian lokal, jalankan Streamlit dan gunakan redirect `http://localhost:8501` saat membuat app:")
st.code("pip install -r requirements.txt\nstreamlit run app.py", language="bash")
st.markdown("Jika Anda ingin panduan lengkap, lihat `README.md` di repo atau buka: https://github.com/bimast/Web_Scheduler/blob/main/README.md")
if show_main:
st.subheader("π Masukkan Data WordPress")
# Session-state initialization
st.session_state.setdefault("check_visible", True)
st.session_state.setdefault("uploaded_file_bytes", None)
st.session_state.setdefault("file_is_valid", None)
st.session_state.setdefault("last_check_msg", "")
st.session_state.setdefault("_start_processing", False)
st.session_state.setdefault("auth_code", None)
st.session_state.setdefault("show_auth_input", False)
st.session_state.setdefault("_stored_access_token", "")
# Credentials + uploader (outside forms so widgets update session_state)
col1, col2 = st.columns(2)
with col1:
client_id = st.text_input("Client ID", placeholder="Masukkan Client ID", key="client_id")
client_secret = st.text_input("Client Secret", placeholder="Masukkan Client Secret", key="client_secret", type="password")
site_url = st.text_input("Site URL (misal: mysite.wordpress.com)", key="site_url")
with col2:
access_token = st.text_input("Access Token", key="access_token", type="password", value=st.session_state.get("_stored_access_token", ""))
# Button to get access token via OAuth
col_token1, col_token2 = st.columns([3, 1])
with col_token1:
st.write("") # spacer
with col_token2:
get_token_clicked = st.button("π Dapatkan Token")
if get_token_clicked:
if not client_id or not client_secret:
st.error("β Harap isi Client ID dan Client Secret terlebih dahulu.")
else:
# Generate authorization URL
auth_url = f"https://public-api.wordpress.com/oauth2/authorize?client_id={client_id}&response_type=code&redirect_uri=https://web-scheduler.streamlit.app&state={uuid.uuid4()}"
# Open authorization URL
components.html(
f"""
<a id="wpauth" href="{auth_url}" target="_blank" rel="noopener noreferrer"></a>
<script>
document.getElementById('wpauth').click();
</script>
""",
height=0,
)
st.info("π Browser akan membuka halaman otorisasi WordPress. Salin **authorization code** dari URL setelah otorisasi, lalu masukkan di bawah.")
st.session_state.show_auth_input = True
# Show auth code input if needed
if st.session_state.show_auth_input:
auth_code = st.text_input("Masukkan Authorization Code dari URL", key="auth_code_input")
col_auth1, col_auth2 = st.columns(2)
with col_auth1:
if st.button("β
Tukarkan dengan Access Token"):
if auth_code:
result = get_access_token(client_id, client_secret, auth_code)
if result["success"]:
st.session_state._stored_access_token = result["access_token"]
st.success(f"β
Access Token berhasil didapatkan!")
st.code(result["access_token"], language="text")
st.session_state.show_auth_input = False
# Rerun to update the access_token input field
try:
st.experimental_rerun()
except Exception:
pass
else:
st.error(f"β Gagal mendapatkan access token: {result['error']}")
else:
st.error("β Harap masukkan authorization code.")
with col_auth2:
if st.button("β Batal"):
st.session_state.show_auth_input = False
try:
st.experimental_rerun()
except Exception:
pass
uploaded_file = st.file_uploader("Upload File posts.xlsx", type=["xlsx"], key="file_upload")
# Callbacks
def handle_check():
data = None
try:
if uploaded_file is not None:
data = uploaded_file.read()
except Exception:
data = None
if not data:
st.session_state.file_is_valid = False
st.session_state.last_check_msg = "Harap upload file terlebih dahulu."
return
try:
df = pd.read_excel(BytesIO(data))
required_cols = ["judul", "konten_html", "tag", "tanggal_publish"]
missing = [c for c in required_cols if c not in df.columns]
if missing:
st.session_state.file_is_valid = False
st.session_state.last_check_msg = f"Kolom hilang: {', '.join(missing)}"
else:
st.session_state.file_is_valid = True
st.session_state.uploaded_file_bytes = data
st.session_state.last_check_msg = "Format file valid."
except Exception as e:
st.session_state.file_is_valid = False
st.session_state.last_check_msg = f"Gagal membaca file: {e}"
def handle_process():
# hide the check UI immediately and schedule processing
st.session_state.check_visible = False
# ensure bytes saved
try:
if uploaded_file is not None:
st.session_state.uploaded_file_bytes = uploaded_file.read()
except Exception:
pass
st.session_state._start_processing = True
try:
st.experimental_rerun()
except Exception:
return
# Render buttons
if st.session_state.check_visible:
st.button("π Periksa File", on_click=handle_check)
st.button("π Proses", on_click=handle_process)
# Show last check status
if st.session_state.get("file_is_valid") is True:
st.success(f"β
Terakhir dicek: {st.session_state.get('last_check_msg')}")
elif st.session_state.get("file_is_valid") is False:
st.warning(f"β οΈ Terakhir dicek: {st.session_state.get('last_check_msg')}")
# If processing was requested (set by handle_process), run it here
if st.session_state.get("_start_processing"):
# Basic validation before processing
if not client_id or not client_secret or not site_url:
st.warning("β Harap isi semua data yang dibutuhkan (Client ID, Client Secret, dan Site URL).")
st.session_state._start_processing = False
elif not st.session_state.get("uploaded_file_bytes"):
st.warning("β Harap upload file posts.xlsx.")
st.session_state._start_processing = False
else:
with st.spinner("β³ Menjadwalkan post ke WordPress..."):
file_bytes = st.session_state.get("uploaded_file_bytes")
with open("temp_posts.xlsx", "wb") as f:
f.write(file_bytes)
hasil = jadwalkan_semua_post("temp_posts.xlsx", access_token, site_url)
try:
os.remove("temp_posts.xlsx")
except Exception:
pass
# Processing finished β reset flags so UI can be reused
st.session_state._start_processing = False
st.session_state.check_visible = True
# Summarize results
success_count = sum(1 for h in hasil if isinstance(h, str) and h.strip().startswith("β
"))
failure_count = len(hasil) - success_count
if success_count > 0 and failure_count == 0:
st.success(f"β
Semua posting berhasil: {success_count} sukses, {failure_count} gagal")
elif success_count > 0 and failure_count > 0:
st.warning(f"β οΈ Proses selesai dengan beberapa kegagalan: {success_count} sukses, {failure_count} gagal")
else:
st.error("β Tidak ada posting yang berhasil. Periksa pesan kesalahan di bawah.")
for h in hasil:
if not isinstance(h, str):
st.write(h)
continue
text = h.strip()
if text.startswith("β
"):
st.write(text)
elif text.startswith("β οΈ"):
st.warning(text)
else:
st.error(text)