Dink0 commited on
Commit
82ad9dd
·
verified ·
1 Parent(s): f0d4215

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -235
app.py CHANGED
@@ -1,240 +1,101 @@
1
  import streamlit as st
2
- import asyncio
3
- import nest_asyncio
4
- nest_asyncio.apply()
5
-
6
- import re
7
- import os
8
  import requests
9
- from telethon import TelegramClient
10
- from telethon.sessions import StringSession
11
- import streamlit.components.v1 as components
12
-
13
- # ----------- Настройки Telegram -----------
14
- TELEGRAM_API_ID = 23646321
15
- TELEGRAM_API_HASH = "102edf31d404cb72d17ee4831d4e8c7b"
16
- # Замените на вашу сгенерированную StringSession (одна строка без переносов)
17
- TELEGRAM_STRING_SESSION = "1ApWapzMBu8a9o9Wu1GexPdkO7Jsr6po1xym67EcfAxkEbVx1oxw6wW7LINb9Xf0VYcyEAP-b-D0RQ_4x0RDAW8Di_0P2r4bj7xNeV9xO3wBMcaeq7FJux3UMq5buq89gqG8JarZG04tIyWoVnH__dq1_6G2Zkr0GF4WJCS2UN7mmwBacIIUEjUryEA97tXYC8jD14J-1MDgFsNNZTJ6uurD5RzH2DIhphChebxOkDBdXmpLGlanEzYzVOlx4p6Mo2zYorRG968o64CxKEh-WiJaPSNrS3IEfIVWFVd_tHKWzQSPD1zHHhcw0v2_ZjCKbZnTMcT3uMYuyLwA55_dckzcw0wZgAZQ="
18
-
19
- # ----------- Настройки ВКонтакте -----------
20
- VK_ACCESS_TOKEN = os.environ.get("VK_ACCESS_TOKEN", "")
21
- if not VK_ACCESS_TOKEN:
22
- st.warning("Не найден VK_ACCESS_TOKEN. Добавьте секрет 'VK_ACCESS_TOKEN' в настройках Spaces!")
23
- VK_API_VERSION = "5.131"
24
-
25
- # --- Инициализация Telethon-клиента (Telegram) ---
26
- def init_telethon():
27
- loop = asyncio.new_event_loop()
28
- asyncio.set_event_loop(loop)
29
- client = TelegramClient(StringSession(TELEGRAM_STRING_SESSION), TELEGRAM_API_ID, TELEGRAM_API_HASH)
30
- loop.run_until_complete(client.connect())
31
- if not loop.run_until_complete(client.is_user_authorized()):
32
- st.error("Telegram-сессия не авторизована. Проверьте StringSession или выполните login_telethon.py.")
33
- st.stop()
34
- return client, loop
35
-
36
- if "telethon_client" not in st.session_state:
37
- st.session_state["telethon_client"], st.session_state["telethon_loop"] = init_telethon()
38
- tg_client = st.session_state["telethon_client"]
39
- tg_loop = st.session_state["telethon_loop"]
40
-
41
- def human_format_views(num: int) -> str:
42
- if num < 1000:
43
- return f"{num / 1000:.1f}K"
44
- elif num < 10_000:
45
- return f"{num / 1000:.1f}K"
46
- elif num < 1_000_000:
47
- return f"{int(num / 1000)}K"
48
- else:
49
- return f"{num / 1_000_000:.1f}M"
50
-
51
- # ------------- Парсер Telegram -------------
52
- def process_telegram_link(link: str) -> tuple[str, int]:
53
- link = link.strip()
54
- if not link:
55
- return "", 0
56
-
57
- # Поддержка ссылок вида t.me, telegram.me (без учета регистра)
58
- match = re.match(r'https?://(?:t(?:elegram)?\.me)/([^/]+)/(\d+)', link, re.IGNORECASE)
59
- if not match:
60
- return f"**Ошибка (TG)**: не распознал ссылку — {link}", 0
61
-
62
- channel_username = match.group(1)
63
- message_id = int(match.group(2))
64
- global tg_client, tg_loop
65
- try:
66
- msg = tg_loop.run_until_complete(tg_client.get_messages(channel_username, ids=message_id))
67
- except RuntimeError as e:
68
- if "Event loop is closed" in str(e):
69
- tg_client, tg_loop = init_telethon()
70
- st.session_state["telethon_client"] = tg_client
71
- st.session_state["telethon_loop"] = tg_loop
72
- msg = tg_loop.run_until_complete(tg_client.get_messages(channel_username, ids=message_id))
73
- else:
74
- return f"**Ошибка (TG)** при обработке [{link}]({link}): {e}", 0
75
- if msg is None:
76
- return f"**Ошибка (TG)**: сообщение не найдено — {link}", 0
77
- try:
78
- channel_entity = tg_loop.run_until_complete(tg_client.get_entity(channel_username))
79
- except RuntimeError as e:
80
- if "Event loop is closed" in str(e):
81
- tg_client, tg_loop = init_telethon()
82
- st.session_state["telethon_client"] = tg_client
83
- st.session_state["telethon_loop"] = tg_loop
84
- channel_entity = tg_loop.run_until_complete(tg_client.get_entity(channel_username))
85
- else:
86
- return f"**Ошибка (TG)** при обработке [{link}]({link}): {e}", 0
87
- channel_title = channel_entity.title
88
- views = msg.views or 0
89
- result = f"- [{channel_title}]({link}) — {human_format_views(views)}"
90
- return result, views
91
-
92
- # ------------- Парсер VK -------------
93
- def process_vk_link(link: str) -> tuple[str, int]:
94
- link = link.strip()
95
- if not link:
96
- return "", 0
97
-
98
- match = re.search(r'vk\.com/wall(-?\d+)_(\d+)', link)
99
- if not match:
100
- return f"**Ошибка (VK)**: не распознал ссылку — {link}", 0
101
-
102
- owner_id = int(match.group(1))
103
- post_id = match.group(2)
104
- posts_param = f"{owner_id}_{post_id}"
105
- api_url = "https://api.vk.com/method/wall.getById"
106
- params = {
107
- "posts": posts_param,
108
- "access_token": VK_ACCESS_TOKEN,
109
- "v": VK_API_VERSION,
110
- "extended": 1
111
- }
112
- try:
113
- resp = requests.get(api_url, params=params)
114
- if resp.status_code != 200:
115
- return f"**Ошибка (VK)**: HTTP {resp.status_code} — {link}", 0
116
- data = resp.json()
117
- if "error" in data:
118
- error_msg = data["error"].get("error_msg", "Неизвестная ошибка")
119
- return f"**Ошибка (VK)**: {error_msg} — {link}", 0
120
- response_data = data.get("response", {})
121
- items = response_data.get("items", [])
122
- if not items:
123
- return f"**Ошибка (VK)**: пост не найден — {link}", 0
124
- post = items[0]
125
- views = post.get("views", {}).get("count", 0)
126
- post_owner_id = post.get("owner_id", owner_id)
127
- title = None
128
- if post_owner_id < 0:
129
- group_id = -post_owner_id
130
- for g in response_data.get("groups", []):
131
- if g["id"] == group_id:
132
- title = g["name"]
133
- break
134
- else:
135
- user_id = post_owner_id
136
- for p in response_data.get("profiles", []):
137
- if p["id"] == user_id:
138
- first_name = p.get("first_name", "")
139
- last_name = p.get("last_name", "")
140
- title = (first_name + " " + last_name).strip()
141
- break
142
- if not title:
143
- title = "VK пост"
144
- result = f"- [{title}]({link}) — {human_format_views(views)}"
145
- return result, views
146
- except Exception as e:
147
- return f"**Ошибка (VK)** при обработке [{link}]({link}): {e}", 0
148
-
149
- # ------------- Определение платформы -------------
150
- def detect_platform(link: str) -> str:
151
- link = link.strip().lower()
152
- if "t.me/" in link or "telegram.me/" in link:
153
- return "telegram"
154
- elif "vk.com/wall" in link:
155
- return "vk"
156
- return ""
157
-
158
- # ---------- Интерфейс Streamlit ----------
159
- st.title("PR Tool made by @Dina")
160
- st.write("Вставьте ссылки (каждая на новой строке) — как Telegram, так и ВКонтакте. Пример:")
161
- st.code("""
162
- https://t.me/meduzalive/122479
163
- https://vk.com/wall-12256840_3220312
164
- https://telegram.me/za_traktor/27423
165
- https://vk.com/wall-237426_1643234
166
  """)
