Skip to content

Instantly share code, notes, and snippets.

@crysxd
Last active August 27, 2022 18:00
Show Gist options
  • Save crysxd/50512bde5bc843b22e2b8861530722dc to your computer and use it in GitHub Desktop.
Save crysxd/50512bde5bc843b22e2b8861530722dc to your computer and use it in GitHub Desktop.
Clear Trash of Adobe Creative Cloud

This is a super ugly Kotlin script to clear the entire trash of Adobe Creative Cloud. There is no "delete all option", so you are stuck with selecting files by shift-clicking and then watching your browser die. I used this script to delete 80.000 files in a few hours. I added some waits to not get flagged by some system, but not sure if needed.

As a dide note, not sure why but the number of "total" files did not constantly decrease although new files were returned every iteraration. It only seems to be an "visual" issue, files get constantly deleted.

While this script isn't copy + paste + run, you can adopt it to your needs and also easily translate it in other languages :) I actually did run this as a Android integration test.

You'll get the Bearer token by visiting creativecloud.adobe.com and then looking through the browser's network logs to find the token:

Screenshot 2022-08-27 at 19 55 10

package com.test.something
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONObject
import org.junit.Test
import timber.log.Timber
import java.nio.file.Files.delete
class ClearAdobeTrash {
private val bearer =
"Bearer eyJhbGciOiJ....from browser"
@Test
fun clearAll() = runBlocking {
val okHttp = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT))
.build()
Timber.plant(Timber.DebugTree())
Timber.i("Starting to clear out files")
var attempts = 0
while(attempts < 100){
try {
okHttp.listFiles()
attempts = 1000
} catch (e: Exception) {
Timber.e(e)
delay(10_000)
attempts++
}
}
}
private suspend fun OkHttpClient.listFiles(): Unit = withContext(Dispatchers.IO) {
Timber.i("Loading next page")
val body = """
{
"sort_orderby": "modify_date",
"creative_cloud_archive": true,
"creative_cloud_discarded_directly": true,
"fetch_fields": {
"includes": [
"creative_cloud_colortheme",
"creative_cloud_gradient",
"_embedded"
]
},
"q": "*",
"scope": [
"creative_cloud"
],
"hints": {
"creative_cloud_rendition_v2": true
},
"sort_order": "asc",
"limit": 100
}
""".trimIndent()
val request = Request.Builder()
.url("https://adobesearch.adobe.io/universal-search/v2/search")
.post(body.toRequestBody(contentType = "application/vnd.adobe.search-request+json".toMediaType()))
.header("X-Api-Key", "CCXWeb1")
.header("X-Product", "AssetsWeb/2.0")
.header("X-Product-Location", "Assets 2.0")
.header("Accept-Language", "en-GB,en;q=0.9")
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15")
.header("Authorization", bearer)
.build()
val response = newCall(request).execute()
val json = JSONObject(response.body!!.string())
val results = json.getJSONArray("result_sets").getJSONObject(0).getJSONArray("items")
val total = json.getJSONObject("metrics").getInt("total_hits")
Timber.i("Deleting ${results.length()} / $total items")
val jobs = (0 until results.length()).map {
val item = results.getJSONObject(it)
val href = item.getJSONObject("_links").getJSONObject("original").getString("href")
val etag = item.getJSONObject("etag").getString("primary")
val name = item.getString("asset_name")
async {
delete(name = name, etag = etag, href = href)
}
}
jobs.awaitAll()
if (total > results.length()) {
Timber.i("Loading next page in 10s, ${total - results.length()} left")
delay(10_000)
listFiles()
}
}
private suspend fun OkHttpClient.delete(name: String, href: String, etag: String) {
delay((0..15_000).random().toLong())
Timber.d(" -> $name")
val body = """
{
"op": "delete",
"target": {
"if-match": "\"$etag\"",
"href": "$href"
},
"recursive": false
}
""".trimIndent()
val request = Request.Builder()
.url("https://platform-cs.adobe.io/content/directory/ops")
.post(body.toRequestBody(contentType = "application/vnd.adobe.search-request+json".toMediaType()))
.header("X-Api-Key", "CCXWeb1")
.header("X-Product", "AssetsWeb/2.0")
.header("X-Product-Location", "Assets 2.0")
.header("Accept-Language", "en-GB,en;q=0.9")
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15")
.header("Authorization", bearer)
.build()
assertWithMessage("Failed to delete $href").that(newCall(request).execute().code).isEqualTo(204)
Timber.d(" <- $name")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment