diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..69bdd94 --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,51 @@ +name: 'Build and release' + +on: workflow_dispatch + +jobs: + build: + name: 'Build the project' + runs-on: ubuntu-latest + outputs: + tag-name: ${{ steps.tag.outputs.tag }} + steps: + - name: 'Checkout Code' + uses: actions/checkout@v3 + with: + ref: master + - name: 'Set up Java' + uses: actions/setup-java@v2 + with: + java-version: 11 + distribution: temurin + cache: 'gradle' + - name: 'Build shadow jar' + uses: gradle/gradle-build-action@v2 + with: + arguments: shadowJar + - name: 'Upload Artifacts' + uses: actions/upload-artifact@v2 + with: + name: artifacts + path: ./build/libs/*-all.jar + - name: 'export version number' + id: tag + run: ./gradlew properties -q | grep "version:" | awk '{ print "::set-output name=tag::"$2 }' + + release-content: + name: 'Create GitHub Release' + runs-on: ubuntu-latest + needs: [ build ] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: 'Download Artifacts' + uses: actions/download-artifact@v2 + with: + name: artifacts + - name: 'Create GitHub Release' + uses: softprops/action-gh-release@v1 + with: + files: ./* + tag_name: ${{needs.build.outputs.tag-name}} + name: version v${{needs.build.outputs.tag-name}} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ff9e184..1f80bb5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,12 +3,18 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.6.10" kotlin("plugin.serialization") version "1.6.10" - application + id("com.github.johnrengelman.shadow") version "7.1.2" id("groovy") } group = "me.shalva" -version = "1.0-SNAPSHOT" +version = "1.0.0" + +val jar by tasks.getting(Jar::class) { + manifest { + attributes["Main-Class"] = "MainKt" + } +} repositories { mavenCentral() @@ -17,8 +23,9 @@ repositories { dependencies { implementation("junit:junit:4.13.2") testImplementation(kotlin("test-junit")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - implementation("com.squareup.okhttp3:okhttp:4.9.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") + implementation("com.squareup.okhttp3:okhttp:4.10.0") + implementation("info.picocli:picocli:4.6.3") } tasks.test { @@ -27,8 +34,4 @@ tasks.test { tasks.withType() { kotlinOptions.jvmTarget = "1.8" -} - -application { - mainClassName = "MainKt" } \ No newline at end of file diff --git a/readme.md b/readme.md index b29517c..d0854e4 100644 --- a/readme.md +++ b/readme.md @@ -1,23 +1,30 @@ # What is it? -This app shows your youtube history, grouped by months and sorted by watch count +This app shows your YouTube history, grouped by months and sorted by watch count # Instructions -1. git clone or download and extract project -2. Go to [google takeout page](https://takeout.google.com/settings/takeout), deselect everything except *YouTube and YouTube Music*. -3. Under *YouTube and YouTube Music* click on *Multiple formats* and change history type from html to json -4. Under *YouTube and YouTube Music* click on *All YouTube data included* and deselect everything except history -5. click next step, choose your method and download files -6. Install Java 11 and make sure JAVA_HOME is set -7. extract `watch-history.json` file into projects directory `/src/main/resources/watch-history.json` -8. If you are on linux open console in projects root directory and execute `./gradlew run` -9. If you are on windows open cmd in projects root directory and execute `gradlew.bat run` -10. The output from the program is MD formatted, you can copy&paste it in your repo on Github or in Notion. +1. Go to [google takeout page](https://takeout.google.com/settings/takeout), deselect everything except *YouTube and + YouTube Music*. +2. Under *YouTube and YouTube Music* click on *Multiple formats* and change history type from html to json +3. Under *YouTube and YouTube Music* click on *All YouTube data included* and deselect everything except history +4. click next step, choose your method and download files +5. Install Java 11 and make sure JAVA_HOME is set +6. Download .jar file from the Releases page and run it with `java -jar youtube-history-all.jar watch-history.json` +7. The output from the program is MD formatted, you can copy&paste it in your repo on Github or in Notion. # Recover removed videos -- you can use google to find video name by url. Then search the name and you will find many similar videos -- these websites maybe helpful: [quiteaplaylist](https://quiteaplaylist.com) and [this post on archivarix](https://archivarix.com/en/blog/download-deleted-youtube-videos/) + +- Search deleted video url with Google. You will find many other similar videos. +- these websites maybe helpful: [quiteaplaylist](https://quiteaplaylist.com) + and [this post on archivarix](https://archivarix.com/en/blog/download-deleted-youtube-videos/) + +# Build from source + +clone the repository and in the project directory run `./gradlew shadowjar` +then inside `build/libs` folder should be a jar file which you can run +with `java -jar filename-all.jar history.json` # this is what it looks like + ![image](https://user-images.githubusercontent.com/22417494/124386451-89aa5980-dceb-11eb-8cd3-1d8fec57ad9a.png) diff --git a/settings.gradle.kts b/settings.gradle.kts index 4006c56..2de0946 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1 @@ - -rootProject.name = "Youtube_history_parser" - +rootProject.name = "Youtube_history_parser" \ No newline at end of file diff --git a/src/main/kotlin/exts.kt b/src/main/kotlin/exts.kt deleted file mode 100644 index f5cdbe3..0000000 --- a/src/main/kotlin/exts.kt +++ /dev/null @@ -1,15 +0,0 @@ -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json - -fun getResourceAsText(path: String): String? { - return object {}.javaClass.getResource(path)?.readText() -} - -fun getVideoHistoryJSON(path: String): List { - val rawJson = getResourceAsText(path) - val parser = Json { - ignoreUnknownKeys = true - } - - return parser.decodeFromString(rawJson!!) -} \ No newline at end of file diff --git a/src/main/kotlin/main.kt b/src/main/kotlin/main.kt index 7529fca..2fa4a75 100644 --- a/src/main/kotlin/main.kt +++ b/src/main/kotlin/main.kt @@ -1,19 +1,42 @@ import models.YoutubeHistory +import picocli.CommandLine +import java.io.File +import java.util.concurrent.Callable +import kotlin.system.exitProcess -fun main() { - val youtubeHistory = YoutubeHistory(resourcePath = "/watch-history.json", minVideoClicks = 10) +@CommandLine.Command( + name = "Youtube History", + mixinStandardHelpOptions = true, + version = ["1.0.0"], + description = ["Prints the statistics of your videos in a Markdown format"] +) +class CLI : Callable { - val results = StringBuilder().apply { - append("# TOP 10") - appendLine() - append("### " + youtubeHistory.totalTimeWatchedForTopTenVideos()) - appendLine() - append(youtubeHistory.getTopTenVideos()) - appendLine() - append("### " + youtubeHistory.totalTimeWatched()) - appendLine() - append(youtubeHistory.getMusicHistory()) - } + @CommandLine.Parameters(description = ["File containing the history"]) + lateinit var historyJsonFile: File + + override fun call(): Int { + + val youtubeHistory = YoutubeHistory( + resourcePath = historyJsonFile, + minVideoClicks = 0 + ) - println(results) + val results = StringBuilder().apply { + append("# TOP 10") + appendLine() + append("### " + youtubeHistory.totalTimeWatchedForTopTenVideos()) + appendLine() + append(youtubeHistory.getTopTenVideos()) + appendLine() + append("### " + youtubeHistory.totalTimeWatched()) + appendLine() + append(youtubeHistory.getMusicHistory()) + } + + println(results) + return 0 + } } + +fun main(args: Array): Unit = exitProcess(CommandLine(CLI()).execute(*args)) diff --git a/src/main/kotlin/models/YoutubeHistory.kt b/src/main/kotlin/models/YoutubeHistory.kt index 1e4f59d..d6b83fa 100644 --- a/src/main/kotlin/models/YoutubeHistory.kt +++ b/src/main/kotlin/models/YoutubeHistory.kt @@ -4,11 +4,12 @@ import VideoLengthProvider import YoutubeVideo import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import java.io.File import java.time.Duration class YoutubeHistory( private val videoLengthProvider: VideoLengthProvider = VideoLengthProvider(), - resourcePath: String = "watch-history.json", + resourcePath: File, val minVideoClicks: Int = 10, ) { @@ -34,12 +35,11 @@ class YoutubeHistory( ) }.filter { it.timesClicked > minVideoClicks - } + }.ifEmpty { throw NoVideoFoundException(minVideoClicks) } } - private fun getVideoHistoryJSON(path: String): List { - val rawJson = javaClass.getResource(path)?.readText() - ?: throw IllegalStateException("Could not read $path, does it exists?") + private fun getVideoHistoryJSON(history: File): List { + val rawJson = history.readText() val parser = Json { ignoreUnknownKeys = true } @@ -112,4 +112,8 @@ class YoutubeHistory( accumulator.plus(videoDurationByURL) }.run(::format) } -} \ No newline at end of file +} + +class NoVideoFoundException( + minVideoClicks: Int, +) : Exception("There is no video that appears $minVideoClicks times, please decrease minVideoClicks parameter") \ No newline at end of file diff --git a/src/test/java/Testings.kt b/src/test/java/Testings.kt index 933e9b5..f71a8f5 100644 --- a/src/test/java/Testings.kt +++ b/src/test/java/Testings.kt @@ -3,20 +3,6 @@ import java.time.Duration class Testing { - private val videos = getVideoHistoryJSON("testData.json") - - @Test - fun `resources file loads`() { - assert(videos.isNotEmpty()) - } - - @Test - fun `sort by most watched urls`() { - assert(sortVideos(videos).isNotEmpty()) - assert(sortVideos(videos).size == 3) - // TODO - } - @Test fun `video length check`() { val videoLengthProvider = VideoLengthProvider() @@ -26,8 +12,4 @@ class Testing { assert(videoLengthProvider.getVideoDurationByID("_Kmh4BbJPz8") == Duration.ofSeconds(31)) } -} - -fun sortVideos(videos: List): List { - return videos } \ No newline at end of file