From 838c8e6aee50180f7e71cc9756618aee921613e9 Mon Sep 17 00:00:00 2001 From: Jonas Kalderstam Date: Mon, 19 Jun 2023 22:53:26 +0200 Subject: [PATCH] Added Feeder News feed for all users. So sorry for modifying everyone's subscriptions! It only happens once for each user. Feel free to delete it if you don't want it. --- CHANGELOG.md | 9 +- .../feeder/archmodel/Repository.kt | 23 +++ .../feeder/archmodel/SettingsStore.kt | 11 +- release.sh | 22 ++- scripts/changelog-to-hugo.main.kts | 153 +++++++++++++++ scripts/convert-changelog.main.kts | 175 ++++++++++++++++++ 6 files changed, 382 insertions(+), 11 deletions(-) create mode 100755 scripts/changelog-to-hugo.main.kts create mode 100755 scripts/convert-changelog.main.kts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8952a415d..5d015b390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1252,7 +1252,7 @@ bruh (1): mm4c (1): * [6fe26dc0] Updated Dutch translation using Weblate -# 2.1.0 +# 2.1.0-1 Jonas Kalderstam (19): * [24c0c8fd] Implemented multi device sync * [211b1281] Fixed spaces getting replaced by + in feed titles @@ -1396,13 +1396,6 @@ zmni (1): Éfrit (1): * [5959c512] Updated French translation using Weblate -# 2.1.0-beta.1 -Jonas Kalderstam (1): - * [24c0c8fd] Implemented multi device sync - -Meiru (1): - * [9957d68b] Updated Japanese translation using Weblate - # 2.0.14 Jonas Kalderstam (1): * [90e8048c] Fixed spaces getting replaced by + in feed titles diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt index acd371470..c7236d5bb 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt @@ -24,6 +24,7 @@ import com.nononsenseapps.feeder.db.room.RemoteFeed import com.nononsenseapps.feeder.db.room.SyncDevice import com.nononsenseapps.feeder.db.room.SyncRemote import com.nononsenseapps.feeder.model.workmanager.SyncServiceSendReadWorker +import com.nononsenseapps.feeder.model.workmanager.requestFeedSync import com.nononsenseapps.feeder.sync.SyncRestClient import com.nononsenseapps.feeder.ui.compose.feed.FeedListItem import com.nononsenseapps.feeder.ui.compose.navdrawer.DrawerItemWithUnreadCount @@ -60,6 +61,28 @@ class Repository(override val di: DI) : DIAware { private val syncClient: SyncRestClient by instance() private val workManager: WorkManager by instance() + init { + addFeederNewsIfInitialStart() + } + + private fun addFeederNewsIfInitialStart() { + if (!settingsStore.addedFeederNews.value) { + applicationCoroutineScope.launch { + val feedId = feedStore.upsertFeed( + Feed( + title = "Feeder News", + url = URL("https://news.nononsenseapps.com/index.atom"), + ), + ) + settingsStore.setAddedFeederNews(true) + requestFeedSync( + di = di, + feedId = feedId, + ) + } + } + } + val showOnlyUnread: StateFlow = settingsStore.showOnlyUnread fun setShowOnlyUnread(value: Boolean) = settingsStore.setShowOnlyUnread(value) diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt index 47cf6440f..006b3ee4b 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt @@ -34,6 +34,13 @@ class SettingsStore(override val di: DI) : DIAware { private val sp: SharedPreferences by instance() private val blocklistDao: BlocklistDao by instance() + private val _addedFeederNews = MutableStateFlow(sp.getBoolean(PREF_ADDED_FEEDER_NEWS, false)) + val addedFeederNews: StateFlow = _addedFeederNews.asStateFlow() + fun setAddedFeederNews(value: Boolean) { + sp.edit().putBoolean(PREF_ADDED_FEEDER_NEWS, value).apply() + _addedFeederNews.value = value + } + private val _showOnlyUnread = MutableStateFlow(sp.getBoolean(PREF_SHOW_ONLY_UNREAD, true)) val showOnlyUnread: StateFlow = _showOnlyUnread.asStateFlow() fun setShowOnlyUnread(value: Boolean) { @@ -382,9 +389,9 @@ const val PREF_WELCOME_DONE = "pref_welcome_done" const val PREF_SHOW_ONLY_UNREAD = "pref_show_only_unread" /** - * Boolean indicating if only bookmarked items should be shown + * Boolean indicating if Feeder News feed has been added or not */ -const val PREF_SHOW_ONLY_BOOKMARKED = "pref_show_only_bookmarked" +const val PREF_ADDED_FEEDER_NEWS = "pref_added_feeder_news" /** * These indicate which fragment to open by default diff --git a/release.sh b/release.sh index 4b2c4ebf1..58782792d 100755 --- a/release.sh +++ b/release.sh @@ -46,7 +46,7 @@ then fi CL="# ${NEXT_VERSION} -$(git shortlog -w76,2,9 --format='* [%h] %s' "${CURRENT_VERSION}..HEAD") +$(git shortlog -w76,2,9 --max-parents=1 --format='* [%h] %s' "${CURRENT_VERSION}..HEAD") " tmpfile="$(mktemp)" @@ -100,3 +100,23 @@ then fi git checkout app/src/main/res fastlane/metadata/android + +read -r -p "Post to feed? [y/N] " response +if [[ "$response" =~ ^[yY]$ ]] +then + scripts/changelog-to-hugo.main.kts ../feeder-news/content/posts/ "${NEXT_VERSION}" + pushd ../feeder-news + git add content/posts/ + git diff --staged + git commit -m "Released ${NEXT_VERSION}" + popd +fi + +read -r -p "Push the lot? [y/N] " response +if [[ "$response" =~ ^[yY]$ ]] +then + git push --follow-tags + pushd ../feeder-news + git push + popd +fi diff --git a/scripts/changelog-to-hugo.main.kts b/scripts/changelog-to-hugo.main.kts new file mode 100755 index 000000000..fbf8c42ee --- /dev/null +++ b/scripts/changelog-to-hugo.main.kts @@ -0,0 +1,153 @@ +#!/usr/bin/env kotlin +@file:DependsOn("org.jetbrains:markdown-jvm:0.4.1") +@file:DependsOn("net.pwall.mustache:kotlin-mustache:0.10") + +import java.io.File +import java.util.concurrent.TimeUnit +import net.pwall.mustache.parser.Parser +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.html.HtmlGenerator +import org.intellij.markdown.parser.MarkdownParser + +val flavour = CommonMarkFlavourDescriptor() + +data class ChangelogEntry( + val version: String, + val content: String, +) { + val timestamp: String + get() = "git log -1 --format=%aI $version".runCommand() + + val title: String + get() = "$version released" +} + +fun parseChangelog(): List { + val src = File("CHANGELOG.md").readText() + val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(src) + val entries = mutableListOf() + val sb = StringBuilder() + + recurseMarkdown( + node = parsedTree, + src = src, + version = "", + sb = sb, + entries = entries, + ) + + return entries +} + +fun recurseMarkdown( + node: ASTNode, + src: String, + version: String, + sb: StringBuilder, + entries: MutableList, +): String { + var newVersion = version + var ignoreContent = false + when (node.type) { + MarkdownElementTypes.MARKDOWN_FILE -> { + // Keep going directly + ignoreContent = true + } + + MarkdownElementTypes.ATX_1 -> { + // Header marks boundary between entries + if (sb.isNotBlank()) { + entries.add( + ChangelogEntry( + version = newVersion, + content = sb.toString(), + ), + ) + sb.clear() + } + val textNode = node.children.last() + return src.slice(textNode.startOffset until textNode.endOffset).trim() + } + } + + if (ignoreContent) { + for (child in node.children) { + newVersion = recurseMarkdown( + node = child, + src = src, + version = newVersion, + sb = sb, + entries = entries, + ) + } + } else { + val content = src.slice(node.startOffset until node.endOffset) + sb.append(content) + } + return newVersion +} + +fun generateHugoEntries(targetDir: File, entries: List) { + val hugoTemplateString = """ + --- + title: "{{title}}" + date: {{timestamp}} + draft: false + thumbnail: "feature.png" + --- + {{&content}} + """.trimIndent() + + println("${entries.size} entries") + + val parser = Parser() + val hugoTemplate = parser.parse(hugoTemplateString) + + entries.forEach { entry -> + val targetFile = targetDir.resolve("${entry.version}.md") + if (targetFile.isFile) { + if (!targetFile.delete()) { + error("Failed to delete existing file: $targetFile") + } + } + targetDir.resolve("${entry.version}.md").bufferedWriter().use { writer -> + writer.write(hugoTemplate.processToString(entry)) + } + } +} + +fun String.runCommand(): String { + val parts = this.split("\\s".toRegex()) + val proc = ProcessBuilder(*parts.toTypedArray()) + .directory(File("/home/jonas/workspace/feeder")) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + proc.waitFor(2, TimeUnit.SECONDS) + return proc.inputStream.bufferedReader().readText().trim() +} + +// $args + +val targetDir = args.firstOrNull() + ?.let { File(it) } + ?: error("Expects target directory as first argument") + +if (!targetDir.isDirectory) { + error("$targetDir does not exist or is not a directory!") +} + +// To only generate a specific tag out of the changelog +val tag = args.getOrNull(1) + +val entries = parseChangelog() + +generateHugoEntries( + targetDir = targetDir, + entries = entries.filter { + tag == null || it.version == tag + }, +) diff --git a/scripts/convert-changelog.main.kts b/scripts/convert-changelog.main.kts new file mode 100755 index 000000000..da0b1b592 --- /dev/null +++ b/scripts/convert-changelog.main.kts @@ -0,0 +1,175 @@ +#!/usr/bin/env kotlin +@file:DependsOn("org.jetbrains:markdown-jvm:0.4.1") +@file:DependsOn("net.pwall.mustache:kotlin-mustache:0.10") + +import java.io.File +import java.util.concurrent.TimeUnit +import net.pwall.mustache.parser.Parser +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.html.HtmlGenerator +import org.intellij.markdown.parser.MarkdownParser + +val flavour = CommonMarkFlavourDescriptor() + +data class ChangelogEntry( + val title: String, + val content: String, +) { + val id: String + get() = "https://github.com/spacecowboy/Feeder/blob/master/CHANGELOG.md/$title" + val link: String + get() = "https://github.com/spacecowboy/Feeder/blob/master/CHANGELOG.md#${ + title.replace( + ".", + "", + ) + }" + + val timestamp: String + get() = "git log -1 --format=%aI $title".runCommand() +} + +fun parseChangelog(): MutableList { + val src = File("CHANGELOG.md").readText() + val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(src) + val entries = mutableListOf() + val sb = StringBuilder() + + recurseMarkdown(parsedTree, src, "", sb, entries) + + return entries +} + +fun recurseMarkdown( + node: ASTNode, + src: String, + version: String, + sb: StringBuilder, + entries: MutableList, +): String { + var newVersion = version + var ignoreContent = false + when (node.type) { + MarkdownElementTypes.MARKDOWN_FILE -> { + // Keep going directly + ignoreContent = true + } + + MarkdownElementTypes.ATX_1 -> { + // Header marks boundary between entries + if (sb.isNotBlank()) { + entries.add( + ChangelogEntry( + title = newVersion, + content = sb.toString(), + ), + ) + sb.clear() + } + val textNode = node.children.last() + return src.slice(textNode.startOffset until textNode.endOffset).trim() + } + } + + if (ignoreContent) { + for (child in node.children) { + newVersion = recurseMarkdown(child, src, newVersion, sb, entries) + } + } else { + val html = HtmlGenerator(src, node, flavour, false).generateHtml() + sb.append(html) + } + return newVersion +} + +fun generateAtomChangelog(entries: List) { + val atomTemplateString = """ + + + https://github.com/spacecowboy/Feeder/blob/master/CHANGELOG.md + Feeder Changelog + {{timestamp}} + + Jonas + jonas@nononsenseapps.com + + + + What's new in Feeder + https://github.com/spacecowboy/Feeder/blob/master/graphics/f_foreground_512.png?raw=true + + {{#entry}} + + {{id}} + + {{title}} + {{timestamp}} + + Jonas + jonas@nononsenseapps.com + + + + + + {{/entry}} + + + """.trimIndent() + + val parser = Parser() + val atomTemplate = parser.parse(atomTemplateString) + + val y = atomTemplate.processToString( + mapOf( + "timestamp" to entries.first().timestamp, + "entry" to entries, + ), + ) + + println(y) +} + +fun String.runCommand(): String { + val parts = this.split("\\s".toRegex()) + val proc = ProcessBuilder(*parts.toTypedArray()) + .directory(File("/home/jonas/workspace/feeder")) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + proc.waitFor(2, TimeUnit.SECONDS) + return proc.inputStream.bufferedReader().readText().trim() +} + +// $args +val entries = parseChangelog() + +generateAtomChangelog( + entries.filter { + val numbers = it.title.split(".") + when { + numbers.first().toInt() > 2 -> { + true + } + + numbers.first().toInt() == 2 -> { + when { + numbers[1].toInt() >= 4 -> { + true + } + + else -> { + false + } + } + } + + else -> { + false + } + } + }, +)