From 7e1d97435134bf0185f84f2898978bb1b4f3cb06 Mon Sep 17 00:00:00 2001 From: Jochen Sprickerhof Date: Mon, 25 Jul 2022 08:51:47 +0200 Subject: [PATCH 1/2] Reduce try/except block --- fdroidserver/common.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 39c1dcd4..ecf4629c 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -1846,20 +1846,21 @@ def parse_androidmanifests(paths, app): else: try: xml = parse_xml(path) - if "package" in xml.attrib: - s = xml.attrib["package"] - if app_matches_packagename(app, s): - package = s - if XMLNS_ANDROID + "versionName" in xml.attrib: - version = xml.attrib[XMLNS_ANDROID + "versionName"] - base_dir = os.path.dirname(path) - version = retrieve_string_singleline(base_dir, version) - if XMLNS_ANDROID + "versionCode" in xml.attrib: - a = xml.attrib[XMLNS_ANDROID + "versionCode"] - if string_is_integer(a): - vercode = a except Exception: logging.warning(_("Problem with xml at '{path}'").format(path=path)) + continue + if "package" in xml.attrib: + s = xml.attrib["package"] + if app_matches_packagename(app, s): + package = s + if XMLNS_ANDROID + "versionName" in xml.attrib: + version = xml.attrib[XMLNS_ANDROID + "versionName"] + base_dir = os.path.dirname(path) + version = retrieve_string_singleline(base_dir, version) + if XMLNS_ANDROID + "versionCode" in xml.attrib: + a = xml.attrib[XMLNS_ANDROID + "versionCode"] + if string_is_integer(a): + vercode = a # Remember package name, may be defined separately from version+vercode if package is None: From 7822db2881539bf64c01f82570feb6130a2a459f Mon Sep 17 00:00:00 2001 From: Jochen Sprickerhof Date: Mon, 25 Jul 2022 08:52:57 +0200 Subject: [PATCH 2/2] Catch DefusedXmlException (as ValueError) defusedxml can't handle the nbsp in the strings.xml (etree can). --- MANIFEST.in | 2 + fdroidserver/common.py | 6 +- tests/common.TestCase | 11 + .../app/build.gradle.kts | 306 +++++ .../app/src/main/res/values/strings.xml | 1023 +++++++++++++++++ 5 files changed, 1345 insertions(+), 3 deletions(-) create mode 100644 tests/source-files/com.ubergeek42.WeechatAndroid/app/build.gradle.kts create mode 100644 tests/source-files/com.ubergeek42.WeechatAndroid/app/src/main/res/values/strings.xml diff --git a/MANIFEST.in b/MANIFEST.in index 9ea77a1b..2d330a4a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -748,6 +748,8 @@ include tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata include tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt include tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt include tests/source-files/com.seafile.seadroid2/app/build.gradle +include tests/source-files/com.ubergeek42.WeechatAndroid/app/build.gradle.kts +include tests/source-files/com.ubergeek42.WeechatAndroid/app/src/main/res/values/strings.xml include tests/source-files/de.varengold.activeTAN/build.gradle include tests/source-files/dev.patrickgold.florisboard/app/build.gradle.kts include tests/source-files/eu.siacs.conversations/build.gradle diff --git a/fdroidserver/common.py b/fdroidserver/common.py index ecf4629c..bbb4fc47 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -1579,7 +1579,7 @@ def retrieve_string(app_dir, string, xmlfiles=None): continue try: xml = parse_xml(path) - except XMLElementTree.ParseError: + except (XMLElementTree.ParseError, ValueError): logging.warning(_("Problem with xml at '{path}'").format(path=path)) continue element = xml.find('string[@name="' + name + '"]') @@ -1625,7 +1625,7 @@ def fetch_real_name(app_dir, flavours): logging.debug("fetch_real_name: Checking manifest at " + path) try: xml = parse_xml(path) - except XMLElementTree.ParseError: + except (XMLElementTree.ParseError, ValueError): logging.warning(_("Problem with xml at '{path}'").format(path=path)) continue app = xml.find('application') @@ -1846,7 +1846,7 @@ def parse_androidmanifests(paths, app): else: try: xml = parse_xml(path) - except Exception: + except (XMLElementTree.ParseError, ValueError): logging.warning(_("Problem with xml at '{path}'").format(path=path)) continue if "package" in xml.attrib: diff --git a/tests/common.TestCase b/tests/common.TestCase index d90bb180..b107b278 100755 --- a/tests/common.TestCase +++ b/tests/common.TestCase @@ -1168,6 +1168,17 @@ class CommonTest(unittest.TestCase): self.assertEqual(('0.3.10', '29', 'dev.patrickgold.florisboard'), fdroidserver.common.parse_androidmanifests(paths, app)) + app = fdroidserver.metadata.App() + app.id = 'com.ubergeek42.WeechatAndroid' + paths = [ + os.path.join('source-files', 'com.ubergeek42.WeechatAndroid', 'app', 'build.gradle.kts'), + os.path.join('source-files', 'com.ubergeek42.WeechatAndroid', 'app', 'src', 'main', 'res', 'values', 'strings.xml'), + ] + for path in paths: + self.assertTrue(os.path.isfile(path)) + self.assertEqual(('1.8.1', '1_08_01', None), + fdroidserver.common.parse_androidmanifests(paths, app)) + def test_parse_androidmanifests_ignore(self): app = fdroidserver.metadata.App() app.id = 'org.fdroid.fdroid' diff --git a/tests/source-files/com.ubergeek42.WeechatAndroid/app/build.gradle.kts b/tests/source-files/com.ubergeek42.WeechatAndroid/app/build.gradle.kts new file mode 100644 index 00000000..88c03a08 --- /dev/null +++ b/tests/source-files/com.ubergeek42.WeechatAndroid/app/build.gradle.kts @@ -0,0 +1,306 @@ +import com.android.build.api.transform.* +import com.android.build.api.variant.VariantInfo +import com.android.utils.FileUtils +import org.gradle.internal.os.OperatingSystem +import org.aspectj.bridge.IMessage +import org.aspectj.bridge.MessageHandler +import org.aspectj.tools.ajc.Main + +plugins { + id("com.android.application") + kotlin("android") + kotlin("kapt") +} + +dependencies { + implementation(project(":cats")) + implementation(project(":relay")) + + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0") + + // these two are required for logging within the relay module. todo remove? + implementation("org.slf4j:slf4j-api:1.7.36") + implementation("com.noveogroup.android:android-logger:1.3.6") + + implementation("androidx.core:core-ktx:1.8.0") + implementation("androidx.legacy:legacy-support-v4:1.0.0") + implementation("androidx.annotation:annotation:1.3.0") // For @Nullable/@NonNull + implementation("androidx.appcompat:appcompat:1.4.2") + implementation("androidx.emoji2:emoji2:1.1.0") + implementation("androidx.preference:preference-ktx:1.2.0") // preference fragment & al + implementation("androidx.legacy:legacy-preference-v14:1.0.0") // styling for the fragment + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1") + implementation("androidx.lifecycle:lifecycle-common-java8:2.4.1") + implementation("androidx.sharetarget:sharetarget:1.2.0-rc01") + + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2") + + implementation("com.github.bumptech.glide:glide:4.13.2") + kapt("com.github.bumptech.glide:compiler:4.13.2") + implementation("com.squareup.okhttp3:okhttp:4.10.0") + + val roomVersion = "2.4.2" + implementation("androidx.room:room-runtime:$roomVersion") + annotationProcessor("androidx.room:room-compiler:$roomVersion") + kapt("androidx.room:room-compiler:$roomVersion") + + implementation("org.yaml:snakeyaml:1.30") + + implementation("org.bouncycastle:bcpkix-jdk15on:1.70") + + // needed for thread-safe date formatting as SimpleDateFormat isn"t thread-safe + // the alternatives, including apache commons and threetenabp, seem to be much slower + // todo perhaps replace with core library desugaring, if it"s fast + implementation("net.danlew:android.joda:2.10.14") + + implementation("org.greenrobot:eventbus:3.3.1") + + debugImplementation("org.aspectj:aspectjrt:1.9.9.1") + debugImplementation("com.squareup.leakcanary:leakcanary-android:2.9.1") + + testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2") +} + +tasks.withType { + options.encoding = "UTF-8" +} + +android { + compileSdk = 31 + + defaultConfig { + versionCode = 1_08_01 + versionName = "1.8.1" + + minSdk = 21 + targetSdk = 31 + buildConfigField("String", "VERSION_BANNER", "\"" + versionBanner() + "\"") + + vectorDrawables.useSupportLibrary = true + + javaCompileOptions { + annotationProcessorOptions { + arguments["room.schemaLocation"] = "$projectDir/schemas" + arguments["room.incremental"] = "true" + } + } + + kotlinOptions { + freeCompilerArgs = listOf( + "-language-version", "1.7", + "-api-version", "1.7") + jvmTarget = "11" + } + } + + signingConfigs { + create("dev") { + try { + storeFile = file(project.properties["devStorefile"] as String) + storePassword = project.properties["devStorePassword"] as String + keyAlias = project.properties["devKeyAlias"] as String + keyPassword = project.properties["devKeyPassword"] as String + } catch (e: Exception) { + project.logger.warn("WARNING: Set the values devStorefile, devStorePassword, " + + "devKeyAlias, and devKeyPassword " + + "in ~/.gradle/gradle.properties to sign the release.") + } + } + } + + buildTypes { + getByName("debug") { + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" + } + + getByName("release") { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + "../cats/proguard-rules.pro") + // kotlinx-coroutines-core debug-only artifact + // see https://github.com/Kotlin/kotlinx.coroutines#avoiding-including-the-debug-infrastructure-in-the-resulting-apk + packagingOptions { + resources.excludes += "DebugProbesKt.bin" + } + } + + create("dev") { + initWith(getByName("release")) + matchingFallbacks += listOf("release") + applicationIdSuffix = ".dev" + versionNameSuffix = "-dev" + signingConfig = signingConfigs.getByName("dev") + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + } + + buildFeatures { + viewBinding = true + } +} + +fun versionBanner(): String { + val os = org.apache.commons.io.output.ByteArrayOutputStream() + project.exec { + commandLine = "git describe --long".split(" ") + standardOutput = os + } + return String(os.toByteArray()).trim() +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// cats +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// ajc gets hold of some files such as R.jar, and on Windows it leads to errors such as: +// The process cannot access the file because it is being used by another process +// to avoid these, weave in a process, which `javaexec` will helpfully launch for us. + +fun weave(classPath: Iterable, aspectPath: Iterable, input: Iterable, output: File) { + val runInAProcess = OperatingSystem.current().isWindows + val bootClassPath = android.bootClasspath + + println(if (runInAProcess) ":: weaving in a process..." else ":: weaving...") + println(":: boot class path: $bootClassPath") + println(":: class path: $classPath") + println(":: aspect path: $aspectPath") + println(":: input: $input") + println(":: output: $output") + + val arguments = listOf("-showWeaveInfo", + "-1.8", + "-preserveAllLocals", + "-bootclasspath", bootClassPath.asArgument, + "-classpath", classPath.asArgument, + "-aspectpath", aspectPath.asArgument, + "-inpath", input.asArgument, + "-d", output.absolutePath) + + if (runInAProcess) { + javaexec { + classpath = weaving + main = "org.aspectj.tools.ajc.Main" + args = arguments + } + } else { + val handler = MessageHandler(true) + Main().run(arguments.toTypedArray(), handler) + + val log = project.logger + for (message in handler.getMessages(null, true)) { + when (message.kind) { + IMessage.DEBUG -> log.debug("DEBUG " + message.message, message.thrown) + IMessage.INFO -> log.info("INFO: " + message.message, message.thrown) + IMessage.WARNING -> log.warn("WARN: " + message.message, message.thrown) + IMessage.FAIL, + IMessage.ERROR, + IMessage.ABORT -> log.error("ERROR: " + message.message, message.thrown) + } + } + } +} + +// the only purpose of the following is to get a hold of aspectjtools jar +// this jar is already on build script classpath, but that classpath is impossible to get +// see https://discuss.gradle.org/t/how-do-i-determine-buildscript-classpath/37973/3 + +val weaving: Configuration by configurations.creating + +dependencies { + weaving("org.aspectj:aspectjtools:1.9.9.1") +} + +// historical note: the problem with weaving Kotlin and Java in-place is that: +// * Java is compiled by task compileDebugJavaWithJavac +// * gradle can run either one of these tasks, or both of them +// * compileDebugJavaWithJavac depends on compileDebugKotlin +// * weaving Kotlin requires Java classes +// +// a transformation is a poorly advertised feature that works on merged code, and also has its own +// inputs and outputs, so this fixes all of our problems... + + class TransformCats : Transform() { + override fun getName(): String = TransformCats::class.simpleName!! + + override fun getInputTypes() = setOf(QualifiedContent.DefaultContentType.CLASSES) + + // only look for annotations in app classes + // transformation will consume these and put woven classes in the output dir + override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT) + + // but also have the rest on our class path + // these will not be touched by the transformation + override fun getReferencedScopes() = mutableSetOf(QualifiedContent.Scope.SUB_PROJECTS, + QualifiedContent.Scope.EXTERNAL_LIBRARIES) + + override fun isIncremental() = false + + // only run on debug builds + override fun applyToVariant(variant: VariantInfo) = variant.isDebuggable + + override fun transform(invocation: TransformInvocation) { + if (!invocation.isIncremental) { + invocation.outputProvider.deleteAll() + } + + val output = invocation.outputProvider.getContentLocation(name, outputTypes, + scopes, Format.DIRECTORY) + if (output.isDirectory) FileUtils.deleteDirectoryContents(output) + FileUtils.mkdirs(output) + + val input = mutableListOf() + val classPath = mutableListOf() + val aspectPath = mutableListOf() + + invocation.inputs.forEach { source -> + source.directoryInputs.forEach { dir -> + input.add(dir.file) + classPath.add(dir.file) + } + + source.jarInputs.forEach { jar -> + input.add(jar.file) + classPath.add(jar.file) + } + } + + invocation.referencedInputs.forEach { source -> + source.directoryInputs.forEach { dir -> + classPath.add(dir.file) + } + + source.jarInputs.forEach { jar -> + classPath.add(jar.file) + // this used to read `if (jar.name == ":cats") ...`, + // but with android gradle plugin 4.2.0 jar names contain garbage + // this is a very simple but a bit fragile workaround. todo improve + if (jar.file.directoriesInsideRootProject().contains("cats")) { + aspectPath.add(jar.file) + } + } + + } + + weave(classPath, aspectPath, input, output) + } +} + +android.registerTransform(TransformCats()) + +val Iterable.asArgument get() = joinToString(File.pathSeparator) + +fun File.directoriesInsideRootProject() = sequence { + var file = this@directoriesInsideRootProject + while (true) { + yield(file.name) + file = file.parentFile ?: break + if (file == rootProject.projectDir) break + } +} diff --git a/tests/source-files/com.ubergeek42.WeechatAndroid/app/src/main/res/values/strings.xml b/tests/source-files/com.ubergeek42.WeechatAndroid/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..0e1ff29b --- /dev/null +++ b/tests/source-files/com.ubergeek42.WeechatAndroid/app/src/main/res/values/strings.xml @@ -0,0 +1,1023 @@ + + +]> + + + + + + + + + Weechat-Android + com.ubergeek42.WeechatAndroid + + + + Relay host is not set + Relay password is not set + SSH host is not set + SSH password is not set + SSH private key is not set + + + + Server unexpectedly closed connection while connecting. + Wrong password or connection type? + + Could not resolve address %s + + Error: %s + Buffer lost some hot messages because of new lines + There are no hot buffers for now + Not connected + Buffer list empty + Activity not found for intent %s + + + + Upload file + Tab + Upload + Send + + Cancel search + Search up + Search down + More + + Go to bottom + + Fetch more lines + Fetching lines… + + search + + filter + + Open drawer + Close drawer + + + + + + Search + + Users + + Hotlist + Show hot message + + Close + + Connect + Disconnect + Stop connecting + + Settings + + Filter lines + + Dark theme + + Attach image + Attach media + Attach file + Attach image + Attach media + Take photo + + Debug + Sync hotlist + Die + + + + Prefix + Message + Both + + Regex + Case sensitive + + + + With timestamps + Without timestamps + Messages only + + + + + + Connection Status + Hotlist + + Waiting for network + Will connect in %d seconds… + Connecting now… + Connected to %s + + + + %d message + %d messages + + +  in %d buffer +  in %d buffers + + + + + New message in %2$s + %1$d new messages in %2$s + + + + (user unknown) + (users unknown) + + (message not fetched) + + (message not fetched) + (%d messages not fetched) + + + + Me + + Reply + + + + + + + + %1$s (%2$s) + + %1$d user + %1$d users + + %s (away) + + + Copy + Select text + + Paste + + + Permission required + + To take photos, app needs write access to public storage + OK + + + + + + Issued to:
+ %1$s
+
+ Issued by:
+ %2$s
+
+ Validity period:
+ Issued on: %3$s
+ Expires on: %4$s
+
+ SHA-256 fingerprint:
+ %5$s + ]]>
+ Unknown + + Reject + Back to safety + + + + + Invalid hostname + + %1$s + but the certificate is only valid for the following hosts: %2$s + ]]> + Note that Android P and beyond does not fall back to Common Name (CN) validation. + Subject Alternative Name (SAN) must be used instead. + Learn more + ]]> +       %s + ]]> +       (none) + ]]> + + + + + Certificate expired + + This certificate is no longer valid. + Please make sure that the device date is correct. + + + + + Certificate not yet valid + + This certificate will be valid in the future. + Please make sure that the device date is correct. + + + + + Untrusted certificate + + This certificate isn’t trusted by Android, but you can still connect. + The app will remember the selected certificate + and trust it and any certificates signed by it. + + + Accept selected + + + + + Certificate not pinned + + This server is trusted by Android, + but a setting requires you to confirm that you trust it as well. + The app will remember the selected certificate + and trust it and any certificates signed by it. + + + Pin selected + + + + + + + Unknown + + + + + Unknown server + %1$s has never been encountered. +
+
%2$s key SHA256 fingerprint: +
%3$s + ]]>
+ + Accept server key + Reject + + + + + Server changed key + ⚠ Warning: it’s possible that someone is trying to hijack this connection! +
+
Server at %1$s is known, but its key doesn’t match the key you previously accepted. +
+
%2$s key SHA256 fingerprint: +
%3$s +
+
If you want to continue, please clear known hosts in preferences. + ]]>
+ + + + + + + + + + Clear + Paste + Choose file + %1$s (%2$s) + set + not set + Clipboard is empty + + password + + Save + Default + Discard changes? + Cancel + Discard + + None + Unknown + + Invalid number + No spaces allowed in hostnames + + + + + + Connection + + Connection type + + Plain connection + WeeChat SSL + SSH tunnel + WebSocket + WebSocket (SSL) + + + + + WebSocket path + + + + + + SSL settings + + + Require certificate pins + + Prompt to confirm that you trust the server, even if the system trusts it + + Clear certificates + No trusted certificates + One trusted certificate + + One trusted certificate + %s trusted certificates + + Clear certificates? + Clear + Cancel + Certificates cleared + Could not clear certificates + + + Client certificate + + PKCS #12 file containing private key and client certificate + + Certificate was stored inside security hardware + + Certificate was stored inside software key store + + Certificate was stored inside key store + + Certificate forgotten + + + Server is asking for a client certificate but none is set. + Wanted: %1$s certificate issued by: %2$s + + Server is asking for a client certificate but the one we have doesn’t fit. + Wanted: %1$s certificate issued by: %2$s + + + + SSH tunnel settings + + SSH host + + SSH port + + SSH username + + Authentication method + + Password + Key + + + Password + + Private key + Ed25519, ECDSA, RSA or DSA key + + %s key was stored inside security hardware + + %s key was stored inside software key store + + %s key was stored inside key store + + %1$s key was stored inside the app. + \n + \nThe key couldn’t be stored in the key store: %2$s + + Key forgotten + + Clear known hosts + No entries + + %s entry + %s entries + + Clear known hosts? + Cancel + Clear + Known hosts cleared + + + Failed to authenticate with password + + Failed to authenticate with key + + + + Relay + + Relay host + + Relay port + + Relay password + + + + Handshake settings + + Handshake + + Compatibility + Modern & fast + Modern + + + In compatibility mode, the password isn’t hashed. This is the fastest method. + This method is required if using WeeChat < 2.9, but works on the later versions as well. + +

