diff --git a/search-actions/src/main/java/de/mm20/launcher2/searchactions/KnownWebsearchEngines.kt b/search-actions/src/main/java/de/mm20/launcher2/searchactions/KnownWebsearchEngines.kt new file mode 100644 index 00000000..3c88346c --- /dev/null +++ b/search-actions/src/main/java/de/mm20/launcher2/searchactions/KnownWebsearchEngines.kt @@ -0,0 +1,21 @@ +package de.mm20.launcher2.searchactions + +import de.mm20.launcher2.searchactions.builders.WebsearchActionBuilder + +fun knownWebsearchByHostname(hostname: String): WebsearchActionBuilder? { + // List of popular web search engines that do not implement the OpenSearch standard + return when(hostname) { + "google.com" -> WebsearchActionBuilder(label = "Google", urlTemplate = "https://google.com/search?q=\${1}") + "bing.com" -> WebsearchActionBuilder(label = "Google", urlTemplate = "https://bing.com/search?q=\${1}") + "amazon.com" -> WebsearchActionBuilder(label = "Amazon", urlTemplate = "https://www.amazon.com/s?k=\${1}") + "amazon.de" -> WebsearchActionBuilder(label = "Amazon DE", urlTemplate = "https://www.amazon.de/s?k=\${1}") + "amazon.co.uk" -> WebsearchActionBuilder(label = "Amazon UK", urlTemplate = "https://www.amazon.co.uk/s?k=\${1}") + "amazon.fr" -> WebsearchActionBuilder(label = "Amazon FR", urlTemplate = "https://www.amazon.fr/s?k=\${1}") + "amazon.co.jp" -> WebsearchActionBuilder(label = "Amazon JP", urlTemplate = "https://www.amazon.co.jp/s?k=\${1}") + "amazon.ca" -> WebsearchActionBuilder(label = "Amazon CA", urlTemplate = "https://www.amazon.ca/s?k=\${1}") + "amazon.cn" -> WebsearchActionBuilder(label = "Amazon CN", urlTemplate = "https://www.amazon.cn/s?k=\${1}") + "duckduckgo.com" -> WebsearchActionBuilder(label = "DuckDuckGo", urlTemplate = "https://duckduckgo.com/?q=\${1}") + "yahoo.com" -> WebsearchActionBuilder(label = "DuckDuckGo", urlTemplate = "https://search.yahoo.com/search?p=\${1}") + else -> null + } +} \ No newline at end of file diff --git a/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt b/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt index 497d2cab..d8fe73a0 100644 --- a/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt +++ b/search-actions/src/main/java/de/mm20/launcher2/searchactions/SearchActionService.kt @@ -119,88 +119,18 @@ internal class SearchActionServiceImpl( .getOrNull(0) ?.absUrl("href") ?.takeIf { it.isNotEmpty() } - ?: return@withContext run { - Log.d("MM20", "Specified URL does not implement the OpenSearch protocol") - null - } - val httpClient = OkHttpClient() - val request = Request.Builder() - .url(openSearchHref) - .build() - val response = httpClient.newCall(request).execute() - val inputStream = response.body?.byteStream() ?: return@withContext null - - var label: String? = null - var urlTemplate: String? = null - var icon: String? = null - var iconSize: Int = 0 - var iconUrl: String? = null - - inputStream.use { - val parser = Xml.newPullParser() - parser.setInput(inputStream.reader()) - while (parser.next() != XmlPullParser.END_DOCUMENT) { - if (parser.eventType == XmlPullParser.START_TAG) { - when (parser.name) { - "ShortName" -> { - parser.next() - if (parser.eventType == XmlPullParser.TEXT) { - label = parser.text - } - } - - "LongName" -> { - parser.next() - if (parser.eventType == XmlPullParser.TEXT) { - if (label != null) label = parser.text - } - } - - "Image" -> { - val size = - parser.getAttributeValue(null, "width")?.toIntOrNull() ?: 0 - if (size > iconSize || iconUrl == null) { - parser.next() - if (parser.eventType == XmlPullParser.TEXT) { - iconUrl = parser.text - iconSize = size - } - } - } - - "Url" -> { - if (parser.getAttributeValue(null, "type") == "text/html") { - val rel = parser.getAttributeValue(null, "rel") - if (rel == null || rel == "results") { - val template = - parser.getAttributeValue(null, "template") - ?.takeIf { it.isNotEmpty() } ?: continue - urlTemplate = template - .replace("{searchTerms}", "\${1}") - .replace("{startPage?}", "1") - } - } - } - - else -> continue - } - } - } - - val localIconUrl = iconUrl?.let { - val uri = Uri.parse(it) - createIcon(uri, iconSize) - } - - return@withContext WebsearchActionBuilder( - label = label ?: "", - icon = if (localIconUrl == null) SearchActionIcon.Search else SearchActionIcon.Custom, - customIcon = localIconUrl, - iconColor = if (localIconUrl == null) 0 else 1, - urlTemplate = urlTemplate ?: "" - ) + var action = openSearchHref?.let { + importOpenSearch(it) } + + if (action != null) { + return@withContext action + } + + val host = URL(u).host ?: return@withContext null + return@withContext knownWebsearchByHostname(host) + } catch (e: IOException) { CrashReporter.logException(e) } catch (e: XmlPullParserException) { @@ -209,6 +139,91 @@ internal class SearchActionServiceImpl( return@withContext null } + private suspend fun importOpenSearch(openSearchHref: String): WebsearchActionBuilder? { + try { + val httpClient = OkHttpClient() + val request = Request.Builder() + .url(openSearchHref) + .build() + val response = httpClient.newCall(request).execute() + val inputStream = response.body?.byteStream() ?: return null + + var label: String? = null + var urlTemplate: String? = null + var icon: String? = null + var iconSize: Int = 0 + var iconUrl: String? = null + + inputStream.use { + val parser = Xml.newPullParser() + parser.setInput(inputStream.reader()) + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.eventType == XmlPullParser.START_TAG) { + when (parser.name) { + "ShortName" -> { + parser.next() + if (parser.eventType == XmlPullParser.TEXT) { + label = parser.text + } + } + + "LongName" -> { + parser.next() + if (parser.eventType == XmlPullParser.TEXT) { + if (label != null) label = parser.text + } + } + + "Image" -> { + val size = + parser.getAttributeValue(null, "width")?.toIntOrNull() ?: 0 + if (size > iconSize || iconUrl == null) { + parser.next() + if (parser.eventType == XmlPullParser.TEXT) { + iconUrl = parser.text + iconSize = size + } + } + } + + "Url" -> { + if (parser.getAttributeValue(null, "type") == "text/html") { + val rel = parser.getAttributeValue(null, "rel") + if (rel == null || rel == "results") { + val template = + parser.getAttributeValue(null, "template") + ?.takeIf { it.isNotEmpty() } ?: continue + urlTemplate = template + .replace("{searchTerms}", "\${1}") + .replace("{startPage?}", "1") + } + } + } + + else -> continue + } + } + } + + val localIconUrl = iconUrl?.let { + val uri = Uri.parse(it) + createIcon(uri, iconSize) + } + + return WebsearchActionBuilder( + label = label ?: "", + icon = if (localIconUrl == null) SearchActionIcon.Search else SearchActionIcon.Custom, + customIcon = localIconUrl, + iconColor = if (localIconUrl == null) 0 else 1, + urlTemplate = urlTemplate ?: "" + ) + } + } catch (e: IOException) { + + } catch (e: XmlPullParserException) {} + return null + } + override suspend fun createIcon(uri: Uri, size: Int): String? = withContext( Dispatchers.IO ) {