on main help sign in
Forkgram 13.0.5 snapshot

Source: https://github.com/forkgram/TelegramAndroid/commit/97371ac75be9a1660d5c249d8beedd6883b66241 Forkgram-Commit: 97371ac75be9a1660d5c249d8beedd6883b66241 Forkgram-Version: 13.0.5 Telegram-Base: 0aae959806c57be8ad457980f2a5c6277ff18635 (update to 13.0.3) Tree is Forkgram's dev branch at that commit with the .github folder removed.

commitad9770fa6d952f421e4ce6b8d9947da9692ce154 authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> dateFri 23 Oct 2026, 17:15 (2 weeks ago) parent86362bd
14 files changed+347−187
TMessagesProj/src/main/java/org/telegram/messenger/forkgram/VoiceTimecodesConfig.kt+156−109 TMessagesProj/src/main/java/org/telegram/ui/VoiceTimecodesSettingsActivity.java+101−43 TMessagesProj/src/main/res/values-ar/strings.xml+8−3 TMessagesProj/src/main/res/values-de/strings.xml+8−3 TMessagesProj/src/main/res/values-es/strings.xml+8−3 TMessagesProj/src/main/res/values-fa/strings.xml+8−3 TMessagesProj/src/main/res/values-it/strings.xml+8−3 TMessagesProj/src/main/res/values-ko/strings.xml+8−3 TMessagesProj/src/main/res/values-nl/strings.xml+8−3 TMessagesProj/src/main/res/values-pt-rBR/strings.xml+8−3 TMessagesProj/src/main/res/values-ru/strings.xml+8−3 TMessagesProj/src/main/res/values-uk/strings.xml+8−3 TMessagesProj/src/main/res/values/strings.xml+8−3 gradle.properties+2−2
TMessagesProj/src/main/java/org/telegram/messenger/forkgram/VoiceTimecodesConfig.kt+156−109
@@ -1,14 +1,14 @@
11package org.telegram.messenger.forkgram
22
33import android.content.SharedPreferences
4−import android.os.Environment
4+import android.util.Base64
55import org.json.JSONObject
6−import org.telegram.messenger.ApplicationLoader
76import org.telegram.messenger.FileLog
87import org.telegram.messenger.MessagesController
98import org.telegram.messenger.UserConfig
10−import java.io.File
11−import java.net.URL
9+import java.io.ByteArrayOutputStream
10+import java.util.zip.Deflater
11+import java.util.zip.Inflater
1212
1313object VoiceTimecodesConfig {
1414
@@ -21,6 +21,20 @@ object VoiceTimecodesConfig {
2121 const val PREF_MIN_DURATION = "voice_timecodes_min_duration"
2222 const val PREF_PROMPT = "voice_timecodes_prompt"
2323
24+ private const val CODE_PREFIX = "FVT1:"
25+ private const val MAX_DECODED_SIZE = 256 * 1024
26+ private val BASE64_FLAGS = Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
27+ private val PREF_KEYS = setOf(
28+ PREF_ENABLED,
29+ PREF_STT_URL,
30+ PREF_STT_TOKEN,
31+ PREF_LLM_URL,
32+ PREF_LLM_TOKEN,
33+ PREF_LLM_MODEL,
34+ PREF_MIN_DURATION,
35+ PREF_PROMPT
36+ )
37+
2438 const val DEFAULT_LLM_MODEL = "gpt-4o-mini"
2539 const val DEFAULT_MIN_DURATION = 10
2640
@@ -40,7 +54,6 @@ object VoiceTimecodesConfig {
4054
4155 @JvmStatic
4256 fun isEnabled(): Boolean {
43− ensureAutoImport()
4457 return prefs().getBoolean(PREF_ENABLED, false)
4558 }
4659
@@ -51,31 +64,26 @@ object VoiceTimecodesConfig {
5164
5265 @JvmStatic
5366 fun sttUrl(): String {
54− ensureAutoImport()
5567 return prefs().getString(PREF_STT_URL, "")?.trim() ?: ""
5668 }
5769
5870 @JvmStatic
5971 fun sttToken(): String {
60− ensureAutoImport()
6172 return prefs().getString(PREF_STT_TOKEN, "")?.trim() ?: ""
6273 }
6374
6475 @JvmStatic
6576 fun llmUrl(): String {
66− ensureAutoImport()
6777 return prefs().getString(PREF_LLM_URL, "")?.trim() ?: ""
6878 }
6979
7080 @JvmStatic
7181 fun llmToken(): String {
72− ensureAutoImport()
7382 return prefs().getString(PREF_LLM_TOKEN, "")?.trim() ?: ""
7483 }
7584
7685 @JvmStatic
7786 fun llmModel(): String {
78− ensureAutoImport()
7987 val m = prefs().getString(PREF_LLM_MODEL, DEFAULT_LLM_MODEL)?.trim() ?: ""
8088 return if (m.isEmpty()) DEFAULT_LLM_MODEL else m
8189 }
@@ -125,62 +133,57 @@ object VoiceTimecodesConfig {
125133 .apply()
126134 }
127135
128− @Volatile
129− private var autoImportChecked = false
130−
131136 @JvmStatic
132− fun ensureAutoImport() {
133− if (autoImportChecked) return
134− autoImportChecked = true
137+ fun exportToString(): String {
138+ return exportToString(
139+ isEnabled(), sttUrl(), sttToken(), llmUrl(), llmToken(), llmModel(), minDuration(), promptTemplate()
140+ )
141+ }
135142
136− val currentStt = prefs().getString(PREF_STT_URL, null)
137− val currentLlm = prefs().getString(PREF_LLM_URL, null)
138− if (!currentStt.isNullOrEmpty() && !currentLlm.isNullOrEmpty()) {
139− return
140− }
143+ @JvmStatic
144+ fun exportToString(
145+ enabled: Boolean,
146+ sttUrl: String,
147+ sttToken: String,
148+ llmUrl: String,
149+ llmToken: String,
150+ llmModel: String,
151+ minDuration: Int,
152+ prompt: String
153+ ): String {
154+ val root = JSONObject()
155+ root.put("v", 1)
156+ root.put("enabled", enabled)
157+ root.put("stt_url", sttUrl.trim())
158+ root.put("stt_token", sttToken.trim())
159+ root.put("llm_url", llmUrl.trim())
160+ root.put("llm_token", llmToken.trim())
161+ root.put("llm_model", llmModel.trim())
162+ root.put("min_duration", if (minDuration <= 0) DEFAULT_MIN_DURATION else minDuration)
163+ root.put("prompt", if (prompt.isBlank()) DEFAULT_PROMPT else prompt)
164+ val encoded = Base64.encodeToString(compress(root.toString().toByteArray(Charsets.UTF_8)), BASE64_FLAGS)
165+ return CODE_PREFIX + encoded
166+ }
141167
142− val candidateDirs = mutableListOf<File>()
143− try {
144− val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
145− if (downloadDir != null) {
146− candidateDirs.add(downloadDir)
147− }
148− } catch (e: Exception) {
149− FileLog.e(e)
168+ @JvmStatic
169+ fun importFromString(text: String): Boolean {
170+ val trimmed = text.trim()
171+ if (trimmed.isEmpty()) {
172+ return false
150173 }
151− val context = ApplicationLoader.applicationContext
152− if (context != null) {
153− try {
154− val extFiles = context.getExternalFilesDir(null)
155− if (extFiles != null) {
156− candidateDirs.add(extFiles)
157− }
174+ val prefixIndex = trimmed.indexOf(CODE_PREFIX, ignoreCase = true)
175+ if (prefixIndex >= 0) {
176+ return try {
177+ val payload = trimmed.substring(prefixIndex + CODE_PREFIX.length)
178+ .filter { it.isLetterOrDigit() || it == '-' || it == '_' }
179+ val json = String(inflate(Base64.decode(payload, BASE64_FLAGS)), Charsets.UTF_8)
180+ importFromJson(json)
158181 } catch (e: Exception) {
159− FileLog.e(e)
160− }
161− }
162−
163− for (dir in candidateDirs) {
164− val f1 = File(dir, "voice_timecodes.json")
165− if (f1.exists() && f1.canRead() && importFromFile(f1)) {
166− return
182+ FileLog.e("VoiceTimecodesConfig code import error: $e")
183+ false
167184 }
168− val f2 = File(dir, "voice_timecodes_backup.json")
169− if (f2.exists() && f2.canRead() && importFromFile(f2)) {
170− return
171− }
172− }
173− }
174−
175− @JvmStatic
176− fun importFromFile(file: File): Boolean {
177− return try {
178− val text = file.readText(Charsets.UTF_8)
179− importFromJson(text)
180− } catch (e: Exception) {
181− FileLog.e("VoiceTimecodesConfig import error from ${file.absolutePath}: $e")
182− false
183185 }
186+ return importFromJson(trimmed)
184187 }
185188
186189 @JvmStatic
@@ -188,68 +191,112 @@ object VoiceTimecodesConfig {
188191 return try {
189192 val root = JSONObject(jsonString)
190193 val editor = prefs().edit()
194+ var updated = false
191195
192− if (root.has("prefs")) {
193− val prefsObj = root.optJSONObject("prefs")
194− val mainConfig = prefsObj?.optJSONObject("mainconfig")
195− if (mainConfig != null) {
196− val it = mainConfig.keys()
197− while (it.hasNext()) {
198− val key = it.next()
199− val item = mainConfig.optJSONObject(key) ?: continue
200− val t = item.optString("t")
201− when (t) {
202− "b" -> editor.putBoolean(key, item.optBoolean("v"))
203− "s" -> editor.putString(key, item.optString("v"))
204− "i" -> editor.putInt(key, item.optInt("v"))
196+ val mainConfig = root.optJSONObject("prefs")?.optJSONObject("mainconfig")
197+ if (mainConfig != null) {
198+ val keys = mainConfig.keys()
199+ while (keys.hasNext()) {
200+ val key = keys.next()
201+ if (key !in PREF_KEYS) {
202+ continue
203+ }
204+ val item = mainConfig.optJSONObject(key) ?: continue
205+ when (item.optString("t")) {
206+ "b" -> {
207+ editor.putBoolean(key, item.optBoolean("v"))
208+ updated = true
209+ }
210+ "s" -> {
211+ editor.putString(key, item.optString("v"))
212+ updated = true
213+ }
214+ "i" -> {
215+ editor.putInt(key, item.optInt("v"))
216+ updated = true
205217 }
206218 }
207− editor.apply()
208− return true
219+ }
220+ } else {
221+ if (root.has("enabled")) {
222+ editor.putBoolean(PREF_ENABLED, root.optBoolean("enabled"))
223+ updated = true
224+ }
225+ if (root.has("stt_url")) {
226+ editor.putString(PREF_STT_URL, root.optString("stt_url"))
227+ updated = true
228+ }
229+ if (root.has("stt_token")) {
230+ editor.putString(PREF_STT_TOKEN, root.optString("stt_token"))
231+ updated = true
232+ }
233+ if (root.has("llm_url")) {
234+ editor.putString(PREF_LLM_URL, root.optString("llm_url"))
235+ updated = true
236+ }
237+ if (root.has("llm_token")) {
238+ editor.putString(PREF_LLM_TOKEN, root.optString("llm_token"))
239+ updated = true
240+ }
241+ if (root.has("llm_model")) {
242+ editor.putString(PREF_LLM_MODEL, root.optString("llm_model"))
243+ updated = true
244+ }
245+ if (root.has("min_duration")) {
246+ editor.putInt(PREF_MIN_DURATION, root.optInt("min_duration", DEFAULT_MIN_DURATION))
247+ updated = true
248+ }
249+ if (root.has("prompt")) {
250+ editor.putString(PREF_PROMPT, root.optString("prompt"))
251+ updated = true
209252 }
210253 }
211254
212− var updated = false
213− if (root.has("enabled")) {
214− editor.putBoolean(PREF_ENABLED, root.getBoolean("enabled"))
215− updated = true
216− }
217− if (root.has("stt_url")) {
218− editor.putString(PREF_STT_URL, root.getString("stt_url"))
219− updated = true
220− }
221− if (root.has("stt_token")) {
222− editor.putString(PREF_STT_TOKEN, root.getString("stt_token"))
223− updated = true
224− }
225− if (root.has("llm_url")) {
226− editor.putString(PREF_LLM_URL, root.getString("llm_url"))
227− updated = true
228− }
229− if (root.has("llm_token")) {
230− editor.putString(PREF_LLM_TOKEN, root.getString("llm_token"))
231− updated = true
232− }
233− if (root.has("llm_model")) {
234− editor.putString(PREF_LLM_MODEL, root.getString("llm_model"))
235− updated = true
236− }
237− if (root.has("min_duration")) {
238− editor.putInt(PREF_MIN_DURATION, root.getInt("min_duration"))
239− updated = true
240− }
241− if (root.has("prompt")) {
242− editor.putString(PREF_PROMPT, root.getString("prompt"))
243− updated = true
244− }
245255 if (updated) {
246256 editor.apply()
247− return true
248257 }
249− false
258+ updated
250259 } catch (e: Exception) {
251260 FileLog.e(e)
252261 false
253262 }
254263 }
264+
265+ private fun compress(data: ByteArray): ByteArray {
266+ val deflater = Deflater(Deflater.BEST_COMPRESSION)
267+ val out = ByteArrayOutputStream()
268+ val buffer = ByteArray(4096)
269+ try {
270+ deflater.setInput(data)
271+ deflater.finish()
272+ while (!deflater.finished()) {
273+ out.write(buffer, 0, deflater.deflate(buffer))
274+ }
275+ } finally {
276+ deflater.end()
277+ }
278+ return out.toByteArray()
279+ }
280+
281+ private fun inflate(data: ByteArray): ByteArray {
282+ val inflater = Inflater()
283+ val out = ByteArrayOutputStream()
284+ val buffer = ByteArray(4096)
285+ try {
286+ inflater.setInput(data)
287+ while (!inflater.finished()) {
288+ val count = inflater.inflate(buffer)
289+ if (count == 0) {
290+ throw IllegalArgumentException("Truncated settings code")
291+ }
292+ out.write(buffer, 0, count)
293+ if (out.size() > MAX_DECODED_SIZE) {
294+ throw IllegalArgumentException("Settings code is too large")
295+ }
296+ }
297+ } finally {
298+ inflater.end()
299+ }
300+ return out.toByteArray()
301+ }
255302}
TMessagesProj/src/main/java/org/telegram/ui/VoiceTimecodesSettingsActivity.java+101−43
@@ -1,10 +1,11 @@
11package org.telegram.ui;
22
3+import android.content.ClipData;
4+import android.content.ClipboardManager;
35import android.content.Context;
4−import android.content.SharedPreferences;
5−import android.os.Environment;
66import android.text.InputType;
77import android.text.method.PasswordTransformationMethod;
8+import android.util.TypedValue;
89import android.view.Gravity;
910import android.view.View;
1011import android.view.inputmethod.EditorInfo;
@@ -16,7 +17,6 @@ import android.widget.Toast;
1617
1718import org.telegram.messenger.AndroidUtilities;
1819import org.telegram.messenger.LocaleController;
19−import org.telegram.messenger.MessagesController;
2020import org.telegram.messenger.R;
2121import org.telegram.messenger.forkgram.VoiceTimecodesConfig;
2222import org.telegram.ui.ActionBar.ActionBar;
@@ -27,8 +27,6 @@ import org.telegram.ui.Components.EditTextBoldCursor;
2727import org.telegram.ui.Components.LayoutHelper;
2828import org.telegram.ui.Components.OutlineEditText;
2929
30−import java.io.File;
31−
3230public class VoiceTimecodesSettingsActivity extends BaseFragment {
3331
3432 private static final int DONE_BUTTON = 1;
@@ -41,6 +39,7 @@ public class VoiceTimecodesSettingsActivity extends BaseFragment {
4139 private EditTextBoldCursor llmModelField;
4240 private EditTextBoldCursor minDurationField;
4341 private EditTextBoldCursor promptField;
42+ private EditTextBoldCursor exportCodeField;
4443
4544 private EditTextBoldCursor lastField;
4645
@@ -138,13 +137,44 @@ public class VoiceTimecodesSettingsActivity extends BaseFragment {
138137 resetPrompt.setOnClickListener(v -> promptField.setText(VoiceTimecodesConfig.DEFAULT_PROMPT));
139138 content.addView(resetPrompt, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
140139
140+ TextView codeInfo = new TextView(context);
141+ codeInfo.setText(LocaleController.getString(R.string.VoiceTimecodesCodeInfo));
142+ codeInfo.setTextSize(14);
143+ codeInfo.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2));
144+ content.addView(codeInfo, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 8));
145+
146+ OutlineEditText codeOutline = new OutlineEditText(context);
147+ codeOutline.setHint(LocaleController.getString(R.string.VoiceTimecodesCodeTitle));
148+ exportCodeField = codeOutline.getEditText();
149+ exportCodeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
150+ exportCodeField.setHint(LocaleController.getString(R.string.VoiceTimecodesCodeHint));
151+ exportCodeField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
152+ exportCodeField.setImeOptions(EditorInfo.IME_ACTION_DONE);
153+ exportCodeField.setPadding(AndroidUtilities.dp(15), AndroidUtilities.dp(16), AndroidUtilities.dp(15), AndroidUtilities.dp(16));
154+ content.addView(codeOutline, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 12));
155+
156+ LinearLayout codeActions = new LinearLayout(context);
157+ codeActions.setOrientation(LinearLayout.HORIZONTAL);
158+
159+ TextView exportButton = new TextView(context);
160+ exportButton.setText(LocaleController.getString(R.string.VoiceTimecodesCodeExport));
161+ exportButton.setTextSize(14);
162+ exportButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4));
163+ exportButton.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
164+ exportButton.setPadding(0, 0, 0, AndroidUtilities.dp(16));
165+ exportButton.setOnClickListener(v -> exportCode());
166+ codeActions.addView(exportButton, LayoutHelper.createLinear(0, LayoutHelper.WRAP_CONTENT, 1f));
167+
141168 TextView importButton = new TextView(context);
142− importButton.setText(LocaleController.getString(R.string.VoiceTimecodesImportFile));
169+ importButton.setText(LocaleController.getString(R.string.VoiceTimecodesCodeImport));
143170 importButton.setTextSize(14);
144171 importButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4));
145− importButton.setPadding(0, AndroidUtilities.dp(6), 0, AndroidUtilities.dp(24));
146− importButton.setOnClickListener(v -> tryImportFromFile());
147− content.addView(importButton, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
172+ importButton.setGravity(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT);
173+ importButton.setPadding(0, 0, 0, AndroidUtilities.dp(16));
174+ importButton.setOnClickListener(v -> importCode());
175+ codeActions.addView(importButton, LayoutHelper.createLinear(0, LayoutHelper.WRAP_CONTENT, 1f));
176+
177+ content.addView(codeActions, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
148178
149179 return fragmentView;
150180 }
@@ -172,52 +202,80 @@ public class VoiceTimecodesSettingsActivity extends BaseFragment {
172202 lastField = field;
173203 }
174204
175− private void tryImportFromFile() {
176− java.util.List<File> candidates = new java.util.ArrayList<>();
205+ private void exportCode() {
206+ int minDuration = VoiceTimecodesConfig.DEFAULT_MIN_DURATION;
177207 try {
178− File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
179− if (downloadDir != null) {
180− candidates.add(new File(downloadDir, "voice_timecodes.json"));
181− candidates.add(new File(downloadDir, "voice_timecodes_backup.json"));
182− }
208+ minDuration = Integer.parseInt(minDurationField.getText().toString().trim());
183209 } catch (Exception ignore) {
184210 }
185− if (getContext() != null) {
186− try {
187− File ext = getContext().getExternalFilesDir(null);
188− if (ext != null) {
189− candidates.add(new File(ext, "voice_timecodes.json"));
190− candidates.add(new File(ext, "voice_timecodes_backup.json"));
191− }
192− } catch (Exception ignore) {
211+ String code = VoiceTimecodesConfig.exportToString(
212+ enabledCell.isChecked(),
213+ sttUrlField.getText().toString(),
214+ sttTokenField.getText().toString(),
215+ llmUrlField.getText().toString(),
216+ llmTokenField.getText().toString(),
217+ llmModelField.getText().toString(),
218+ minDuration,
219+ promptField.getText().toString()
220+ );
221+ exportCodeField.setText(code);
222+ exportCodeField.setSelection(0);
223+ if (getParentActivity() != null) {
224+ ClipboardManager clipboard = (ClipboardManager) getParentActivity().getSystemService(Context.CLIPBOARD_SERVICE);
225+ if (clipboard != null) {
226+ clipboard.setPrimaryClip(ClipData.newPlainText("Forkgram voice timecodes", code));
193227 }
228+ Toast.makeText(getParentActivity(), LocaleController.getString(R.string.VoiceTimecodesCodeCopied), Toast.LENGTH_SHORT).show();
194229 }
195− boolean success = false;
196− for (File target : candidates) {
197− if (target.exists() && target.canRead() && VoiceTimecodesConfig.importFromFile(target)) {
198− success = true;
199− break;
200− }
230+ }
231+
232+ private void importCode() {
233+ String clipboardText = readClipboard();
234+ String fieldText = exportCodeField.getText().toString().trim();
235+ boolean success = !clipboardText.isEmpty() && VoiceTimecodesConfig.importFromString(clipboardText);
236+ if (!success && !fieldText.isEmpty()) {
237+ success = VoiceTimecodesConfig.importFromString(fieldText);
201238 }
202239 if (success) {
203− enabledCell.setChecked(VoiceTimecodesConfig.isEnabled());
204− sttUrlField.setText(VoiceTimecodesConfig.sttUrl());
205− sttTokenField.setText(VoiceTimecodesConfig.sttToken());
206− llmUrlField.setText(VoiceTimecodesConfig.llmUrl());
207− llmTokenField.setText(VoiceTimecodesConfig.llmToken());
208− llmModelField.setText(VoiceTimecodesConfig.llmModel());
209− minDurationField.setText(String.valueOf(VoiceTimecodesConfig.minDuration()));
210− promptField.setText(VoiceTimecodesConfig.promptTemplate());
211− if (getParentActivity() != null) {
212− Toast.makeText(getParentActivity(), LocaleController.getString(R.string.VoiceTimecodesImportSuccess), Toast.LENGTH_SHORT).show();
213− }
214− } else {
240+ refreshFromConfig();
241+ String code = VoiceTimecodesConfig.exportToString();
242+ exportCodeField.setText(code);
243+ exportCodeField.setSelection(0);
215244 if (getParentActivity() != null) {
216− Toast.makeText(getParentActivity(), LocaleController.getString(R.string.VoiceTimecodesImportFailed), Toast.LENGTH_SHORT).show();
245+ Toast.makeText(getParentActivity(), LocaleController.getString(R.string.VoiceTimecodesCodeImported), Toast.LENGTH_SHORT).show();
217246 }
247+ } else if (getParentActivity() != null) {
248+ Toast.makeText(getParentActivity(), LocaleController.getString(R.string.VoiceTimecodesCodeImportFailed), Toast.LENGTH_SHORT).show();
218249 }
219250 }
220251
252+ private String readClipboard() {
253+ if (getParentActivity() == null) {
254+ return "";
255+ }
256+ ClipboardManager clipboard = (ClipboardManager) getParentActivity().getSystemService(Context.CLIPBOARD_SERVICE);
257+ if (clipboard == null || !clipboard.hasPrimaryClip() || clipboard.getPrimaryClip() == null) {
258+ return "";
259+ }
260+ ClipData clipData = clipboard.getPrimaryClip();
261+ if (clipData.getItemCount() == 0) {
262+ return "";
263+ }
264+ CharSequence text = clipData.getItemAt(0).coerceToText(getParentActivity());
265+ return text != null ? text.toString().trim() : "";
266+ }
267+
268+ private void refreshFromConfig() {
269+ enabledCell.setChecked(VoiceTimecodesConfig.isEnabled());
270+ sttUrlField.setText(VoiceTimecodesConfig.sttUrl());
271+ sttTokenField.setText(VoiceTimecodesConfig.sttToken());
272+ llmUrlField.setText(VoiceTimecodesConfig.llmUrl());
273+ llmTokenField.setText(VoiceTimecodesConfig.llmToken());
274+ llmModelField.setText(VoiceTimecodesConfig.llmModel());
275+ minDurationField.setText(String.valueOf(VoiceTimecodesConfig.minDuration()));
276+ promptField.setText(VoiceTimecodesConfig.promptTemplate());
277+ }
278+
221279 private void save() {
222280 boolean enabled = enabledCell.isChecked();
223281 String sttUrl = sttUrlField.getText().toString().trim();
TMessagesProj/src/main/res/values-ar/strings.xml+8−3
@@ -1520,9 +1520,14 @@
15201520 <string name="VoiceTimecodesMinDuration">الحد الأدنى لمدة الرسالة الصوتية (بالثواني)</string>
15211521 <string name="VoiceTimecodesPrompt">قالب التوجيه ({transcript} = النص)</string>
15221522 <string name="VoiceTimecodesResetPrompt">إعادة التوجيه إلى الوضع الافتراضي</string>
1523− <string name="VoiceTimecodesImportFile">استيراد من /sdcard/Download/voice_timecodes.json</string>
1524− <string name="VoiceTimecodesImportSuccess">تم استيراد الإعدادات بنجاح</string>
1525− <string name="VoiceTimecodesImportFailed">الملف غير موجود أو التنسيق غير صالح</string>
1523+ <string name="VoiceTimecodesCodeTitle">رمز الإعدادات</string>
1524+ <string name="VoiceTimecodesCodeInfo">رمز من سطر واحد يحتوي على جميع إعدادات الطوابع الزمنية للصوت، بما في ذلك مفاتيح API. انسخه إلى الرسائل المحفوظة، ثم على جهاز آخر انسخه مرة أخرى واضغط على «استيراد الرمز». احتفظ بالرمز خاصًا.</string>
1525+ <string name="VoiceTimecodesCodeHint">اضغط على «تصدير» أو الصق رمزًا من جهاز آخر</string>
1526+ <string name="VoiceTimecodesCodeExport">تصدير إلى الحافظة</string>
1527+ <string name="VoiceTimecodesCodeImport">استيراد الرمز</string>
1528+ <string name="VoiceTimecodesCodeCopied">تم نسخ رمز الإعدادات</string>
1529+ <string name="VoiceTimecodesCodeImported">تم استيراد الإعدادات</string>
1530+ <string name="VoiceTimecodesCodeImportFailed">رمز الإعدادات فارغ أو غير صالح</string>
15261531 <string name="VoiceTimecodesSaved">تم حفظ إعدادات أكواد توقيت الصوت</string>
15271532 <string name="VoiceTimecodesDone">تمت إضافة أكواد التوقيت</string>
15281533 <string name="VoiceTimecodesGenerate">إضافة أكواد التوقيت</string>
TMessagesProj/src/main/res/values-de/strings.xml+8−3
@@ -1652,9 +1652,14 @@
16521652 <string name="VoiceTimecodesMinDuration">Mindestdauer der Sprachnachricht (Sekunden)</string>
16531653 <string name="VoiceTimecodesPrompt">Prompt-Vorlage ({transcript} = Text)</string>
16541654 <string name="VoiceTimecodesResetPrompt">Prompt zurücksetzen</string>
1655− <string name="VoiceTimecodesImportFile">Aus /sdcard/Download/voice_timecodes.json importieren</string>
1656− <string name="VoiceTimecodesImportSuccess">Einstellungen erfolgreich importiert</string>
1657− <string name="VoiceTimecodesImportFailed">Datei nicht gefunden oder ungültiges Format</string>
1655+ <string name="VoiceTimecodesCodeTitle">Einstellungscode</string>
1656+ <string name="VoiceTimecodesCodeInfo">Ein einzeiliger Code mit allen Einstellungen für Sprach-Zeitstempel, einschließlich API-Schlüsseln. Kopieren Sie ihn in die Gespeicherten Nachrichten und kopieren Sie ihn auf einem anderen Gerät erneut und tippen Sie auf „Code importieren“. Halten Sie den Code geheim.</string>
1657+ <string name="VoiceTimecodesCodeHint">Tippen Sie auf „Exportieren“ oder fügen Sie einen Code von einem anderen Gerät ein</string>
1658+ <string name="VoiceTimecodesCodeExport">In Zwischenablage exportieren</string>
1659+ <string name="VoiceTimecodesCodeImport">Code importieren</string>
1660+ <string name="VoiceTimecodesCodeCopied">Einstellungscode kopiert</string>
1661+ <string name="VoiceTimecodesCodeImported">Einstellungen importiert</string>
1662+ <string name="VoiceTimecodesCodeImportFailed">Ungültiger oder leerer Einstellungscode</string>
16581663 <string name="VoiceTimecodesSaved">Zeitmarken-Einstellungen gespeichert</string>
16591664 <string name="VoiceTimecodesDone">Zeitmarken hinzugefügt</string>
16601665 <string name="VoiceTimecodesGenerate">Zeitmarken hinzufügen</string>
TMessagesProj/src/main/res/values-es/strings.xml+8−3
@@ -1592,9 +1592,14 @@
15921592 <string name="VoiceTimecodesMinDuration">Duración mínima del mensaje de voz (segundos)</string>
15931593 <string name="VoiceTimecodesPrompt">Plantilla del prompt ({transcript} = texto)</string>
15941594 <string name="VoiceTimecodesResetPrompt">Restablecer el prompt</string>
1595− <string name="VoiceTimecodesImportFile">Importar desde /sdcard/Download/voice_timecodes.json</string>
1596− <string name="VoiceTimecodesImportSuccess">Ajustes importados correctamente</string>
1597− <string name="VoiceTimecodesImportFailed">Archivo no encontrado o formato no válido</string>
1595+ <string name="VoiceTimecodesCodeTitle">Código de ajustes</string>
1596+ <string name="VoiceTimecodesCodeInfo">Un código de una sola línea con todos los ajustes de marcas de tiempo de voz, incluidas las claves de API. Cópialo en Mensajes guardados y, en otro dispositivo, vuelve a copiarlo y pulsa «Importar código». Mantén el código en privado.</string>
1597+ <string name="VoiceTimecodesCodeHint">Toca «Exportar» o pega un código de otro dispositivo</string>
1598+ <string name="VoiceTimecodesCodeExport">Exportar al portapapeles</string>
1599+ <string name="VoiceTimecodesCodeImport">Importar código</string>
1600+ <string name="VoiceTimecodesCodeCopied">Código de ajustes copiado</string>
1601+ <string name="VoiceTimecodesCodeImported">Ajustes importados</string>
1602+ <string name="VoiceTimecodesCodeImportFailed">Código de ajustes no válido o vacío</string>
15981603 <string name="VoiceTimecodesSaved">Ajustes de marcas de tiempo guardados</string>
15991604 <string name="VoiceTimecodesDone">Marcas de tiempo añadidas</string>
16001605 <string name="VoiceTimecodesGenerate">Añadir marcas de tiempo</string>
TMessagesProj/src/main/res/values-fa/strings.xml+8−3
@@ -281,9 +281,14 @@
281281 <string name="VoiceTimecodesMinDuration">حداقل مدت پیام صوتی (ثانیه)</string>
282282 <string name="VoiceTimecodesPrompt">قالب دستور ({transcript} = متن)</string>
283283 <string name="VoiceTimecodesResetPrompt">بازنشانی دستور به حالت پیش‌فرض</string>
284− <string name="VoiceTimecodesImportFile">درون‌ریزی از /sdcard/Download/voice_timecodes.json</string>
285− <string name="VoiceTimecodesImportSuccess">تنظیمات با موفقیت درون‌ریزی شد</string>
286− <string name="VoiceTimecodesImportFailed">فایل یافت نشد یا قالب نامعتبر است</string>
284+ <string name="VoiceTimecodesCodeTitle">کد تنظیمات</string>
285+ <string name="VoiceTimecodesCodeInfo">کدی تک‌خطی با همهٔ تنظیمات مهرهای زمانی صدا، شامل کلیدهای API. آن را در پیام‌های ذخیره‌شده کپی کنید، سپس در دستگاه دیگر دوباره کپی کنید و «درون‌ریزی کد» را بزنید. کد را خصوصی نگه دارید.</string>
286+ <string name="VoiceTimecodesCodeHint">روی «برون‌ریزی» بزنید یا کدی از دستگاه دیگر بچسبانید</string>
287+ <string name="VoiceTimecodesCodeExport">برون‌ریزی به کلیپ‌بورد</string>
288+ <string name="VoiceTimecodesCodeImport">درون‌ریزی کد</string>
289+ <string name="VoiceTimecodesCodeCopied">کد تنظیمات کپی شد</string>
290+ <string name="VoiceTimecodesCodeImported">تنظیمات درون‌ریزی شد</string>
291+ <string name="VoiceTimecodesCodeImportFailed">کد تنظیمات خالی یا نامعتبر است</string>
287292 <string name="VoiceTimecodesSaved">تنظیمات کدهای زمانی صدا ذخیره شد</string>
288293 <string name="VoiceTimecodesDone">کدهای زمانی افزوده شد</string>
289294 <string name="VoiceTimecodesGenerate">افزودن کدهای زمانی</string>
TMessagesProj/src/main/res/values-it/strings.xml+8−3
@@ -1814,9 +1814,14 @@
18141814 <string name="VoiceTimecodesMinDuration">Durata minima del vocale (secondi)</string>
18151815 <string name="VoiceTimecodesPrompt">Modello di prompt ({transcript} = testo)</string>
18161816 <string name="VoiceTimecodesResetPrompt">Ripristina il prompt</string>
1817− <string name="VoiceTimecodesImportFile">Importa da /sdcard/Download/voice_timecodes.json</string>
1818− <string name="VoiceTimecodesImportSuccess">Impostazioni importate correttamente</string>
1819− <string name="VoiceTimecodesImportFailed">File non trovato o formato non valido</string>
1817+ <string name="VoiceTimecodesCodeTitle">Codice impostazioni</string>
1818+ <string name="VoiceTimecodesCodeInfo">Un codice su una sola riga con tutte le impostazioni dei timestamp vocali, incluse le chiavi API. Copialo nei Messaggi salvati, poi su un altro dispositivo copialo di nuovo e tocca «Importa codice». Mantieni il codice privato.</string>
1819+ <string name="VoiceTimecodesCodeHint">Tocca «Esporta» o incolla un codice da un altro dispositivo</string>
1820+ <string name="VoiceTimecodesCodeExport">Esporta negli appunti</string>
1821+ <string name="VoiceTimecodesCodeImport">Importa codice</string>
1822+ <string name="VoiceTimecodesCodeCopied">Codice impostazioni copiato</string>
1823+ <string name="VoiceTimecodesCodeImported">Impostazioni importate</string>
1824+ <string name="VoiceTimecodesCodeImportFailed">Codice impostazioni non valido o vuoto</string>
18201825 <string name="VoiceTimecodesSaved">Impostazioni dei timecode vocali salvate</string>
18211826 <string name="VoiceTimecodesDone">Timecode aggiunti</string>
18221827 <string name="VoiceTimecodesGenerate">Aggiungi timecode</string>
TMessagesProj/src/main/res/values-ko/strings.xml+8−3
@@ -1413,9 +1413,14 @@
14131413 <string name="VoiceTimecodesMinDuration">최소 음성 길이(초)</string>
14141414 <string name="VoiceTimecodesPrompt">프롬프트 템플릿 ({transcript} = 텍스트)</string>
14151415 <string name="VoiceTimecodesResetPrompt">프롬프트 기본값으로 재설정</string>
1416− <string name="VoiceTimecodesImportFile">/sdcard/Download/voice_timecodes.json에서 가져오기</string>
1417− <string name="VoiceTimecodesImportSuccess">설정을 가져왔습니다</string>
1418− <string name="VoiceTimecodesImportFailed">파일을 찾을 수 없거나 형식이 올바르지 않습니다</string>
1416+ <string name="VoiceTimecodesCodeTitle">설정 코드</string>
1417+ <string name="VoiceTimecodesCodeInfo">API 키를 포함한 모든 음성 타임코드 설정이 담긴 한 줄짜리 코드입니다. 저장된 메시지에 복사한 뒤 다른 기기에서 다시 복사하여 ‘코드 가져오기’를 누르세요. 코드를 비공개로 유지하세요.</string>
1418+ <string name="VoiceTimecodesCodeHint">‘내보내기’를 누르거나 다른 기기의 코드를 붙여넣으세요</string>
1419+ <string name="VoiceTimecodesCodeExport">클립보드로 내보내기</string>
1420+ <string name="VoiceTimecodesCodeImport">코드 가져오기</string>
1421+ <string name="VoiceTimecodesCodeCopied">설정 코드가 복사되었습니다</string>
1422+ <string name="VoiceTimecodesCodeImported">설정을 가져왔습니다</string>
1423+ <string name="VoiceTimecodesCodeImportFailed">설정 코드가 비어 있거나 잘못되었습니다</string>
14191424 <string name="VoiceTimecodesSaved">음성 타임코드 설정을 저장했습니다</string>
14201425 <string name="VoiceTimecodesDone">타임코드를 추가했습니다</string>
14211426 <string name="VoiceTimecodesGenerate">타임코드 추가</string>
TMessagesProj/src/main/res/values-nl/strings.xml+8−3
@@ -1361,9 +1361,14 @@
13611361 <string name="VoiceTimecodesMinDuration">Minimale duur van spraakbericht (seconden)</string>
13621362 <string name="VoiceTimecodesPrompt">Promptsjabloon ({transcript} = tekst)</string>
13631363 <string name="VoiceTimecodesResetPrompt">Prompt terugzetten naar standaard</string>
1364− <string name="VoiceTimecodesImportFile">Importeren uit /sdcard/Download/voice_timecodes.json</string>
1365− <string name="VoiceTimecodesImportSuccess">Instellingen geïmporteerd</string>
1366− <string name="VoiceTimecodesImportFailed">Bestand niet gevonden of ongeldige indeling</string>
1364+ <string name="VoiceTimecodesCodeTitle">Instellingencode</string>
1365+ <string name="VoiceTimecodesCodeInfo">Een code van één regel met alle instellingen voor spraaktijdstempels, inclusief API-sleutels. Kopieer hem naar Opgeslagen berichten en kopieer hem op een ander apparaat opnieuw en tik op ‘Code importeren’. Houd de code privé.</string>
1366+ <string name="VoiceTimecodesCodeHint">Tik op ‘Exporteren’ of plak een code van een ander apparaat</string>
1367+ <string name="VoiceTimecodesCodeExport">Naar klembord exporteren</string>
1368+ <string name="VoiceTimecodesCodeImport">Code importeren</string>
1369+ <string name="VoiceTimecodesCodeCopied">Instellingencode gekopieerd</string>
1370+ <string name="VoiceTimecodesCodeImported">Instellingen geïmporteerd</string>
1371+ <string name="VoiceTimecodesCodeImportFailed">Ongeldige of lege instellingencode</string>
13671372 <string name="VoiceTimecodesSaved">Instellingen voor tijdmarkeringen opgeslagen</string>
13681373 <string name="VoiceTimecodesDone">Tijdmarkeringen toegevoegd</string>
13691374 <string name="VoiceTimecodesGenerate">Tijdmarkeringen toevoegen</string>
TMessagesProj/src/main/res/values-pt-rBR/strings.xml+8−3
@@ -1543,9 +1543,14 @@
15431543 <string name="VoiceTimecodesMinDuration">Duração mínima do áudio (segundos)</string>
15441544 <string name="VoiceTimecodesPrompt">Modelo de prompt ({transcript} = texto)</string>
15451545 <string name="VoiceTimecodesResetPrompt">Restaurar prompt padrão</string>
1546− <string name="VoiceTimecodesImportFile">Importar de /sdcard/Download/voice_timecodes.json</string>
1547− <string name="VoiceTimecodesImportSuccess">Configurações importadas com sucesso</string>
1548− <string name="VoiceTimecodesImportFailed">Arquivo não encontrado ou formato inválido</string>
1546+ <string name="VoiceTimecodesCodeTitle">Código de configurações</string>
1547+ <string name="VoiceTimecodesCodeInfo">Um código de uma linha com todas as configurações de marcações de tempo de voz, incluindo chaves de API. Copie-o para Mensagens salvas e, em outro dispositivo, copie-o novamente e toque em «Importar código». Mantenha o código privado.</string>
1548+ <string name="VoiceTimecodesCodeHint">Toque em «Exportar» ou cole um código de outro dispositivo</string>
1549+ <string name="VoiceTimecodesCodeExport">Exportar para a área de transferência</string>
1550+ <string name="VoiceTimecodesCodeImport">Importar código</string>
1551+ <string name="VoiceTimecodesCodeCopied">Código de configurações copiado</string>
1552+ <string name="VoiceTimecodesCodeImported">Configurações importadas</string>
1553+ <string name="VoiceTimecodesCodeImportFailed">Código de configurações inválido ou vazio</string>
15491554 <string name="VoiceTimecodesSaved">Configurações de timecodes salvas</string>
15501555 <string name="VoiceTimecodesDone">Timecodes adicionados</string>
15511556 <string name="VoiceTimecodesGenerate">Adicionar timecodes</string>
TMessagesProj/src/main/res/values-ru/strings.xml+8−3
@@ -552,9 +552,14 @@
552552 <string name="VoiceTimecodesMinDuration">Мин. длительность голосового (сек)</string>
553553 <string name="VoiceTimecodesPrompt">Шаблон промпта ({transcript} = текст)</string>
554554 <string name="VoiceTimecodesResetPrompt">Сбросить промпт по умолчанию</string>
555− <string name="VoiceTimecodesImportFile">Импортировать из /sdcard/Download/voice_timecodes.json</string>
556− <string name="VoiceTimecodesImportSuccess">Настройки успешно импортированы</string>
557− <string name="VoiceTimecodesImportFailed">Файл не найден или содержит ошибку</string>
555+ <string name="VoiceTimecodesCodeTitle">Код настроек</string>
556+ <string name="VoiceTimecodesCodeInfo">Однострочный код со всеми настройками автотаймкодов, включая API-ключи. Скопируйте его в «Избранное», а на другом устройстве скопируйте оттуда и нажмите «Импортировать код». Никому не показывайте этот код.</string>
557+ <string name="VoiceTimecodesCodeHint">Нажмите «Экспорт» или вставьте код с другого устройства</string>
558+ <string name="VoiceTimecodesCodeExport">Экспортировать в буфер</string>
559+ <string name="VoiceTimecodesCodeImport">Импортировать код</string>
560+ <string name="VoiceTimecodesCodeCopied">Код настроек скопирован</string>
561+ <string name="VoiceTimecodesCodeImported">Настройки импортированы</string>
562+ <string name="VoiceTimecodesCodeImportFailed">Код настроек пуст или повреждён</string>
558563 <string name="VoiceTimecodesSaved">Настройки таймкодов сохранены</string>
559564 <string name="VoiceTimecodesDone">Таймкоды добавлены</string>
560565 <string name="VoiceTimecodesGenerate">Добавить таймкоды</string>
TMessagesProj/src/main/res/values-uk/strings.xml+8−3
@@ -553,9 +553,14 @@
553553 <string name="VoiceTimecodesMinDuration">Мінімальна тривалість голосового (секунди)</string>
554554 <string name="VoiceTimecodesPrompt">Шаблон промпту ({transcript} = текст)</string>
555555 <string name="VoiceTimecodesResetPrompt">Скинути промпт до типового</string>
556− <string name="VoiceTimecodesImportFile">Імпортувати з /sdcard/Download/voice_timecodes.json</string>
557− <string name="VoiceTimecodesImportSuccess">Налаштування успішно імпортовано</string>
558− <string name="VoiceTimecodesImportFailed">Файл не знайдено або недійсний формат</string>
556+ <string name="VoiceTimecodesCodeTitle">Код налаштувань</string>
557+ <string name="VoiceTimecodesCodeInfo">Однорядковий код з усіма налаштуваннями автотаймкодів, включно з API-ключами. Скопіюйте його у «Вибране», а на іншому пристрої скопіюйте звідти й натисніть «Імпортувати код». Нікому не показуйте цей код.</string>
558+ <string name="VoiceTimecodesCodeHint">Натисніть «Експорт» або вставте код з іншого пристрою</string>
559+ <string name="VoiceTimecodesCodeExport">Експортувати в буфер</string>
560+ <string name="VoiceTimecodesCodeImport">Імпортувати код</string>
561+ <string name="VoiceTimecodesCodeCopied">Код налаштувань скопійовано</string>
562+ <string name="VoiceTimecodesCodeImported">Налаштування імпортовано</string>
563+ <string name="VoiceTimecodesCodeImportFailed">Код налаштувань порожній або пошкоджений</string>
559564 <string name="VoiceTimecodesSaved">Налаштування таймкодів збережено</string>
560565 <string name="VoiceTimecodesDone">Таймкоди додано</string>
561566 <string name="VoiceTimecodesGenerate">Додати таймкоди</string>
TMessagesProj/src/main/res/values/strings.xml+8−3
@@ -11835,9 +11835,14 @@
1183511835 <string name="VoiceTimecodesMinDuration">Minimum voice duration (seconds)</string>
1183611836 <string name="VoiceTimecodesPrompt">Prompt template ({transcript} = text)</string>
1183711837 <string name="VoiceTimecodesResetPrompt">Reset prompt to default</string>
11838− <string name="VoiceTimecodesImportFile">Import from /sdcard/Download/voice_timecodes.json</string>
11839− <string name="VoiceTimecodesImportSuccess">Settings imported successfully</string>
11840− <string name="VoiceTimecodesImportFailed">File not found or invalid format</string>
11838+ <string name="VoiceTimecodesCodeTitle">Settings code</string>
11839+ <string name="VoiceTimecodesCodeInfo">A single-line code with all voice timecode settings, including API keys. Copy it to Saved Messages, then on another device copy it again and tap Import code. Keep the code private.</string>
11840+ <string name="VoiceTimecodesCodeHint">Tap Export or paste a code from another device</string>
11841+ <string name="VoiceTimecodesCodeExport">Export to clipboard</string>
11842+ <string name="VoiceTimecodesCodeImport">Import code</string>
11843+ <string name="VoiceTimecodesCodeCopied">Settings code copied</string>
11844+ <string name="VoiceTimecodesCodeImported">Settings imported</string>
11845+ <string name="VoiceTimecodesCodeImportFailed">Invalid or empty settings code</string>
1184111846 <string name="VoiceTimecodesSaved">Voice timecodes settings saved</string>
1184211847 <string name="VoiceTimecodesDone">Timecodes added</string>
1184311848 <string name="VoiceTimecodesGenerate">Add timecodes</string>
gradle.properties+2−2
@@ -13,8 +13,8 @@
1313# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
1414# org.gradle.parallel=true
1515#Sat Mar 12 05:53:50 MSK 2016
16−APP_VERSION_CODE=7184
17−APP_VERSION_NAME=13.0.4
16+APP_VERSION_CODE=7185
17+APP_VERSION_NAME=13.0.5
1818APP_PACKAGE=org.telegram.messenger
1919IS_PRIVATE=false
2020RELEASE_KEY_PASSWORD=android