167
- links_input = st.text_area("Ссылки:", height=200)
168
-
169
- if st.button("Обработать"):
170
- lines = links_input.strip().split('\n')
171
- tg_results = []
172
- tg_total_views = 0
173
- vk_results = []
174
- vk_total_views = 0
175
-
176
- for link in lines:
177
- platform = detect_platform(link)
178
- if platform == "telegram":
179
- res_line, views = process_telegram_link(link)
180
- tg_results.append((res_line, views))
181
- tg_total_views += views
182
- elif platform == "vk":
183
- res_line, views = process_vk_link(link)
184
- vk_results.append((res_line, views))
185
- vk_total_views += views
186
- else:
187
- st.markdown(f"**Ошибка**: не распознал платформу — {link}", unsafe_allow_html=True)
188
-
189
- tg_results.sort(key=lambda x: x[1], reverse=True)
190
- vk_results.sort(key=lambda x: x[1], reverse=True)
191
 
192
- # Формирование итогового Markdown-вывода
193
- md_lines = []
194
-
195
- md_lines.append("## Telegram")
196
- md_lines.append(f"**Суммарный охват постов — {human_format_views(tg_total_views)} просмотров**")
197
- if tg_results:
198
- for r_line, _ in tg_results:
199
- md_lines.append(r_line)
200
- else:
201
- md_lines.append("_Нет ссылок на Telegram_")
202
- md_lines.append("")
203
-
204
- md_lines.append("## ВКонтакте")
205
- md_lines.append(f"**Суммарный охват постов — {human_format_views(vk_total_views)} просмотров**")
206
- if vk_results:
207
- for r_line, _ in vk_results:
208
- md_lines.append(r_line)
209
- else:
210
- md_lines.append("_Нет ссылок на ВКонтакте_")
211
- md_lines.append("")
212
-
213
- st.markdown("### Результат:")
214
- for line in md_lines:
215
- st.markdown(line, unsafe_allow_html=True)
216
-
217
- markdown_result = "\n".join(md_lines)
 
 
 
 
 
 
218
 
