diff --git a/README.md b/README.md index f6d5b04..bc6d123 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ [badge_release_img]: https://img.shields.io/github/v/release/Eyre-S/Coeur-Morny-Cono?display_name=release&label=latest&color=#00fa9a [badge_release_target]: https://mvn.sukazyo.cc/#/releases/cc/sukazyo/morny-coeur [//]: # (on branch master) -[badge_tests_img]: https://img.shields.io/github/actions/workflow/status/Eyre-S/Coeur-Morny-Cono/test.yml?branch=master&label=Tests +[badge_tests_img]: https://img.shields.io/github/actions/workflow/status/Eyre-S/Coeur-Morny-Cono/work_test.yml?branch=master&label=Tests [//]: # (on branch 2.0.0) [//]: # ([badge_tests_img]: https://img.shields.io/github/actions/workflow/status/Eyre-S/Coeur-Morny-Cono/test.yml?branch=2.0.0&label=Tests%20on%202.0.0) [badge_snapshot_img]: https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fmvn.sukazyo.cc%2Fsnapshots%2Fcc%2Fsukazyo%2Fmorny-coeur%2Fmaven-metadata.xml&label=snapshots&color=%231e90ff diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXApi.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXApi.scala index 11a4af8..37fac56 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXApi.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXApi.scala @@ -49,6 +49,8 @@ object FXApi { implicit val decoder_FXPoolChoice: Decoder[FXPoolChoice] = deriveDecoder implicit val decoder_FXPool: Decoder[FXPool] = deriveDecoder implicit val decoder_FXTranslate: Decoder[FXTranslate] = deriveDecoder + implicit val decoder_FXFacet: Decoder[FXFacet] = deriveDecoder + implicit val decoder_FXRawText: Decoder[FXRawText] = deriveDecoder implicit val decoder_FXTweet_media: Decoder[FXTweet.mediaType] = deriveDecoder implicit val decoder_FXTweet: Decoder[FXTweet] = deriveDecoder implicit val decoder_FXApi: Decoder[FXApi] = deriveDecoder diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXFacet.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXFacet.scala new file mode 100644 index 0000000..137df55 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXFacet.scala @@ -0,0 +1,47 @@ +package cc.sukazyo.cono.morny.extra.twitter + +/** The facets (rich text indicators) of the text. + * + * Every facet is a part of the original text, contains some extra information about the + * text segment. + * + * @param `type` The type of the facet. + * This may be one of the following (not fully listed): + * + * - `hashtag` - A hashtag (like #topic) + * - `media` - A media, like a photo or a video. + * + * @param indices The indices of the text segment in the original text. + * + * This should always be a list of two integers, the first one is the start + * index, and the second one is the end index of the text + * segment in the raw_text's text. + * + * @param original The original text of the facet. + * Should be the same with texts that the indices point to. + * + * The content type is different for each type: + * - for `media`, this is a media shortcode (like `t.co/abcde`). + * - for `hashtag`, this is the hashtag name without hash char (like `topic`, + * but not `#topic`). + * + * @param replacement Alternative method to show this facet. + * + * For now, only `media` have this field, and it is a URL that points to + * the media in the tweet (`https://x.com/user/status/123/photo/1`). + * + * @param display The display text of the facet. + * + * For now, only `media` have this field, and it seems like the permanent URL + * of the media (`pic.x.com/abcde`). + * + * @param id A very large integer ID of the facet. Seems only `media` have this field. + */ +case class FXFacet ( + `type`: String, + indices: List[Int], + original: Option[String], + replacement: Option[String], + display: Option[String], + id: Option[String], +) diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXPhoto.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXPhoto.scala index d759110..d48e725 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXPhoto.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXPhoto.scala @@ -6,11 +6,15 @@ package cc.sukazyo.cono.morny.extra.twitter * @param url URL of the photo * @param width Width of the photo, in pixels * @param height Height of the photo, in pixels + * @param altText Alternative text of the photo, or also known as photo description. + * + * It seems that this is not provided by Fix-Twitter API after 2025-04. */ case class FXPhoto ( `type`: "photo", url: String, width: Int, height: Int, - altText: String // todo + // todo: Find a tweet to test if this can still work + altText: Option[String] ) diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXRawText.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXRawText.scala new file mode 100644 index 0000000..c90e2e3 --- /dev/null +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXRawText.scala @@ -0,0 +1,13 @@ +package cc.sukazyo.cono.morny.extra.twitter + +/** The raw text of the tweet. + * + * Contains all information that you want to know about the tweet content. + * + * @param text The text-formatted tweet content. Medias is also attached as a URL in the text. + * @param facets The facets (rich text information) of the text. + */ +case class FXRawText ( + text: String, + facets: List[FXFacet] +) diff --git a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXTweet.scala b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXTweet.scala index b4b2b57..c525330 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXTweet.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/extra/twitter/FXTweet.scala @@ -7,7 +7,10 @@ import cc.sukazyo.cono.morny.util.EpochDateTime.EpochSeconds * * @param id Status (Tweet) ID * @param url Link to original Tweet - * @param text Text of Tweet + * @param text Text of Tweet. May not contains some extra information like URLs. + * @param raw_text Raw text of Tweet, contains a full article text and facets (rich information). + * Comparing with the `text` field, this contains all information that you want + * to know, like even the media is included. * @param created_at Date/Time in UTC when the Tweet was created * @param created_timestamp Date/Time in UTC when the Tweet was created * @param color Dominant color pulled from either Tweet media or from the author's profile picture. @@ -39,6 +42,7 @@ case class FXTweet ( id: String, url: String, text: String, + raw_text: FXRawText, created_at: String, created_timestamp: EpochSeconds, is_note_tweet: Boolean, // todo @@ -50,7 +54,7 @@ case class FXTweet ( // twitter_card: "tweet"|"summary"|"summary_large_image"|"player", twitter_card: Option[String], author: FXAuthor, - source: String, + source: Option[String], ///==================== /// Interaction counts diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/bot/query/ShareToolBilibiliTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/bot/query/ShareToolBilibiliTest.scala index 265345b..cf5faec 100644 --- a/src/test/scala/cc/sukazyo/cono/morny/test/bot/query/ShareToolBilibiliTest.scala +++ b/src/test/scala/cc/sukazyo/cono/morny/test/bot/query/ShareToolBilibiliTest.scala @@ -45,7 +45,7 @@ class ShareToolBilibiliTest extends MornyTests { rx_title.get(rx).asInstanceOf[String] }) for (shou <- shouldContains) - titles should contain(s"[bilibili] Share video / BV${shou.bv}") + titles should contain(s"[Bilibili] Video BV${shou.bv}") } "that contains a video id/url should contains it in result" in { diff --git a/src/test/scala/cc/sukazyo/cono/morny/test/extra/twitter/FXApiTest.scala b/src/test/scala/cc/sukazyo/cono/morny/test/extra/twitter/FXApiTest.scala index 7a84054..0eaf0f1 100644 --- a/src/test/scala/cc/sukazyo/cono/morny/test/extra/twitter/FXApiTest.scala +++ b/src/test/scala/cc/sukazyo/cono/morny/test/extra/twitter/FXApiTest.scala @@ -36,28 +36,28 @@ class FXApiTest extends MornyTests with TableDrivenPropertyChecks { val examples = Table[(Option[String], String), FXApi =>Unit]( ("id", "checking"), - ((Some("_Eyre_S"), "1669362743332438019"), api => { - api.tweet shouldBe defined - api.tweet.get.text shouldEqual "猫头猫头鹰头猫头鹰头猫头鹰" - api.tweet.get.quote shouldBe defined - api.tweet.get.quote.get.id shouldEqual "1669302279386828800" - }), - ((None, "1669362743332438019"), api => { - api.tweet shouldBe defined - api.tweet.get.text shouldEqual "猫头猫头鹰头猫头鹰头猫头鹰" - api.tweet.get.quote shouldBe defined - api.tweet.get.quote.get.id shouldEqual "1669302279386828800" - }), - ((None, "1654080016802807809"), api => { - api.tweet shouldBe defined - api.tweet.get.media shouldBe defined - api.tweet.get.media.get.videos shouldBe empty - api.tweet.get.media.get.photos shouldBe defined - api.tweet.get.media.get.photos.get.length shouldBe 1 - api.tweet.get.media.get.photos.get.head.width shouldBe 2048 - api.tweet.get.media.get.photos.get.head.height shouldBe 1536 - api.tweet.get.media.get.mosaic shouldBe empty - }), +// ((Some("_Eyre_S"), "1669362743332438019"), api => { +// api.tweet shouldBe defined +// api.tweet.get.text shouldEqual "猫头猫头鹰头猫头鹰头猫头鹰" +// api.tweet.get.quote shouldBe defined +// api.tweet.get.quote.get.id shouldEqual "1669302279386828800" +// }), +// ((None, "1669362743332438019"), api => { +// api.tweet shouldBe defined +// api.tweet.get.text shouldEqual "猫头猫头鹰头猫头鹰头猫头鹰" +// api.tweet.get.quote shouldBe defined +// api.tweet.get.quote.get.id shouldEqual "1669302279386828800" +// }), +// ((None, "1654080016802807809"), api => { +// api.tweet shouldBe defined +// api.tweet.get.media shouldBe defined +// api.tweet.get.media.get.videos shouldBe empty +// api.tweet.get.media.get.photos shouldBe defined +// api.tweet.get.media.get.photos.get.length shouldBe 1 +// api.tweet.get.media.get.photos.get.head.width shouldBe 2048 +// api.tweet.get.media.get.photos.get.head.height shouldBe 1536 +// api.tweet.get.media.get.mosaic shouldBe empty +// }), ((None, "1538536152093044736"), api => { api.tweet shouldBe defined api.tweet.get.media shouldBe defined @@ -69,7 +69,43 @@ class FXApiTest extends MornyTests with TableDrivenPropertyChecks { api.tweet.get.media.get.photos.get(1).width shouldBe 2894 api.tweet.get.media.get.photos.get(1).height shouldBe 4093 api.tweet.get.media.get.mosaic shouldBe defined - }) + }), + // https://x.com/_suk_ws/status/1472085698081484800 + ((Some("_suk_ws"), "1472085698081484800"), api => { + api.code shouldEqual 200 + api.tweet shouldBe defined + api.tweet.get.text shouldEqual "今年的工房要做年报&^&" + // this tweet is single + api.tweet.get.quote shouldBe empty + }), + // https://x.com/_suk_ws/status/1463410234504802306 + ((None, "1463410234504802306"), api => { + api.code shouldEqual 200 + api.tweet shouldBe defined + api.tweet.get.text shouldEqual "bread-card-ui 平面拟物&~&" + // this tweet is single + api.tweet.get.quote shouldBe empty + // this tweet has 1 photo and no video, only one media shouldn't be mosaic + api.tweet.get.media shouldBe defined + api.tweet.get.media.get.videos shouldBe empty + api.tweet.get.media.get.photos shouldBe defined + api.tweet.get.media.get.photos.get.length shouldBe 1 + api.tweet.get.media.get.mosaic shouldBe empty + }), + // https://x.com/_suk_ws/status/1463099580149424131 + ((Some("_suk_ws"), "1463099580149424131"), api => { + api.code shouldEqual 200 + api.tweet shouldBe defined + // this tweet has 3 photos and no video. multiple photos will be mosaic + api.tweet.get.media shouldBe defined + api.tweet.get.media.get.videos shouldBe empty + api.tweet.get.media.get.photos shouldBe defined + api.tweet.get.media.get.photos.get.length shouldBe 3 + api.tweet.get.media.get.mosaic shouldBe defined + // this tweet has a quote + api.tweet.get.quote shouldBe defined + api.tweet.get.quote.get.id shouldEqual "1463084178023452674" + }), ) forAll(examples) { (data, assertion) => s"tweet $data should be fetched successful" taggedAs (Slow, Network) in {