Modern & fast handshake limits algorithms to the SHA-2 family. + +

Modern handshake also includes PBKDF2. + These algorithms can be very slow, depending on the number of iterations. + +

Password hashing offers little to no benefit if the connection is encrypted. + Learn more + ]]> + + + + Synchronization settings + + + Only sync open buffers + + Can significantly reduce traffic and battery usage, + but hotlist updates will only happen once 5 minutes + + + Sync buffer read status + + Mark buffers as read in WeeChat when you read them in this app + + + Number of lines to fetch + + The number of lines requested when opening a buffer + or when you press the “Load more lines” button (%s) + + + Number of lines to fetch for search + + When starting a new search, unless already fetched, + the app will request up to this many lines from WeeChat (%s) + + + These settings take effect after reconnection. + Note that due to filtering the number of lines actually shown + might be less than the number of loaded lines. + Also note that due to WeeChat’s limitations + the app has to re-fetch all lines every times it requests more lines. + + + + Miscellaneous + + Reconnect on connection loss + + Connect on system boot + + + + Ping settings + + + Enable ping + + Periodically check that the relay connection is still alive when idle + + + Idle time + + Number of seconds to wait before sending a ping when the connection is idle (%s) + + + Ping timeout + + Number of seconds to wait before closing an unresponsive connection (%s) + + + + + + Buffer list + + + Sort buffer list + + Sort by number of highlights/private messages/unread messages + + + Hide non-conversation buffers + + E.g. server buffers and plugin buffers + + + Hide hidden buffers + + Hide buffers hidden with /buffer hide + + + Show buffer filter + + Filter matches full buffer names and reveals matching hidden buffers + + + System gesture exclusion zone + + On Android Q, the left side of the screen is reserved for the back gesture. + Enable this to have a small area in the bottom of the screen + where you can open the buffer list normally. + + + + + + Look & feel + + Text size + + + Hide action bar + + Hide action bar when the keyboard is open or when scrolling up + + + Filter messages + + Hide messages filtered by WeeChat (e.g. irc_smart_filter) + + + Prefix alignment + + Left aligned + Right aligned + Timestamp aligned + No alignment + + + + Maximum width of prefix + + In terms of letters; longer nicknames will be cut (%s) + + + Enclose nicknames + + Enclose the nicknames in < and > + + Timestamp format + %s (default: HH:mm:ss) + Invalid timestamp format + + + Buffer font + Default + Import + Imported: %s + + + Non-monospace fonts will not work well with alignment. + Import fonts from the dialog, + or put them into one of the following locations:%1$s + + + + + + Theme + + @string/pref__theme__theme__system + Dark + Light + + + Set by battery saver + + Theme switch + Show theme switch in the menu + + Light color scheme + Dark color scheme + Error loading color scheme %s + Not set + Error + Import + Imported: %s + + Learn more +
+
Import color schemes from the dialogs + or put them into the following location:%1$s + ]]>
+ + + Dim down non-human lines + + Display joins/quits in a faint color, as set in the color scheme + + + + + + Buttons + + Show tab button + + Show send button + + Show paperclip button + + Paperclip button short tap + + @string/pref__buttons__paperclip__actions__content_images + @string/pref__buttons__paperclip__actions__content_media + @string/pref__buttons__paperclip__actions__content_anything + @string/pref__buttons__paperclip__actions__mediastore_images + @string/pref__buttons__paperclip__actions__mediastore_media + @string/pref__buttons__paperclip__actions__camera + + + Paperclip button long tap + + @string/pref__buttons__paperclip__actions__none + @string/pref__buttons__paperclip__actions__content_images + @string/pref__buttons__paperclip__actions__content_media + @string/pref__buttons__paperclip__actions__content_anything + @string/pref__buttons__paperclip__actions__mediastore_images + @string/pref__buttons__paperclip__actions__mediastore_media + @string/pref__buttons__paperclip__actions__camera + + + Disabled + + System: attach images + + System: attach images and videos + + System: attach any files + + Gallery: attach images + + Gallery: attach images and videos + + Take photo + + + When the paperclip button gets hidden to provide more space for the input field, + you can still attach files via overflow menu. + + + Volume buttons change text size + + If set, volume buttons will change text size instead of volume + + + + + + Notifications + + + Enable notifications + + Notify about hot messages such as private messages or highlights + + Notification sound + + Vibration + + Notification light + + + + + + Media preview + + Enabled + + Never + On Wi-Fi only + On unmetered networks only + Always + + + Context + Disabled everywhere + Enabled for %s + + Chat + Paste dialog + Notifications + + + + Insecure requests + + Allow + Rewrite as HTTPS + Disallow + + + +
⚠ Warning: the app is accessing the web directly. + A malicious person could craft a website to learn your IP address and other data. + To prevent the app from accessing websites you don’t know, + remove the strategy for the wildcard host “*” or set it to “none”. + Learn more + ]]>
+ + + Strategies + + Defines the ways images are fetched from individual websites, and some filters. + \n + \n%1$s; %2$s; + \n + \n%3$s + Error + Message filter set + Message filter not set + line filters not set + + %d line filter set + %d line filters set + + No strategies loaded + Strategies: %s + +"# don’t look for links in the part +# of the message that matches the +# following regex. this prevents +# the app from showing broken links +# in matrix clients’ quotes, e.g. +# <nick "http://broken.co"> message +#message filter: +# ^<[^ ]{1,16} \".{1,33}\">\\s + +line filters: +# don’t display thumbnails for any +# lines that match the following regex +- regex: '^(?:Title: |[↑^] )' + +# don’t display thumbnails +# for any lines from bot +#- nicks: [bot] + +# don’t display thumbnails +# for any lines from bot +# that also math the given regex +#- nicks: [bot] +# regex: ^<\\S+>\\s + +strategies: +- name: skip pastebins + type: none + hosts: + - pastebin.com + - bpa.st + - dpaste.com + - termbin.com + +- name: skip site banners + type: none + hosts: + - github.com + - gist.github.com + - stackoverflow.com + - '*.stackexchange.com' + - twitch.tv + - '*.twitch.tv' + +#- name: skip the rest, including redirects +# type: none +# hosts: ['*'] + +- name: try the rest + type: any + hosts: ['*'] + +- name: youtube + type: image + hosts: [www.youtube.com, m.youtube.com, youtube.com, youtu.be] + regex: (?i)^https?://(?:(?:www\\.|m\\.)?youtube\\.com/watch\\?v=|youtu\\.be/)([A-Za-z0-9_-]+) + small: https://img.youtube.com/vi/$1/mqdefault.jpg + big: https://img.youtube.com/vi/$1/hqdefault.jpg + +- name: i.imgur + type: image + hosts: [i.imgur.com] + regex: (?i)^https?://i\\.imgur\\.com/([A-Za-z0-9]+) + small: https://i.imgur.com/$1m.jpg + big: https://i.imgur.com/$1h.jpg + +- name: imgur/gallery + type: any + hosts: [imgur.com, www.imgur.com] + regex: (?i)^https?://(?:www\\.)?imgur\\.com/gallery/(.*) + sub: https://imgur.com/a/$1 + +- name: 9gag + type: image + hosts: [9gag.com, img-9gag-fun.9cache.com] + regex: (?i)^https?://(?:9gag\\.com/gag|img-9gag-fun\\.9cache\\.com/photo)/([^_]+) + small: https://images-cdn.9gag.com/photo/$1_700b.jpg + big: https://images-cdn.9gag.com/photo/$1_700b.jpg + +- name: mobile.twitter + type: any + hosts: [mobile.twitter.com] + regex: (?i)^https?://mobile\\.twitter\\.com/(.*) + sub: https://twitter.com/$1 + +- name: common + type: any + regex: (?i)^https?://(.+) + sub: https://$1 + hosts: + - '*.wikipedia.org' + - gfycat.com + - imgur.com + +- name: reddit + type: any + hosts: [v.redd.it, reddit.com, www.reddit.com, old.reddit.com] + body size: 196608 +" + + Advanced + + Download size limit + %s MB + + + Disk cache + + %s MB; takes effect on restart + + + Success cooldown + + %s hours. The app will consider successfully fetched image available, + either from cache or the web, + for the specified amount of time. + + Thumbnail width + + Minimum thumbnail height + + Maximum thumbnail height + + + + + + File sharing + + Accept from other apps + + Text only + Text, images and videos + Everything + + + Direct share + + Disabled + Up to one buffer + Up to two buffers + Up to three buffers + Up to four buffers + + + Upload URL + + File field + + Regex + + +
curl -s --user user:pass \\ +
  --header \'Additional: Header\' \\ +
  --form additional=field \\ +
  --form file=@file.ext \\ +
  https://example.com | perl -nle \\ +
  \'m#^https://\\S+#; print $1//$&\'
+
+
If the regular expression is set, it is used to find the URL in the response body; + either the first capture group is used, or the whole match. + Learn more + ]]>
+ + Advanced + + Additional headers + + Additional fields + + Authentication + + None + Basic + + + User + + Password + + Remember uploads for + %s hours + + + + + + About + + Weechat-Android %s + + created by ubergeek42 + + build ID %s + Unknown version ID + + + \nThis project uses the following libraries: + \n + \n    • Android Logger by Noveo Group + \n    • AspectJ by Eclipse Foundation + \n    • Bouncy Castle + \n    • EventBus by greenrobot + \n    • Glide by Bump Technologies + \n    • Hugo by Jake Wharton + \n    • Java-WebSocket by Nathan Rajlich + \n    • joda-time-android by Daniel Lew + \n    • JSch by JCraft + \n    • LeakCanary by Square, Inc + \n    • Mockito by Szczepan Faber and friends + \n    • nv-websocket-client by Takahiko Kawasaki + \n    • OkHttp by Square, Inc + \n    • SLF4J by QOS.ch + \n    • SnakeYAML by Andrey Somov + \n    • sshlib by ConnectBot + \n + \nPlease create an issue on GitHub + if you find a bug or have a feature request. + \n + \n +