219
- # HTML/JS-компонент для копирования в буфер обмена с изменением текста кнопки
220
- copy_button_html = f"""
221
- <html>
222
- <head>
223
- <script>
224
- function copyToClipboard() {{
225
- const btn = document.getElementById("copy-btn");
226
- const text = `{markdown_result}`;
227
- navigator.clipboard.writeText(text).then(function() {{
228
- btn.innerText = "Скопировано успешно";
229
- }}, function(err) {{
230
- btn.innerText = "Ошибка при копировании";
231
- }});
232
- }}
233
- </script>
234
- </head>
235
- <body>
236
- <button id="copy-btn" onclick="copyToClipboard()" style="padding: 8px 16px; font-size: 16px;">Скопировать код</button>
237
- </body>
238
- </html>
239
- """
240
- components.html(copy_button_html, height=80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
 
 
 
 
 
 
2
  import requests
3
+ import urllib.parse
4
+ import hashlib
5
+ import base64
6
+ import secrets
7
+
8
+ def generate_code_verifier(length=128):
9
+ return secrets.token_urlsafe(length)[:length]
10
+
11
+ def generate_code_challenge(code_verifier):
12
+ hashed = hashlib.sha256(code_verifier.encode('utf-8')).digest()
13
+ return base64.urlsafe_b64encode(hashed).decode('utf-8').rstrip('=')
14
+
15
+ st.title("VK ID Authorization with PKCE — Manual Input for PKCE Parameters")
16
+
17
+ st.markdown("""
18
+ Введите вручную сгенерированные вами значения **code_verifier** и **code_challenge**.
19
+ **Пошагово:**
20
+ 1. Сгенерируйте с вашей стороны `code_verifier` и вычислите `code_challenge` (например, через Python или онлайн-инструмент).
21
+ 2. Вставьте их ниже.
22
+ 3. Нажмите кнопку для перехода на страницу авторизации.
23
+ 4. После авторизации VK вернёт вас на redirect_uri с параметрами `?code=...&device_id=...`.
24
+ 5. Скопируйте полученные `code` и `device_id` в соответствующие поля и нажмите «Обменять code», чтобы получить токены.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ # Генерация code_verifier и code_challenge
28
+ code_verifier = generate_code_verifier()
29
+ code_challenge = generate_code_challenge(code_verifier)
30
+
31
+ # Поля для ручного ввода PKCE-параметров
32
+ code_verifier_input = st.text_input("Введите code_verifier", value=code_verifier)
33
+ code_challenge_input = st.text_input("Введите code_challenge", value=code_challenge)
34
+
35
+ # Параметры приложения
36
+ client_id = st.text_input("client_id", value="53202706")
37
+ client_secret = st.text_input("client_secret", value="piaMXHEDj0afa4QObJCD")
38
+ redirect_uri = st.text_input("redirect_uri", value="https://huggingface.co/spaces/PRAnalytics/prtooltest")
39
+ scope = st.text_input("scope", value="wall")
40
+
41
+ st.markdown("### 1) Авторизация")
42
+ auth_params = {
43
+ "client_id": client_id,
44
+ "redirect_uri": redirect_uri,
45
+ "response_type": "code",
46
+ "scope": scope,
47
+ "state": "my_state",
48
+ "code_challenge": code_challenge,
49
+ "code_challenge_method": "S256",
50
+ "response_mode": "query"
51
+ }
52
+ auth_url = "https://id.vk.com/authorize?" + urllib.parse.urlencode(auth_params, safe="-._~")
53
+ st.markdown(f"[Перейти к авторизации]({auth_url})")
54
+
55
+ st.markdown("""
56
+ После авторизации VK вернёт вас на redirect_uri с параметрами:
57
+ `?code=...&device_id=...&state=...`
58
+ """)
59
 
60
+ # Поля для ручного ввода полученных значений
61
+ user_code = st.text_input("Введите полный code, полученный из URL", value="")
62
+ user_device_id = st.text_input("Введите полный device_id, полученный из URL", value="")
63
+
64
+ st.write("Введённый code:", user_code)
65
+ st.write("Введённый device_id:", user_device_id)
66
+
67
+ # Отладочный вывод перед обменом
68
+ st.markdown("#### Отладочный вывод перед обменом:")
69
+ st.write("client_id:", client_id)
70
+ st.write("client_secret:", client_secret)
71
+ st.write("redirect_uri:", redirect_uri)
72
+ st.write("scope:", scope)
73
+ st.write("code_verifier:", code_verifier)
74
+ st.write("code_challenge:", code_challenge)
75
+
76
+ if user_code:
77
+ st.markdown("### 2) Обмен кода на токены")
78
+ if st.button("Обменять code"):
79
+ url = "https://id.vk.com/access_token" # Правильный endpoint для обмена токенов
80
+ data = {
81
+ "grant_type": "authorization_code",
82
+ "client_id": client_id,
83
+ "client_secret": client_secret,
84
+ "redirect_uri": redirect_uri,
85
+ "code": user_code,
86
+ "code_verifier": code_verifier
87
+ }
88
+ if user_device_id:
89
+ data["device_id"] = user_device_id
90
+ st.write("Отправляем запрос на:", url)
91
+ st.write("Параметры запроса:", data)
92
+ resp = requests.post(url, data=data)
93
+ st.write("Status code:", resp.status_code)
94
+ st.write("Response text:", resp.text)
95
+ try:
96
+ tokens = resp.json()
97
+ st.write("Ответ (JSON):")
98
+ st.json(tokens)
99
+ except Exception as e:
100
+ st.error(f"Ошибка при декодировании ответа: {e}")
101
+ st.text(resp.text)