From 5200821084fb8b7b4cd3e89b4d009821f0253af7 Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Thu, 29 Feb 2024 22:41:52 +0100 Subject: [PATCH 1/2] Add base url --- .../admin/AdminConfigController.kt | 7 +-- .../controllers/api/EpisodeController.kt | 10 ++++- .../fr/shikkanime/entities/enums/Link.kt | 9 +++- .../jobs/FetchDeprecatedEpisodeJob.kt | 19 ++++++-- .../kotlin/fr/shikkanime/modules/Routing.kt | 15 +++++-- .../AnimationDigitalNetworkPlatform.kt | 18 ++++++-- .../platforms/CrunchyrollPlatform.kt | 23 +++++++--- .../platforms/DisneyPlusPlatform.kt | 2 +- .../platforms/PrimeVideoPlatform.kt | 1 - .../repositories/AnimeRepository.kt | 6 ++- .../repositories/MemberRepository.kt | 8 +++- .../fr/shikkanime/services/EpisodeService.kt | 3 +- .../fr/shikkanime/services/ImageService.kt | 7 ++- .../fr/shikkanime/services/MemberService.kt | 8 +++- .../shikkanime/services/SimulcastService.kt | 3 +- .../services/caches/AnimeCacheService.kt | 13 +++--- .../socialnetworks/BskySocialNetwork.kt | 6 ++- .../socialnetworks/DiscordSocialNetwork.kt | 22 +++------- .../socialnetworks/TwitterSocialNetwork.kt | 23 +++++++--- .../kotlin/fr/shikkanime/utils/Constant.kt | 11 ++--- .../fr/shikkanime/wrappers/ThreadsWrapper.kt | 6 ++- .../db/changelog/2024/02/02-changelog.xml | 3 +- .../db/changelog/2024/02/03-changelog.xml | 3 +- .../db/changelog/2024/02/05-changelog.xml | 2 +- .../db/changelog/2024/02/10-changelog.xml | 3 +- .../db/changelog/2024/02/11-changelog.xml | 3 +- src/main/resources/templates/admin/images.ftl | 6 ++- src/main/resources/templates/site/_layout.ftl | 2 +- .../resources/templates/site/_navigation.ftl | 12 ++++-- src/main/resources/templates/site/anime.ftl | 3 +- .../templates/site/components/anime.ftl | 3 +- src/main/resources/templates/site/home.ftl | 3 +- .../resources/templates/site/presentation.ftl | 19 +++++--- .../kotlin/fr/shikkanime/OldEpisodeScraper.kt | 43 ++++++++++++++----- .../AnimationDigitalNetworkPlatformTest.kt | 5 ++- .../TwitterSocialNetworkTest.kt | 6 ++- 36 files changed, 227 insertions(+), 109 deletions(-) diff --git a/src/main/kotlin/fr/shikkanime/controllers/admin/AdminConfigController.kt b/src/main/kotlin/fr/shikkanime/controllers/admin/AdminConfigController.kt index 21420d16..3dda3b9a 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/admin/AdminConfigController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/admin/AdminConfigController.kt @@ -47,12 +47,7 @@ class AdminConfigController { @AdminSessionAuthenticated private fun postConfig(@PathParam("uuid") uuid: UUID, @BodyParam parameters: Parameters): Response { configService.update(uuid, parameters) - - Constant.abstractSocialNetworks.forEach { - it.logout() - it.login() - } - + Constant.abstractSocialNetworks.forEach { it.logout() } return Response.redirect(Link.CONFIG.href) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeController.kt b/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeController.kt index c5efe5c3..17e83b91 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/api/EpisodeController.kt @@ -50,7 +50,15 @@ class EpisodeController : HasPageableRoute() { @QueryParam("desc") descParam: String?, ): Response { val (page, limit, sortParameters) = pageableRoute(pageParam, limitParam, sortParam, descParam) - return Response.ok(episodeCacheService.findAllBy(CountryCode.fromNullable(countryParam) ?: CountryCode.FR, animeParam, sortParameters, page, limit)) + return Response.ok( + episodeCacheService.findAllBy( + CountryCode.fromNullable(countryParam) ?: CountryCode.FR, + animeParam, + sortParameters, + page, + limit + ) + ) } @Path("/{uuid}/media-image") diff --git a/src/main/kotlin/fr/shikkanime/entities/enums/Link.kt b/src/main/kotlin/fr/shikkanime/entities/enums/Link.kt index 6a189d56..028b27aa 100644 --- a/src/main/kotlin/fr/shikkanime/entities/enums/Link.kt +++ b/src/main/kotlin/fr/shikkanime/entities/enums/Link.kt @@ -2,7 +2,14 @@ package fr.shikkanime.entities.enums import fr.shikkanime.utils.Constant -enum class Link(var href: String, val template: String, val icon: String, val label: String, val title: String = label, val footer: Boolean = false) { +enum class Link( + var href: String, + val template: String, + val icon: String, + val label: String, + val title: String = label, + val footer: Boolean = false +) { // Admin DASHBOARD("/admin/dashboard", "/admin/dashboard.ftl", "bi bi-pc-display", "Dashboard"), PLATFORMS("/admin/platforms", "/admin/platforms/list.ftl", "bi bi-display", "Platforms"), diff --git a/src/main/kotlin/fr/shikkanime/jobs/FetchDeprecatedEpisodeJob.kt b/src/main/kotlin/fr/shikkanime/jobs/FetchDeprecatedEpisodeJob.kt index 8b8fcd96..73a6015c 100644 --- a/src/main/kotlin/fr/shikkanime/jobs/FetchDeprecatedEpisodeJob.kt +++ b/src/main/kotlin/fr/shikkanime/jobs/FetchDeprecatedEpisodeJob.kt @@ -37,7 +37,9 @@ class FetchDeprecatedEpisodeJob : AbstractJob { val takeSize = configCacheService.getValueAsInt(ConfigPropertyKey.FETCH_OLD_EPISODE_DESCRIPTION_SIZE, 0) val now = ZonedDateTime.now().withSecond(0).withNano(0).withUTC() - val deprecatedDateTime = now.minusDays(configCacheService.getValueAsInt(ConfigPropertyKey.FETCH_DEPRECATED_EPISODE_DATE, 30).toLong()) + val deprecatedDateTime = now.minusDays( + configCacheService.getValueAsInt(ConfigPropertyKey.FETCH_DEPRECATED_EPISODE_DATE, 30).toLong() + ) val crunchyrollEpisodes = episodeService.findAllByPlatformDeprecatedEpisodes(Platform.CRUN, deprecatedDateTime) val adnEpisodes = episodeService.findAllByPlatformDeprecatedEpisodes(Platform.ANIM, deprecatedDateTime) val episodes = (crunchyrollEpisodes + adnEpisodes).shuffled().take(takeSize) @@ -134,11 +136,22 @@ class FetchDeprecatedEpisodeJob : AbstractJob { } } - private suspend fun normalizeContent(episode: Episode, httpRequest: HttpRequest, accessToken: String, cms: CrunchyrollWrapper.CMS): JsonObject? { + private suspend fun normalizeContent( + episode: Episode, + httpRequest: HttpRequest, + accessToken: String, + cms: CrunchyrollWrapper.CMS + ): JsonObject? { return when (episode.platform) { Platform.CRUN -> { try { - httpRequest.getBrowser(normalizeUrl(episode.platform!!, episode.anime!!.countryCode!!, episode.url!!)) + httpRequest.getBrowser( + normalizeUrl( + episode.platform!!, + episode.anime!!.countryCode!!, + episode.url!! + ) + ) } catch (e: Exception) { return null } diff --git a/src/main/kotlin/fr/shikkanime/modules/Routing.kt b/src/main/kotlin/fr/shikkanime/modules/Routing.kt index 49b471b1..05a9fc2c 100644 --- a/src/main/kotlin/fr/shikkanime/modules/Routing.kt +++ b/src/main/kotlin/fr/shikkanime/modules/Routing.kt @@ -257,7 +257,12 @@ private suspend fun handleMultipartResponse(call: ApplicationCall, response: Res call.respondBytes(map["image"] as ByteArray, map["contentType"] as ContentType) } -private suspend fun handleTemplateResponse(call: ApplicationCall, controller: Any, replacedPath: String, response: Response) { +private suspend fun handleTemplateResponse( + call: ApplicationCall, + controller: Any, + replacedPath: String, + response: Response +) { val configCacheService = Constant.injector.getInstance(ConfigCacheService::class.java) val simulcastCacheService = Constant.injector.getInstance(SimulcastCacheService::class.java) @@ -288,7 +293,8 @@ private suspend fun handleTemplateResponse(call: ApplicationCall, controller: An } ?: Constant.NAME modelMap["seoDescription"] = configCacheService.getValueAsString(ConfigPropertyKey.SEO_DESCRIPTION) - configCacheService.getValueAsString(ConfigPropertyKey.GOOGLE_SITE_VERIFICATION_ID)?.let { modelMap["googleSiteVerification"] = it } + configCacheService.getValueAsString(ConfigPropertyKey.GOOGLE_SITE_VERIFICATION_ID) + ?.let { modelMap["googleSiteVerification"] = it } simulcastCacheService.currentSimulcast?.let { modelMap["currentSimulcast"] = it } call.respond(response.status, FreeMarkerContent(map["template"] as String, modelMap, "", response.contentType)) @@ -341,7 +347,10 @@ private fun handleQueryParam(kParameter: KParameter, call: ApplicationCall): Any Int::class.starProjectedType.withNullability(true) -> queryParamValue?.toIntOrNull() String::class.starProjectedType.withNullability(true) -> queryParamValue CountryCode::class.starProjectedType.withNullability(true) -> CountryCode.fromNullable(queryParamValue) - UUID::class.starProjectedType.withNullability(true) -> if (queryParamValue.isNullOrBlank()) null else UUID.fromString(queryParamValue) + UUID::class.starProjectedType.withNullability(true) -> if (queryParamValue.isNullOrBlank()) null else UUID.fromString( + queryParamValue + ) + else -> throw Exception("Unknown type ${kParameter.type}") } } diff --git a/src/main/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatform.kt b/src/main/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatform.kt index 034628ee..f2baf83c 100644 --- a/src/main/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatform.kt +++ b/src/main/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatform.kt @@ -59,7 +59,8 @@ class AnimationDigitalNetworkPlatform : val show = requireNotNull(jsonObject.getAsJsonObject("show")) { "Show is null" } val season = jsonObject.getAsString("season")?.toIntOrNull() ?: 1 - var animeName = requireNotNull(show.getAsString("shortTitle") ?: show.getAsString("title")) { "Anime name is null" } + var animeName = + requireNotNull(show.getAsString("shortTitle") ?: show.getAsString("title")) { "Anime name is null" } animeName = animeName.replace(Regex("Saison \\d"), "").trim() animeName = animeName.replace(season.toString(), "").trim() animeName = animeName.replace(Regex(" -.*"), "").trim() @@ -71,7 +72,12 @@ class AnimationDigitalNetworkPlatform : val genres = show.getAsJsonArray("genres") ?: JsonArray() val contains = configuration!!.simulcasts.map { it.name.lowercase() }.contains(animeName.lowercase()) - if ((genres.isEmpty || !genres.any { it.asString.startsWith("Animation ", true) }) && !contains) throw Exception("Anime is not an animation") + if ((genres.isEmpty || !genres.any { + it.asString.startsWith( + "Animation ", + true + ) + }) && !contains) throw Exception("Anime is not an animation") var isSimulcasted = show.getAsBoolean("simulcast") == true || show.getAsString("firstReleaseYear") in (0..1).map { (zonedDateTime.year - it).toString() } || @@ -90,7 +96,10 @@ class AnimationDigitalNetworkPlatform : val releaseDate = ZonedDateTime.parse(releaseDateString) val numberAsString = jsonObject.getAsString("shortNumber") - if (numberAsString?.startsWith("Bande-annonce") == true || numberAsString?.startsWith("Bande annonce") == true || numberAsString?.startsWith("Court-métrage") == true) throw Exception( + if (numberAsString?.startsWith("Bande-annonce") == true || numberAsString?.startsWith("Bande annonce") == true || numberAsString?.startsWith( + "Court-métrage" + ) == true + ) throw Exception( "Anime is a trailer" ) @@ -102,7 +111,8 @@ class AnimationDigitalNetworkPlatform : else -> EpisodeType.EPISODE } - if (numberAsString?.contains(".") == true || show.getAsString("type") == "OAV") episodeType = EpisodeType.SPECIAL + if (numberAsString?.contains(".") == true || show.getAsString("type") == "OAV") episodeType = + EpisodeType.SPECIAL val id = jsonObject.getAsInt("id") val title = jsonObject.getAsString("name")?.ifBlank { null } diff --git a/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt b/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt index 0b135ef1..fb789734 100644 --- a/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt +++ b/src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt @@ -46,7 +46,8 @@ class CrunchyrollPlatform : AbstractPlatform simulcast.getAsString("id") } + val simulcasts = runBlocking { CrunchyrollWrapper.getSimulcasts(it.locale, accessToken) }.take(2) + .map { simulcast -> simulcast.getAsString("id") } val series = simulcasts.flatMap { simulcastId -> runBlocking { @@ -79,7 +80,8 @@ class CrunchyrollPlatform : AbstractPlatform $currentSimulcastCode") val currentSimulcastAnimes = - currentSimulcastContent.select(simulcastAnimesSelector).map { a -> a.text().lowercase() }.toSet() + currentSimulcastContent.select(simulcastAnimesSelector).map { a -> a.text().lowercase() } + .toSet() logger.info("Found ${currentSimulcastAnimes.size} animes for the current simulcast") val previousSimulcastCode = getPreviousSimulcastCode(currentSimulcastCode) @@ -90,7 +92,8 @@ class CrunchyrollPlatform : AbstractPlatform a.text().lowercase() }.toSet() + previousSimulcastContent.select(simulcastAnimesSelector).map { a -> a.text().lowercase() } + .toSet() logger.info("Found ${previousSimulcastAnimes.size} animes for the previous simulcast") val combinedSimulcasts = (currentSimulcastAnimes + previousSimulcastAnimes).toSet() @@ -277,14 +280,17 @@ class CrunchyrollPlatform : AbstractPlatform>() { diff --git a/src/main/kotlin/fr/shikkanime/repositories/AnimeRepository.kt b/src/main/kotlin/fr/shikkanime/repositories/AnimeRepository.kt index 817f0ebb..20f333fa 100644 --- a/src/main/kotlin/fr/shikkanime/repositories/AnimeRepository.kt +++ b/src/main/kotlin/fr/shikkanime/repositories/AnimeRepository.kt @@ -97,7 +97,11 @@ class AnimeRepository : AbstractRepository() { fun findAllByLikeName(countryCode: CountryCode, name: String?): List { return inTransaction { - createReadOnlyQuery(it, "FROM Anime WHERE countryCode = :countryCode AND LOWER(name) LIKE :name", getEntityClass()) + createReadOnlyQuery( + it, + "FROM Anime WHERE countryCode = :countryCode AND LOWER(name) LIKE :name", + getEntityClass() + ) .setParameter("countryCode", countryCode) .setParameter("name", "%${name?.lowercase()}%") .resultList diff --git a/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt b/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt index 9d4e5fcf..a3760a5d 100644 --- a/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt +++ b/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt @@ -3,7 +3,7 @@ package fr.shikkanime.repositories import fr.shikkanime.entities.Member import fr.shikkanime.entities.enums.Role import org.hibernate.Hibernate -import java.util.UUID +import java.util.* class MemberRepository : AbstractRepository() { private fun Member.initialize(): Member { @@ -31,7 +31,11 @@ class MemberRepository : AbstractRepository() { fun findByUsernameAndPassword(username: String, password: ByteArray): Member? { return inTransaction { - createReadOnlyQuery(it, "FROM Member WHERE username = :username AND encryptedPassword = :password", getEntityClass()) + createReadOnlyQuery( + it, + "FROM Member WHERE username = :username AND encryptedPassword = :password", + getEntityClass() + ) .setParameter("username", username) .setParameter("password", password) .resultList diff --git a/src/main/kotlin/fr/shikkanime/services/EpisodeService.kt b/src/main/kotlin/fr/shikkanime/services/EpisodeService.kt index 3e822f5c..d7211806 100644 --- a/src/main/kotlin/fr/shikkanime/services/EpisodeService.kt +++ b/src/main/kotlin/fr/shikkanime/services/EpisodeService.kt @@ -91,7 +91,8 @@ class EpisodeService : AbstractService() { override fun save(entity: Episode): Episode { val copy = entity.anime!!.copy() - val anime = animeService.findAllByLikeName(copy.countryCode!!, copy.name!!).firstOrNull() ?: animeService.save(copy) + val anime = + animeService.findAllByLikeName(copy.countryCode!!, copy.name!!).firstOrNull() ?: animeService.save(copy) if (anime.banner.isNullOrBlank() && !copy.banner.isNullOrBlank()) { anime.banner = copy.banner diff --git a/src/main/kotlin/fr/shikkanime/services/ImageService.kt b/src/main/kotlin/fr/shikkanime/services/ImageService.kt index e70f9ee7..1b1d70c3 100644 --- a/src/main/kotlin/fr/shikkanime/services/ImageService.kt +++ b/src/main/kotlin/fr/shikkanime/services/ImageService.kt @@ -155,7 +155,12 @@ object ImageService { file.writeBytes(FileManager.toGzip(ObjectParser.toJson(cache).toByteArray())) } - logger.info("Saved images cache part in $take ms (${toHumanReadable(cache.sumOf { it.originalSize })} -> ${toHumanReadable(cache.sumOf { it.size })})") + logger.info( + "Saved images cache part in $take ms (${toHumanReadable(cache.sumOf { it.originalSize })} -> ${ + toHumanReadable( + cache.sumOf { it.size }) + })" + ) } fun add(uuid: UUID, type: Type, url: String, width: Int, height: Int) { diff --git a/src/main/kotlin/fr/shikkanime/services/MemberService.kt b/src/main/kotlin/fr/shikkanime/services/MemberService.kt index b9360ccd..dc8d1615 100644 --- a/src/main/kotlin/fr/shikkanime/services/MemberService.kt +++ b/src/main/kotlin/fr/shikkanime/services/MemberService.kt @@ -27,7 +27,13 @@ class MemberService : AbstractService() { check(adminUsers.isEmpty()) { "Admin user already exists" } val password = RandomManager.generateRandomString(32) logger.info("Default admin password: $password") - save(Member(username = "admin", encryptedPassword = EncryptionManager.generate(password), roles = mutableSetOf(Role.ADMIN))) + save( + Member( + username = "admin", + encryptedPassword = EncryptionManager.generate(password), + roles = mutableSetOf(Role.ADMIN) + ) + ) return password } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/SimulcastService.kt b/src/main/kotlin/fr/shikkanime/services/SimulcastService.kt index ccd3afe4..739b7ebe 100644 --- a/src/main/kotlin/fr/shikkanime/services/SimulcastService.kt +++ b/src/main/kotlin/fr/shikkanime/services/SimulcastService.kt @@ -12,7 +12,8 @@ class SimulcastService : AbstractService() { override fun getRepository() = simulcastRepository - override fun findAll() = super.findAll().sortedWith(compareBy({ it.year }, { Constant.seasons.indexOf(it.season) })).reversed() + override fun findAll() = + super.findAll().sortedWith(compareBy({ it.year }, { Constant.seasons.indexOf(it.season) })).reversed() fun findBySeasonAndYear(season: String, year: Int) = simulcastRepository.findBySeasonAndYear(season, year) diff --git a/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt b/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt index 54442136..70a8bc74 100644 --- a/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt +++ b/src/main/kotlin/fr/shikkanime/services/caches/AnimeCacheService.kt @@ -25,12 +25,13 @@ class AnimeCacheService : AbstractCacheService { ) } - private val findAllByNameCache = MapCache>(classes = listOf(Anime::class.java)) { - PageableDto.fromPageable( - animeService.findAllByName(it.name, it.countryCode, it.page, it.limit), - AnimeDto::class.java - ) - } + private val findAllByNameCache = + MapCache>(classes = listOf(Anime::class.java)) { + PageableDto.fromPageable( + animeService.findAllByName(it.name, it.countryCode, it.page, it.limit), + AnimeDto::class.java + ) + } private val findBySlugCache = MapCache(classes = listOf(Anime::class.java)) { AbstractConverter.convert(animeService.findBySlug(it), AnimeDto::class.java) diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt index d9808731..6a904a15 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt @@ -4,6 +4,7 @@ import fr.shikkanime.dtos.EpisodeDto import fr.shikkanime.entities.enums.ConfigPropertyKey import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.entities.enums.LangType +import fr.shikkanime.utils.Constant import fr.shikkanime.utils.FileManager import fr.shikkanime.utils.LoggerFactory import fr.shikkanime.utils.ObjectParser.getAsString @@ -73,14 +74,15 @@ class BskySocialNetwork : AbstractSocialNetwork() { checkSession() if (!isInitialized) return - val url = "https://www.shikkanime.fr/animes/${episodeDto.anime.slug}" + val url = "${Constant.BASE_URL}/animes/${episodeDto.anime.slug}" val uncensored = if (episodeDto.uncensored) " non censuré" else "" val isVoice = if (episodeDto.langType == LangType.VOICE) " en VF " else " " val message = "\uD83D\uDEA8 ${information(episodeDto)}${uncensored} de ${episodeDto.anime.shortName} est maintenant disponible${isVoice}sur ${episodeDto.platform.platformName}\n\nBon visionnage. \uD83C\uDF7F" val webpByteArray = FileManager.encodeToWebP(mediaImage) - val imageJson = runBlocking { BskyWrapper.uploadBlob(accessJwt!!, ContentType.parse("image/webp"), webpByteArray) } + val imageJson = + runBlocking { BskyWrapper.uploadBlob(accessJwt!!, ContentType.parse("image/webp"), webpByteArray) } runBlocking { BskyWrapper.createRecord(accessJwt!!, did!!, message, listOf(BskyWrapper.Image(imageJson, url))) } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/DiscordSocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/DiscordSocialNetwork.kt index ac3b3f37..4d9d95ac 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/DiscordSocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/DiscordSocialNetwork.kt @@ -11,15 +11,11 @@ import net.dv8tion.jda.api.JDA import net.dv8tion.jda.api.JDABuilder import net.dv8tion.jda.api.entities.Activity import net.dv8tion.jda.api.entities.channel.concrete.TextChannel -import net.dv8tion.jda.api.events.session.ReadyEvent -import net.dv8tion.jda.api.hooks.ListenerAdapter import java.net.URI import java.time.ZonedDateTime import java.util.logging.Level import javax.imageio.ImageIO -private const val BASE_URL = "https://www.shikkanime.fr/" - class DiscordSocialNetwork : AbstractSocialNetwork() { private val logger = LoggerFactory.getLogger(DiscordSocialNetwork::class.java) private var isInitialized = false @@ -35,15 +31,9 @@ class DiscordSocialNetwork : AbstractSocialNetwork() { val token = requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.DISCORD_TOKEN)) if (token.isBlank()) throw Exception("Token is empty") val builder = JDABuilder.createDefault(token) - builder.setActivity(Activity.playing(BASE_URL)) - builder.addEventListeners(object : ListenerAdapter() { - override fun onReady(event: ReadyEvent) { - logger.info("DiscordSocialNetwork is ready") - isInitialized = true - jda = event.jda - } - }) - builder.build() + builder.setActivity(Activity.playing(Constant.BASE_URL)) + this.jda = builder.build().awaitReady() + isInitialized = true } catch (e: Exception) { logger.log(Level.SEVERE, "Error while initializing DiscordSocialNetwork", e) } @@ -78,13 +68,13 @@ class DiscordSocialNetwork : AbstractSocialNetwork() { embedMessage.setAuthor( episodeDto.platform.platformName, episodeDto.platform.url, - "${BASE_URL}assets/img/platforms/${episodeDto.platform.image}" + "${Constant.BASE_URL}/assets/img/platforms/${episodeDto.platform.image}" ) - embedMessage.setTitle(episodeDto.anime.shortName, "${BASE_URL}animes/${episodeDto.anime.slug}") + embedMessage.setTitle(episodeDto.anime.shortName, "${Constant.BASE_URL}/animes/${episodeDto.anime.slug}") embedMessage.setThumbnail(episodeDto.anime.image) embedMessage.setDescription("**${episodeDto.title ?: "Untitled"}**\n${StringUtils.toEpisodeString(episodeDto)}") embedMessage.setImage(episodeDto.image) - embedMessage.setFooter(Constant.NAME, "${BASE_URL}assets/img/favicons/favicon-64x64.png") + embedMessage.setFooter(Constant.NAME, "${Constant.BASE_URL}/assets/img/favicons/favicon-64x64.png") embedMessage.setTimestamp(ZonedDateTime.parse(episodeDto.releaseDateTime).toInstant()) val embed = embedMessage.build() diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt index 4062adb2..3e0e4eab 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt @@ -5,6 +5,7 @@ import fr.shikkanime.entities.enums.ConfigPropertyKey import fr.shikkanime.entities.enums.EpisodeType import fr.shikkanime.entities.enums.LangType import fr.shikkanime.entities.enums.Platform +import fr.shikkanime.utils.Constant import fr.shikkanime.utils.LoggerFactory import fr.shikkanime.utils.StringUtils import twitter4j.Twitter @@ -24,11 +25,17 @@ class TwitterSocialNetwork : AbstractSocialNetwork() { if (isInitialized) return try { - val consumerKey = requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_CONSUMER_KEY)) - val consumerSecret = requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_CONSUMER_SECRET)) - val accessToken = requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_ACCESS_TOKEN)) - val accessTokenSecret = requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_ACCESS_TOKEN_SECRET)) - if (consumerKey.isBlank() || consumerSecret.isBlank() || accessToken.isBlank() || accessTokenSecret.isBlank()) throw Exception("Twitter credentials are empty") + val consumerKey = + requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_CONSUMER_KEY)) + val consumerSecret = + requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_CONSUMER_SECRET)) + val accessToken = + requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_ACCESS_TOKEN)) + val accessTokenSecret = + requireNotNull(configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_ACCESS_TOKEN_SECRET)) + if (consumerKey.isBlank() || consumerSecret.isBlank() || accessToken.isBlank() || accessTokenSecret.isBlank()) throw Exception( + "Twitter credentials are empty" + ) twitter = TwitterFactory( ConfigurationBuilder() @@ -88,10 +95,12 @@ class TwitterSocialNetwork : AbstractSocialNetwork() { val isVoice = if (episodeDto.langType == LangType.VOICE) " en VF " else " " var configMessage = configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_MESSAGE) ?: "" - configMessage = configMessage.replace("{SHIKKANIME_URL}", "https://www.shikkanime.fr/animes/${episodeDto.anime.slug}") + configMessage = + configMessage.replace("{SHIKKANIME_URL}", "${Constant.BASE_URL}/animes/${episodeDto.anime.slug}") configMessage = configMessage.replace("{URL}", episodeDto.url) configMessage = configMessage.replace("{PLATFORM_ACCOUNT}", platformAccount(episodeDto.platform)) - configMessage = configMessage.replace("{ANIME_HASHTAG}", "#${StringUtils.getHashtag(episodeDto.anime.shortName)}") + configMessage = + configMessage.replace("{ANIME_HASHTAG}", "#${StringUtils.getHashtag(episodeDto.anime.shortName)}") configMessage = configMessage.replace("{ANIME_TITLE}", episodeDto.anime.shortName) configMessage = configMessage.replace("{EPISODE_INFORMATION}", "${information(episodeDto)}${uncensored}") configMessage = configMessage.replace("{VOICE}", isVoice) diff --git a/src/main/kotlin/fr/shikkanime/utils/Constant.kt b/src/main/kotlin/fr/shikkanime/utils/Constant.kt index bec62067..c35cc07c 100644 --- a/src/main/kotlin/fr/shikkanime/utils/Constant.kt +++ b/src/main/kotlin/fr/shikkanime/utils/Constant.kt @@ -7,7 +7,6 @@ import com.microsoft.playwright.Playwright import fr.shikkanime.modules.DefaultModule import fr.shikkanime.platforms.AbstractPlatform import fr.shikkanime.socialnetworks.AbstractSocialNetwork -import fr.shikkanime.socialnetworks.DiscordSocialNetwork import org.reflections.Reflections import java.io.File import java.time.ZoneId @@ -37,17 +36,13 @@ object Constant { val jwtDomain: String = System.getenv("JWT_DOMAIN") ?: "https://jwt-provider-domain/" val jwtRealm: String = System.getenv("JWT_REALM") ?: "ktor sample app" val jwtSecret: String = System.getenv("JWT_SECRET") ?: "secret" - const val DEFAULT_IMAGE_PREVIEW = "https://www.shikkanime.fr/assets/img/episode_no_image_preview.jpg" + + const val BASE_URL = "https://www.shikkanime.fr" + const val DEFAULT_IMAGE_PREVIEW = "$BASE_URL/assets/img/episode_no_image_preview.jpg" init { abstractPlatforms.forEach { it.loadConfiguration() } - - abstractSocialNetworks - .filter { it.javaClass != DiscordSocialNetwork::class.java } - .forEach { - it.login() - } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/wrappers/ThreadsWrapper.kt b/src/main/kotlin/fr/shikkanime/wrappers/ThreadsWrapper.kt index d5dc2aac..71960fad 100644 --- a/src/main/kotlin/fr/shikkanime/wrappers/ThreadsWrapper.kt +++ b/src/main/kotlin/fr/shikkanime/wrappers/ThreadsWrapper.kt @@ -152,8 +152,10 @@ object ThreadsWrapper { require(response.status.value == 200) { "Failed to login: ${response.status}" } val responseJson = ObjectParser.fromJson(response.bodyAsText()) - val rawBloks = responseJson.getAsJsonObject("layout").getAsJsonObject("bloks_payload").getAsJsonObject("tree").getAsJsonObject("㐟").getAsString("#") - val substring = rawBloks!!.substring(rawBloks.indexOfFirst { it == '{' }, rawBloks.indexOfLast { it == '}' } + 1) + val rawBloks = responseJson.getAsJsonObject("layout").getAsJsonObject("bloks_payload").getAsJsonObject("tree") + .getAsJsonObject("㐟").getAsString("#") + val substring = + rawBloks!!.substring(rawBloks.indexOfFirst { it == '{' }, rawBloks.indexOfLast { it == '}' } + 1) val sToken = substring.split("Bearer IGT:2:")[1] val token = sToken.substring(0, sToken.indexOf("\\\\\\\"")) diff --git a/src/main/resources/db/changelog/2024/02/02-changelog.xml b/src/main/resources/db/changelog/2024/02/02-changelog.xml index d7d5206c..bef2c340 100644 --- a/src/main/resources/db/changelog/2024/02/02-changelog.xml +++ b/src/main/resources/db/changelog/2024/02/02-changelog.xml @@ -3,7 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.25.xsd" objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS"> + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.25.xsd" + objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS"> diff --git a/src/main/resources/db/changelog/2024/02/03-changelog.xml b/src/main/resources/db/changelog/2024/02/03-changelog.xml index e252082e..3d1a48c4 100644 --- a/src/main/resources/db/changelog/2024/02/03-changelog.xml +++ b/src/main/resources/db/changelog/2024/02/03-changelog.xml @@ -3,7 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS"> + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" + objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS"> diff --git a/src/main/resources/db/changelog/2024/02/05-changelog.xml b/src/main/resources/db/changelog/2024/02/05-changelog.xml index 50e8b62f..8ea8c875 100644 --- a/src/main/resources/db/changelog/2024/02/05-changelog.xml +++ b/src/main/resources/db/changelog/2024/02/05-changelog.xml @@ -1,7 +1,7 @@ diff --git a/src/main/resources/db/changelog/2024/02/10-changelog.xml b/src/main/resources/db/changelog/2024/02/10-changelog.xml index a1fb4010..f89ff8a9 100644 --- a/src/main/resources/db/changelog/2024/02/10-changelog.xml +++ b/src/main/resources/db/changelog/2024/02/10-changelog.xml @@ -18,7 +18,8 @@ - + \ No newline at end of file diff --git a/src/main/resources/db/changelog/2024/02/11-changelog.xml b/src/main/resources/db/changelog/2024/02/11-changelog.xml index 85f4b76e..e05727c5 100644 --- a/src/main/resources/db/changelog/2024/02/11-changelog.xml +++ b/src/main/resources/db/changelog/2024/02/11-changelog.xml @@ -46,7 +46,8 @@ - + \ No newline at end of file diff --git a/src/main/resources/templates/admin/images.ftl b/src/main/resources/templates/admin/images.ftl index 8068d2e9..5bdcec8e 100644 --- a/src/main/resources/templates/admin/images.ftl +++ b/src/main/resources/templates/admin/images.ftl @@ -16,12 +16,14 @@
- +
- +
diff --git a/src/main/resources/templates/site/_layout.ftl b/src/main/resources/templates/site/_layout.ftl index fa8a294a..4f8447ad 100644 --- a/src/main/resources/templates/site/_layout.ftl +++ b/src/main/resources/templates/site/_layout.ftl @@ -18,7 +18,7 @@ <#if googleSiteVerification?? && googleSiteVerification?length != 0> - + <#-- Favicons --> diff --git a/src/main/resources/templates/site/_navigation.ftl b/src/main/resources/templates/site/_navigation.ftl index 9a222104..8f9862f8 100644 --- a/src/main/resources/templates/site/_navigation.ftl +++ b/src/main/resources/templates/site/_navigation.ftl @@ -59,7 +59,8 @@

Ce qui nous différencie

    -
  • Système automatisé : Fini les informations obsolètes ! Notre plateforme se charge de parcourir continuellement les plateformes +
  • Système automatisé : Fini les informations obsolètes ! Notre plateforme se charge de + parcourir continuellement les plateformes de streaming.
  • -
  • Open source : Contribuez à l'amélioration de Shikkanime en modifiant le code source selon vos besoins et envies, comblant ainsi +
  • Open source : Contribuez à l'amélioration de Shikkanime en modifiant le code source + selon vos besoins et envies, comblant ainsi un vide dans l'offre actuelle.
  • -
  • VF et VOSTFR : Ne manquez plus aucun épisode, que vous soyez fan de la version française ou originale.
  • -
  • Épisodes spéciaux et films : Retrouvez tous vos contenus préférés en un seul endroit.
  • +
  • VF et VOSTFR : Ne manquez plus aucun épisode, que vous soyez fan de la version + française ou originale. +
  • +
  • Épisodes spéciaux et films : Retrouvez tous vos contenus préférés en un seul endroit. +

Nos points forts

    @@ -41,7 +47,8 @@
  • Maintenance accrue pour garantir la fiabilité du système automatisé
  • Délais potentiels pour l'ajout d'animes et d'épisodes
  • Dépendance des licences de diffusion des plateformes de streaming
  • -
  • Absence de système communautaire entraînant des délais plus longs pour l'ajout d'animés et d'épisodes
  • +
  • Absence de système communautaire entraînant des délais plus longs pour l'ajout d'animés et d'épisodes +

Notre objectif

Shikkanime n'a pas pour but de remplacer les plateformes existantes, mais plutôt de :

diff --git a/src/test/kotlin/fr/shikkanime/OldEpisodeScraper.kt b/src/test/kotlin/fr/shikkanime/OldEpisodeScraper.kt index cdee0eb3..bf138807 100644 --- a/src/test/kotlin/fr/shikkanime/OldEpisodeScraper.kt +++ b/src/test/kotlin/fr/shikkanime/OldEpisodeScraper.kt @@ -112,8 +112,10 @@ fun main() { series.forEach { val postersTall = it.getAsJsonObject("images").getAsJsonArray("poster_tall")[0].asJsonArray val postersWide = it.getAsJsonObject("images").getAsJsonArray("poster_wide")[0].asJsonArray - val image = postersTall?.maxByOrNull { poster -> poster.asJsonObject.getAsInt("width")!! }?.asJsonObject?.getAsString("source")!! - val banner = postersWide?.maxByOrNull { poster -> poster.asJsonObject.getAsInt("width")!! }?.asJsonObject?.getAsString("source")!! + val image = + postersTall?.maxByOrNull { poster -> poster.asJsonObject.getAsInt("width")!! }?.asJsonObject?.getAsString("source")!! + val banner = + postersWide?.maxByOrNull { poster -> poster.asJsonObject.getAsInt("width")!! }?.asJsonObject?.getAsString("source")!! val description = it.getAsString("description") crunchyrollPlatform.animeInfoCache.set( @@ -124,21 +126,41 @@ fun main() { val episodeIds = ids.parallelStream().map { seriesId -> runBlocking { CrunchyrollWrapper.getSeasons(CountryCode.FR.locale, accessToken, cms, seriesId) } - .filter { jsonObject -> jsonObject.getAsJsonArray("subtitle_locales").map { it.asString }.contains(CountryCode.FR.locale) } + .filter { jsonObject -> + jsonObject.getAsJsonArray("subtitle_locales").map { it.asString }.contains(CountryCode.FR.locale) + } .map { jsonObject -> jsonObject.getAsString("id")!! } - .flatMap { id -> runBlocking { CrunchyrollWrapper.getEpisodes(CountryCode.FR.locale, accessToken, cms, id) } } + .flatMap { id -> + runBlocking { + CrunchyrollWrapper.getEpisodes( + CountryCode.FR.locale, + accessToken, + cms, + id + ) + } + } .map { jsonObject -> jsonObject.getAsString("id")!! } }.toList().flatten().toSet() episodeIds.chunked(25).parallelStream().forEach { episodeIdsChunked -> - val `object` = runBlocking { CrunchyrollWrapper.getObject(CountryCode.FR.locale, accessToken, cms, *episodeIdsChunked.toTypedArray()) } + val `object` = runBlocking { + CrunchyrollWrapper.getObject( + CountryCode.FR.locale, + accessToken, + cms, + *episodeIdsChunked.toTypedArray() + ) + } `object`.forEach { episodeJson -> try { - episodes.add(crunchyrollPlatform.convertJsonEpisode( - CountryCode.FR, - episodeJson, - )) + episodes.add( + crunchyrollPlatform.convertJsonEpisode( + CountryCode.FR, + episodeJson, + ) + ) } catch (e: Exception) { println("Error while converting episode (Episode ID: ${episodeJson.getAsString("id")}): ${e.message}") e.printStackTrace() @@ -151,7 +173,8 @@ fun main() { episodes.removeIf { it.releaseDateTime.toLocalDate() !in dates } episodes.sortedBy { it.releaseDateTime }.forEach { episode -> - episode.anime?.releaseDateTime = episodes.filter { it.anime?.name == episode.anime?.name }.minOf { it.anime!!.releaseDateTime } + episode.anime?.releaseDateTime = + episodes.filter { it.anime?.name == episode.anime?.name }.minOf { it.anime!!.releaseDateTime } episodeService.save(episode) } diff --git a/src/test/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatformTest.kt b/src/test/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatformTest.kt index 3976deca..bc04ade9 100644 --- a/src/test/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatformTest.kt +++ b/src/test/kotlin/fr/shikkanime/platforms/AnimationDigitalNetworkPlatformTest.kt @@ -109,7 +109,10 @@ class AnimationDigitalNetworkPlatformTest { assertEquals(3, episodes.size) assertEquals("Demon Slave", episodes[0].anime?.name) assertNotNull(episodes[0].description) - assertEquals("My Instant Death Ability Is So Overpowered, No One in This Other World Stands a Chance Against Me!", episodes[1].anime?.name) + assertEquals( + "My Instant Death Ability Is So Overpowered, No One in This Other World Stands a Chance Against Me!", + episodes[1].anime?.name + ) assertNotNull(episodes[1].description) assertEquals("Urusei Yatsura", episodes[2].anime?.name) assertNotNull(episodes[2].description) diff --git a/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt b/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt index 8c7ed1ca..2b728211 100644 --- a/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt +++ b/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt @@ -145,7 +145,8 @@ class TwitterSocialNetworkTest { "\n" + "Bon visionnage. \uD83C\uDF7F\n" + "\n" + - "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/shangri-la-frontier", twitterSocialNetwork.getMessage(episodeDto) + "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/shangri-la-frontier", + twitterSocialNetwork.getMessage(episodeDto) ) } @@ -197,7 +198,8 @@ class TwitterSocialNetworkTest { "\n" + "Bon visionnage. \uD83C\uDF7F\n" + "\n" + - "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/looking-up-to-magical-girls", twitterSocialNetwork.getMessage(episodeDto) + "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/looking-up-to-magical-girls", + twitterSocialNetwork.getMessage(episodeDto) ) } From 3a8a065bd26af80dcd6e50963cb174db906599ef Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Fri, 1 Mar 2024 10:12:41 +0100 Subject: [PATCH 2/2] Social networks configs --- .../entities/enums/ConfigPropertyKey.kt | 3 + .../socialnetworks/AbstractSocialNetwork.kt | 35 +++ .../socialnetworks/BskySocialNetwork.kt | 22 +- .../socialnetworks/ThreadsSocialNetwork.kt | 34 +-- .../socialnetworks/TwitterSocialNetwork.kt | 35 +-- .../db/changelog/2024/03/01-changelog.xml | 53 ++++ .../db/changelog/db.changelog-master.xml | 2 + .../TwitterSocialNetworkTest.kt | 274 ------------------ 8 files changed, 103 insertions(+), 355 deletions(-) create mode 100644 src/main/resources/db/changelog/2024/03/01-changelog.xml delete mode 100644 src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt diff --git a/src/main/kotlin/fr/shikkanime/entities/enums/ConfigPropertyKey.kt b/src/main/kotlin/fr/shikkanime/entities/enums/ConfigPropertyKey.kt index 9b4440fe..c5dac76b 100644 --- a/src/main/kotlin/fr/shikkanime/entities/enums/ConfigPropertyKey.kt +++ b/src/main/kotlin/fr/shikkanime/entities/enums/ConfigPropertyKey.kt @@ -20,4 +20,7 @@ enum class ConfigPropertyKey(val key: String) { THREADS_USERNAME("threads_username"), THREADS_PASSWORD("threads_password"), THREADS_MESSAGE("threads_message"), + BSKY_MESSAGE("bsky_message"), + BSKY_SESSION_TIMEOUT("bsky_session_timeout"), + THREADS_SESSION_TIMEOUT("threads_session_timeout"), } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/AbstractSocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/AbstractSocialNetwork.kt index 053324b3..ace6cd86 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/AbstractSocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/AbstractSocialNetwork.kt @@ -2,7 +2,12 @@ package fr.shikkanime.socialnetworks import com.google.inject.Inject import fr.shikkanime.dtos.EpisodeDto +import fr.shikkanime.entities.enums.EpisodeType +import fr.shikkanime.entities.enums.LangType +import fr.shikkanime.entities.enums.Platform import fr.shikkanime.services.caches.ConfigCacheService +import fr.shikkanime.utils.Constant +import fr.shikkanime.utils.StringUtils abstract class AbstractSocialNetwork { @Inject @@ -12,5 +17,35 @@ abstract class AbstractSocialNetwork { abstract fun logout() abstract fun sendMessage(message: String) + + open fun platformAccount(platform: Platform): String { + return platform.platformName + } + + private fun information(episodeDto: EpisodeDto): String { + return when (episodeDto.episodeType) { + EpisodeType.SPECIAL -> "L'épisode spécial" + EpisodeType.FILM -> "Le film" + else -> "L'épisode ${episodeDto.number}" + } + } + + fun getEpisodeMessage(episodeDto: EpisodeDto, baseMessage: String): String { + val uncensored = if (episodeDto.uncensored) " non censuré" else "" + val isVoice = if (episodeDto.langType == LangType.VOICE) " en VF " else " " + + var configMessage = baseMessage + configMessage = configMessage.replace("{SHIKKANIME_URL}", "${Constant.BASE_URL}/animes/${episodeDto.anime.slug}") + configMessage = configMessage.replace("{URL}", episodeDto.url) + configMessage = configMessage.replace("{PLATFORM_ACCOUNT}", platformAccount(episodeDto.platform)) + configMessage = configMessage.replace("{ANIME_HASHTAG}", "#${StringUtils.getHashtag(episodeDto.anime.shortName)}") + configMessage = configMessage.replace("{ANIME_TITLE}", episodeDto.anime.shortName) + configMessage = configMessage.replace("{EPISODE_INFORMATION}", "${information(episodeDto)}${uncensored}") + configMessage = configMessage.replace("{VOICE}", isVoice) + configMessage = configMessage.replace("\\n", "\n") + configMessage = configMessage.trim() + return configMessage + } + abstract fun sendEpisodeRelease(episodeDto: EpisodeDto, mediaImage: ByteArray) } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt index 6a904a15..ac5fc5c7 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/BskySocialNetwork.kt @@ -2,8 +2,6 @@ package fr.shikkanime.socialnetworks import fr.shikkanime.dtos.EpisodeDto import fr.shikkanime.entities.enums.ConfigPropertyKey -import fr.shikkanime.entities.enums.EpisodeType -import fr.shikkanime.entities.enums.LangType import fr.shikkanime.utils.Constant import fr.shikkanime.utils.FileManager import fr.shikkanime.utils.LoggerFactory @@ -50,7 +48,7 @@ class BskySocialNetwork : AbstractSocialNetwork() { private fun checkSession() { if (!isInitialized) return - if (initializedAt!!.plusMinutes(10).isBefore(ZonedDateTime.now())) { + if (initializedAt!!.plusMinutes(configCacheService.getValueAsInt(ConfigPropertyKey.BSKY_SESSION_TIMEOUT, 10).toLong()).isBefore(ZonedDateTime.now())) { logout() login() } @@ -62,27 +60,13 @@ class BskySocialNetwork : AbstractSocialNetwork() { runBlocking { BskyWrapper.createRecord(accessJwt!!, did!!, message) } } - private fun information(episodeDto: EpisodeDto): String { - return when (episodeDto.episodeType) { - EpisodeType.SPECIAL -> "L'épisode spécial" - EpisodeType.FILM -> "Le film" - else -> "L'épisode ${episodeDto.number}" - } - } - override fun sendEpisodeRelease(episodeDto: EpisodeDto, mediaImage: ByteArray) { checkSession() if (!isInitialized) return - val url = "${Constant.BASE_URL}/animes/${episodeDto.anime.slug}" - val uncensored = if (episodeDto.uncensored) " non censuré" else "" - val isVoice = if (episodeDto.langType == LangType.VOICE) " en VF " else " " - val message = - "\uD83D\uDEA8 ${information(episodeDto)}${uncensored} de ${episodeDto.anime.shortName} est maintenant disponible${isVoice}sur ${episodeDto.platform.platformName}\n\nBon visionnage. \uD83C\uDF7F" - + val message = getEpisodeMessage(episodeDto, configCacheService.getValueAsString(ConfigPropertyKey.BSKY_MESSAGE) ?: "") val webpByteArray = FileManager.encodeToWebP(mediaImage) - val imageJson = - runBlocking { BskyWrapper.uploadBlob(accessJwt!!, ContentType.parse("image/webp"), webpByteArray) } + val imageJson = runBlocking { BskyWrapper.uploadBlob(accessJwt!!, ContentType.parse("image/webp"), webpByteArray) } runBlocking { BskyWrapper.createRecord(accessJwt!!, did!!, message, listOf(BskyWrapper.Image(imageJson, url))) } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/ThreadsSocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/ThreadsSocialNetwork.kt index b5f81aba..795ccbee 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/ThreadsSocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/ThreadsSocialNetwork.kt @@ -2,8 +2,6 @@ package fr.shikkanime.socialnetworks import fr.shikkanime.dtos.EpisodeDto import fr.shikkanime.entities.enums.ConfigPropertyKey -import fr.shikkanime.entities.enums.EpisodeType -import fr.shikkanime.entities.enums.LangType import fr.shikkanime.entities.enums.Platform import fr.shikkanime.utils.LoggerFactory import fr.shikkanime.wrappers.ThreadsWrapper @@ -54,7 +52,9 @@ class ThreadsSocialNetwork : AbstractSocialNetwork() { private fun checkSession() { if (!isInitialized) return - if (initializedAt!!.plusMinutes(10).isBefore(ZonedDateTime.now())) { + if (initializedAt!!.plusMinutes(configCacheService.getValueAsInt(ConfigPropertyKey.THREADS_SESSION_TIMEOUT, 10).toLong()) + .isBefore(ZonedDateTime.now()) + ) { logout() login() } @@ -66,7 +66,7 @@ class ThreadsSocialNetwork : AbstractSocialNetwork() { runBlocking { ThreadsWrapper.publish(username!!, deviceId!!, userId!!, token!!, message) } } - private fun platformAccount(platform: Platform): String { + override fun platformAccount(platform: Platform): String { return when (platform) { Platform.CRUN -> "@crunchyroll_fr" Platform.NETF -> "@netflixfr" @@ -75,34 +75,10 @@ class ThreadsSocialNetwork : AbstractSocialNetwork() { } } - private fun information(episodeDto: EpisodeDto): String { - return when (episodeDto.episodeType) { - EpisodeType.SPECIAL -> "L'épisode spécial" - EpisodeType.FILM -> "Le film" - else -> "L'épisode ${episodeDto.number}" - } - } - - fun getMessage(episodeDto: EpisodeDto): String { - val uncensored = if (episodeDto.uncensored) " non censuré" else "" - val isVoice = if (episodeDto.langType == LangType.VOICE) " en VF " else " " - - var configMessage = configCacheService.getValueAsString(ConfigPropertyKey.THREADS_MESSAGE) ?: "" - configMessage = configMessage.replace("{URL}", episodeDto.url) - configMessage = configMessage.replace("{PLATFORM_ACCOUNT}", platformAccount(episodeDto.platform)) - configMessage = configMessage.replace("{ANIME_TITLE}", episodeDto.anime.shortName) - configMessage = configMessage.replace("{EPISODE_INFORMATION}", "${information(episodeDto)}${uncensored}") - configMessage = configMessage.replace("{VOICE}", isVoice) - configMessage = configMessage.replace("\\n", "\n") - configMessage = configMessage.trim() - return configMessage - } - override fun sendEpisodeRelease(episodeDto: EpisodeDto, mediaImage: ByteArray) { checkSession() if (!isInitialized) return - - val message = getMessage(episodeDto) + val message = getEpisodeMessage(episodeDto, configCacheService.getValueAsString(ConfigPropertyKey.THREADS_MESSAGE) ?: "") runBlocking { ThreadsWrapper.publish(username!!, deviceId!!, userId!!, token!!, message, mediaImage) } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt b/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt index 3e0e4eab..acfd28f4 100644 --- a/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt +++ b/src/main/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetwork.kt @@ -2,12 +2,8 @@ package fr.shikkanime.socialnetworks import fr.shikkanime.dtos.EpisodeDto import fr.shikkanime.entities.enums.ConfigPropertyKey -import fr.shikkanime.entities.enums.EpisodeType -import fr.shikkanime.entities.enums.LangType import fr.shikkanime.entities.enums.Platform -import fr.shikkanime.utils.Constant import fr.shikkanime.utils.LoggerFactory -import fr.shikkanime.utils.StringUtils import twitter4j.Twitter import twitter4j.TwitterFactory import twitter4j.conf.ConfigurationBuilder @@ -72,7 +68,7 @@ class TwitterSocialNetwork : AbstractSocialNetwork() { twitter!!.v2.createTweet(text = message) } - private fun platformAccount(platform: Platform): String { + override fun platformAccount(platform: Platform): String { return when (platform) { Platform.ANIM -> "@ADNanime" Platform.CRUN -> "@Crunchyroll_fr" @@ -82,38 +78,11 @@ class TwitterSocialNetwork : AbstractSocialNetwork() { } } - private fun information(episodeDto: EpisodeDto): String { - return when (episodeDto.episodeType) { - EpisodeType.SPECIAL -> "L'épisode spécial" - EpisodeType.FILM -> "Le film" - else -> "L'épisode ${episodeDto.number}" - } - } - - fun getMessage(episodeDto: EpisodeDto): String { - val uncensored = if (episodeDto.uncensored) " non censuré" else "" - val isVoice = if (episodeDto.langType == LangType.VOICE) " en VF " else " " - - var configMessage = configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_MESSAGE) ?: "" - configMessage = - configMessage.replace("{SHIKKANIME_URL}", "${Constant.BASE_URL}/animes/${episodeDto.anime.slug}") - configMessage = configMessage.replace("{URL}", episodeDto.url) - configMessage = configMessage.replace("{PLATFORM_ACCOUNT}", platformAccount(episodeDto.platform)) - configMessage = - configMessage.replace("{ANIME_HASHTAG}", "#${StringUtils.getHashtag(episodeDto.anime.shortName)}") - configMessage = configMessage.replace("{ANIME_TITLE}", episodeDto.anime.shortName) - configMessage = configMessage.replace("{EPISODE_INFORMATION}", "${information(episodeDto)}${uncensored}") - configMessage = configMessage.replace("{VOICE}", isVoice) - configMessage = configMessage.replace("\\n", "\n") - configMessage = configMessage.trim() - return configMessage - } - override fun sendEpisodeRelease(episodeDto: EpisodeDto, mediaImage: ByteArray) { login() if (!isInitialized) return if (twitter == null) return - val message = getMessage(episodeDto) + val message = getEpisodeMessage(episodeDto, configCacheService.getValueAsString(ConfigPropertyKey.TWITTER_MESSAGE) ?: "") val uploadMedia = twitter!!.tweets().uploadMedia( UUID.randomUUID().toString(), diff --git a/src/main/resources/db/changelog/2024/03/01-changelog.xml b/src/main/resources/db/changelog/2024/03/01-changelog.xml new file mode 100644 index 00000000..5fc50b75 --- /dev/null +++ b/src/main/resources/db/changelog/2024/03/01-changelog.xml @@ -0,0 +1,53 @@ + + + + + + + + SELECT COUNT(*) + FROM config + WHERE property_key = 'bsky_message' + + + + + + + + + + + + SELECT COUNT(*) + FROM config + WHERE property_key = 'bsky_session_timeout' + + + + + + + + + + + + SELECT COUNT(*) + FROM config + WHERE property_key = 'threads_session_timeout' + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.xml b/src/main/resources/db/changelog/db.changelog-master.xml index 1685255e..559e95db 100644 --- a/src/main/resources/db/changelog/db.changelog-master.xml +++ b/src/main/resources/db/changelog/db.changelog-master.xml @@ -29,4 +29,6 @@ + + \ No newline at end of file diff --git a/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt b/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt deleted file mode 100644 index 2b728211..00000000 --- a/src/test/kotlin/fr/shikkanime/socialnetworks/TwitterSocialNetworkTest.kt +++ /dev/null @@ -1,274 +0,0 @@ -package fr.shikkanime.socialnetworks - -import com.google.inject.Inject -import fr.shikkanime.dtos.AnimeDto -import fr.shikkanime.dtos.EpisodeDto -import fr.shikkanime.dtos.SimulcastDto -import fr.shikkanime.dtos.enums.Status -import fr.shikkanime.entities.Config -import fr.shikkanime.entities.enums.* -import fr.shikkanime.repositories.ConfigRepository -import fr.shikkanime.utils.Constant -import fr.shikkanime.utils.MapCache -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.util.* - -class TwitterSocialNetworkTest { - @Inject - private lateinit var twitterSocialNetwork: TwitterSocialNetwork - - @Inject - private lateinit var configRepository: ConfigRepository - - @BeforeEach - fun setUp() { - Constant.injector.injectMembers(this) - - configRepository.save( - Config( - propertyKey = ConfigPropertyKey.TWITTER_MESSAGE.key, - propertyValue = "🚨 {EPISODE_INFORMATION} de {ANIME_HASHTAG} est maintenant disponible{VOICE}sur {PLATFORM_ACCOUNT}\n\nBon visionnage. 🍿\n\n🔶 Lien de l'épisode : {SHIKKANIME_URL}", - - ) - ) - - MapCache.invalidate(Config::class.java) - } - - @AfterEach - fun tearDown() { - configRepository.findByName(ConfigPropertyKey.TWITTER_MESSAGE.key)?.let { configRepository.delete(it) } - MapCache.invalidate(Config::class.java) - } - - @Test - fun getMessage1() { - val episodeDto = EpisodeDto( - uuid = UUID.fromString("c2d2759a-8475-4ba1-9554-3c721c8b281f"), - platform = Platform.CRUN, - anime = AnimeDto( - uuid = UUID.fromString("c2d2759a-8475-4ba1-9554-3c721c8b281f"), - countryCode = CountryCode.FR, - name = "The Foolish Angel Dances with the Devil", - shortName = "The Foolish Angel Dances with the Devil", - releaseDateTime = "2024-01-08T18:00:00Z", - image = "https://www.crunchyroll.com/imgsrv/display/thumbnail/480x720/catalog/crunchyroll/44b38d6f3cc6e0a97006bdac5b139ea5.jpe", - banner = "https://www.crunchyroll.com/imgsrv/display/thumbnail/1920x1080/catalog/crunchyroll/18187c3c5bb45febea51842b9748d2b2.jpe", - description = "Déterminé à protéger son monde démoniaque des anges célestes, le démon Masatora Akutsu se rend sur Terre à la recherche d'une humaine afin d’augmenter ses effectifs. Se faisant passer pour un lycéen durant sa mission de recrutement, il est captivé par la charmante Lily Amane. Elle est pourtant l’un de ses ennemis jurés (un ange) et bien décidée à lui faire oublier ses réflexes démoniaques !", - simulcasts = listOf( - SimulcastDto( - uuid = UUID.fromString("d1993c0c-4f65-474b-9c04-c577435aa7d5"), - season = "WINTER", - year = 2024, - slug = "winter-2024", - label = "Hiver 2024", - ) - ), - status = Status.VALID, - slug = "the-foolish-angel-dances-with-the-devil", - lastReleaseDateTime = "2024-02-26T18:00:00Z", - ), - episodeType = EpisodeType.EPISODE, - langType = LangType.SUBTITLES, - hash = "FR-CRUN-918505-SUBTITLES", - releaseDateTime = "2024-02-26T18:00:00Z", - season = 1, - number = 8, - title = "Deux idiots en désaccord", - url = "https://www.crunchyroll.com/media-918505", - image = "https://www.crunchyroll.com/imgsrv/display/thumbnail/1920x1080/catalog/crunchyroll/bc972b9a51f735d67b763edf2f690708.jpe", - duration = 1420, - description = "Comme ils ont été vus sortant ensemble du même immeuble le matin, Akutsu et Amane sont instantanément devenus l'objet de toutes les rumeurs. Il va maintenant falloir dissiper le malentendu.", - uncensored = false, - lastUpdateDateTime = "2024-02-26T18:00:00Z", - status = Status.VALID, - ) - - assertEquals( - "\uD83D\uDEA8 L'épisode 8 de #TheFoolishAngelDancesWithTheDevil est maintenant disponible sur @Crunchyroll_fr\n" + - "\n" + - "Bon visionnage. \uD83C\uDF7F\n" + - "\n" + - "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/the-foolish-angel-dances-with-the-devil", - twitterSocialNetwork.getMessage(episodeDto) - ) - } - - @Test - fun getMessage2() { - val episodeDto = EpisodeDto( - uuid = UUID.fromString("e858f537-9a15-4dd6-a1a5-d2d26c760413"), - platform = Platform.CRUN, - anime = AnimeDto( - uuid = UUID.fromString("e32b99b1-a7a8-4785-9586-f079355f9502"), - countryCode = CountryCode.FR, - name = "Shangri-La Frontier", - shortName = "Shangri-La Frontier", - releaseDateTime = "2024-01-07T10:30:00Z", - image = "https://www.crunchyroll.com/imgsrv/display/thumbnail/480x720/catalog/crunchyroll/a2f948157077e3d65471329d9dd43be1.jpe", - banner = "https://www.crunchyroll.com/imgsrv/display/thumbnail/1920x1080/catalog/crunchyroll/34cf65c200da726462d0c0ac7e0e55af.jpe", - description = "Dans un futur proche, les jeux s’affichant sur des écrans sont passés de mode. La technologie VR en immersion domine ce marché, mais parmi la multitude de jeux, un grand nombre sont bien souvent nuls et sans intérêt. Certains ont décidé de s’attaquer à ces jeux, comme Rakuro Hizutome, alias Sunraku, qui se plaît à enchaîner les pires d’entre-eux. Mais cette fois, il a décidé de se lancer dans Shangri-La Frontier, un jeu grand public aux quelques trente millions de membres inscrits ! Des amis en ligne, un univers immense, des rencontres avec des rivaux... Rakuro est loin d’imaginer que sa vie, voire son destin et celui de nombreuses personnes, est sur le point de changer lorsqu’il se lance dans cette aventure…", - simulcasts = listOf( - SimulcastDto( - uuid = UUID.fromString("d1993c0c-4f65-474b-9c04-c577435aa7d5"), - season = "WINTER", - year = 2024, - slug = "winter-2024", - label = "Hiver 2024", - ) - ), - status = Status.VALID, - slug = "shangri-la-frontier", - lastReleaseDateTime = "2024-02-25T20:00:00Z", - ), - episodeType = EpisodeType.EPISODE, - langType = LangType.VOICE, - hash = "FR-CRUN-922508-VOICE", - releaseDateTime = "2024-02-25T20:00:00Z", - season = 1, - number = 16, - title = "Émotions pour un bref instant (partie 2)", - url = "https://www.crunchyroll.com/media-922508", - image = "https://www.crunchyroll.com/imgsrv/display/thumbnail/1920x1080/catalog/crunchyroll/8c314cd552392412f3a6ae5c54a0ab67.jpe", - duration = 1578, - description = "Sunraku, Katzo et Pencilgon ont entamé leur combat contre Wethermon. Leur objectif : survivre dix minutes afin d’atteindre la deuxième phase du boss. Mais celui-ci s’avère redoutable et force l’équipe à utiliser tous ses objets de soin.", - uncensored = false, - lastUpdateDateTime = "2024-02-25T20:00:00Z", - status = Status.VALID, - ) - - assertEquals( - "\uD83D\uDEA8 L'épisode 16 de #ShangriLaFrontier est maintenant disponible en VF sur @Crunchyroll_fr\n" + - "\n" + - "Bon visionnage. \uD83C\uDF7F\n" + - "\n" + - "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/shangri-la-frontier", - twitterSocialNetwork.getMessage(episodeDto) - ) - } - - @Test - fun getMessage3() { - val episodeDto = EpisodeDto( - uuid = UUID.fromString("ca116f60-51df-4590-9670-de0adb118c27"), - platform = Platform.ANIM, - anime = AnimeDto( - uuid = UUID.fromString("fe2eb550-2821-4a82-ae1c-e3f11deb61f6"), - countryCode = CountryCode.FR, - name = "Looking up to Magical Girls", - shortName = "Looking up to Magical Girls", - releaseDateTime = "2024-01-03T15:30:00Z", - image = "https://image.animationdigitalnetwork.fr/license/mahoushoujoniakogarete/tv/web/affiche_350x500.jpg", - banner = "https://image.animationdigitalnetwork.fr/license/mahoushoujoniakogarete/tv/web/license_640x360.jpg", - description = "Fan de magical girls depuis sa plus tendre enfance, Utena Hiiragi rêve de combattre auprès des Tres Magia, le trio protégeant sa ville. Alors, quand un adorable démon lui propose de la métamorphoser, l’adolescente accepte immédiatement. Cependant, son enthousiasme est de courte durée lorsqu’elle prend conscience qu’elle est désormais enrôlée au sein d’Enormita, une organisation maléfique contre laquelle luttent ses justicières favorites ! Si Utena est dans un premier temps réticente à s’engager dans ce conflit, elle doit faire face à une troublante révélation : torturer ses idoles lui procure un immense plaisir.", - simulcasts = listOf( - SimulcastDto( - uuid = UUID.fromString("d1993c0c-4f65-474b-9c04-c577435aa7d5"), - season = "WINTER", - year = 2024, - slug = "winter-2024", - label = "Hiver 2024", - ) - ), - status = Status.VALID, - slug = "looking-up-to-magical-girls", - lastReleaseDateTime = "2024-02-21T15:30:00Z", - ), - episodeType = EpisodeType.EPISODE, - langType = LangType.SUBTITLES, - hash = "FR-ANIM-24154-SUBTITLES", - releaseDateTime = "2024-02-21T15:30:00Z", - season = 1, - number = 8, - title = "L'Apparition du groupe Lord", - url = "https://animationdigitalnetwork.fr/video/looking-up-to-magical-girls/24154-episode-8-nc-auftritt-der-lord-truppe", - image = "https://image.animationdigitalnetwork.fr/license/mahoushoujoniakogaretenc/tv/web/eps8_640x360.jpg", - duration = 1419, - description = "Utena, Kiwi et Korisu se rendent à Nacht base et font la connaissance de Lord Enorme, Loco Musica, Sister Gigant et Leberblume, les membres historiques d’Enormita. Cette rencontre va prendre une tournure des plus inattendues…", - uncensored = true, - lastUpdateDateTime = "2024-02-21T15:30:00Z", - status = Status.VALID, - ) - - assertEquals( - "\uD83D\uDEA8 L'épisode 8 non censuré de #LookingUpToMagicalGirls est maintenant disponible sur @ADNanime\n" + - "\n" + - "Bon visionnage. \uD83C\uDF7F\n" + - "\n" + - "\uD83D\uDD36 Lien de l'épisode : https://www.shikkanime.fr/animes/looking-up-to-magical-girls", - twitterSocialNetwork.getMessage(episodeDto) - ) - } - - @Test - fun getMessage4() { - configRepository.findByName(ConfigPropertyKey.TWITTER_MESSAGE.key)?.let { configRepository.delete(it) } - configRepository.save( - Config( - propertyKey = ConfigPropertyKey.TWITTER_MESSAGE.key, - propertyValue = "Nouveau ! {EPISODE_INFORMATION} de {ANIME_HASHTAG} est dispo{VOICE}sur {PLATFORM_ACCOUNT} !\n" + - "\n" + - "Ne manquez pas la suite des aventures de {ANIME_TITLE} !\n" + - "\n" + - "➡\uFE0F Lien : {URL}\n" + - "\n" + - "#anime" - ) - ) - MapCache.invalidate(Config::class.java) - - val episodeDto = EpisodeDto( - uuid = UUID.fromString("ca116f60-51df-4590-9670-de0adb118c27"), - platform = Platform.ANIM, - anime = AnimeDto( - uuid = UUID.fromString("fe2eb550-2821-4a82-ae1c-e3f11deb61f6"), - countryCode = CountryCode.FR, - name = "Looking up to Magical Girls", - shortName = "Looking up to Magical Girls", - releaseDateTime = "2024-01-03T15:30:00Z", - image = "https://image.animationdigitalnetwork.fr/license/mahoushoujoniakogarete/tv/web/affiche_350x500.jpg", - banner = "https://image.animationdigitalnetwork.fr/license/mahoushoujoniakogarete/tv/web/license_640x360.jpg", - description = "Fan de magical girls depuis sa plus tendre enfance, Utena Hiiragi rêve de combattre auprès des Tres Magia, le trio protégeant sa ville. Alors, quand un adorable démon lui propose de la métamorphoser, l’adolescente accepte immédiatement. Cependant, son enthousiasme est de courte durée lorsqu’elle prend conscience qu’elle est désormais enrôlée au sein d’Enormita, une organisation maléfique contre laquelle luttent ses justicières favorites ! Si Utena est dans un premier temps réticente à s’engager dans ce conflit, elle doit faire face à une troublante révélation : torturer ses idoles lui procure un immense plaisir.", - simulcasts = listOf( - SimulcastDto( - uuid = UUID.fromString("d1993c0c-4f65-474b-9c04-c577435aa7d5"), - season = "WINTER", - year = 2024, - slug = "winter-2024", - label = "Hiver 2024", - ) - ), - status = Status.VALID, - slug = "looking-up-to-magical-girls", - lastReleaseDateTime = "2024-02-21T15:30:00Z", - ), - episodeType = EpisodeType.EPISODE, - langType = LangType.SUBTITLES, - hash = "FR-ANIM-24154-SUBTITLES", - releaseDateTime = "2024-02-21T15:30:00Z", - season = 1, - number = 8, - title = "L'Apparition du groupe Lord", - url = "https://animationdigitalnetwork.fr/video/looking-up-to-magical-girls/24154-episode-8-nc-auftritt-der-lord-truppe", - image = "https://image.animationdigitalnetwork.fr/license/mahoushoujoniakogaretenc/tv/web/eps8_640x360.jpg", - duration = 1419, - description = "Utena, Kiwi et Korisu se rendent à Nacht base et font la connaissance de Lord Enorme, Loco Musica, Sister Gigant et Leberblume, les membres historiques d’Enormita. Cette rencontre va prendre une tournure des plus inattendues…", - uncensored = true, - lastUpdateDateTime = "2024-02-21T15:30:00Z", - status = Status.VALID, - ) - - assertEquals( - "Nouveau ! L'épisode 8 non censuré de #LookingUpToMagicalGirls est dispo sur @ADNanime !\n" + - "\n" + - "Ne manquez pas la suite des aventures de Looking up to Magical Girls !\n" + - "\n" + - "➡\uFE0F Lien : https://animationdigitalnetwork.fr/video/looking-up-to-magical-girls/24154-episode-8-nc-auftritt-der-lord-truppe\n" + - "\n" + - "#anime", twitterSocialNetwork.getMessage(episodeDto) - ) - } -} \ No newline at end of file