diff --git a/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxOEmbedMethods.kt b/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxOEmbedMethods.kt new file mode 100644 index 000000000..9b2b5dc55 --- /dev/null +++ b/bigbone-rx/src/main/kotlin/social/bigbone/rx/RxOEmbedMethods.kt @@ -0,0 +1,35 @@ +package social.bigbone.rx + +import io.reactivex.rxjava3.core.Single +import social.bigbone.MastodonClient +import social.bigbone.api.entity.OEmbedMetadata +import social.bigbone.api.method.OEmbedMethods + +/** + * Reactive implementation of [OEmbedMethods]. For generating OEmbed previews. + * @see Mastodon oembed API methods + */ +class RxOEmbedMethods(private val mastodonClient: MastodonClient) { + + private val oEmbedMethods = OEmbedMethods(mastodonClient) + + /** + * Get oEmbed info as JSON. + * @see Mastodon API documentation: methods/oembed/#get + * @param urlOfStatus URL of a status for which to return oEmbed info. + * @param maxWidth Width of the iframe. Defaults to 400. + * @param maxHeight Height of the iframe. Defaults to null. + */ + @JvmOverloads + fun getOEmbedInfoAsJson( + urlOfStatus: String, + maxWidth: Int = 400, + maxHeight: Int? = null + ): Single = Single.fromCallable { + oEmbedMethods.getOEmbedInfoAsJson( + urlOfStatus = urlOfStatus, + maxWidth = maxWidth, + maxHeight = maxHeight + ).execute() + } +} diff --git a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt index 8cb542d9c..78db60986 100644 --- a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt +++ b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt @@ -32,6 +32,7 @@ import social.bigbone.api.method.MediaMethods import social.bigbone.api.method.MuteMethods import social.bigbone.api.method.NotificationMethods import social.bigbone.api.method.OAuthMethods +import social.bigbone.api.method.OEmbedMethods import social.bigbone.api.method.PollMethods import social.bigbone.api.method.PreferenceMethods import social.bigbone.api.method.ReportMethods @@ -67,214 +68,221 @@ private constructor( private var port: Int = 443 /** - * Access API methods under "api/vX/accounts" endpoint. + * Access API methods under the "accounts" endpoint. */ @Suppress("unused") // public API @get:JvmName("accounts") val accounts: AccountMethods by lazy { AccountMethods(this) } /** - * Access API methods under "api/vX/announcements" endpoint. + * Access API methods under the "announcements" endpoint. */ @Suppress("unused") // public API @get:JvmName("announcements") val announcements: AnnouncementMethods by lazy { AnnouncementMethods(this) } /** - * Access API methods under "api/vX/apps" endpoint. + * Access API methods under the "apps" endpoint. */ @Suppress("unused") // public API @get:JvmName("apps") val apps: AppMethods by lazy { AppMethods(this) } /** - * Access API methods under "api/vX/blocks" endpoint. + * Access API methods under the "blocks" endpoint. */ @Suppress("unused") // public API @get:JvmName("blocks") val blocks: BlockMethods by lazy { BlockMethods(this) } /** - * Access API methods under "api/vX/bookmarks" endpoint. + * Access API methods under the "bookmarks" endpoint. */ @Suppress("unused") // public API @get:JvmName("bookmarks") val bookmarks: BookmarkMethods by lazy { BookmarkMethods(this) } /** - * Access API methods under "api/vX/conversations" endpoint. + * Access API methods under the "conversations" endpoint. */ @Suppress("unused") // public API @get:JvmName("conversations") val conversations: ConversationMethods by lazy { ConversationMethods(this) } /** - * Access API methods under "api/vX/custom_emojis" endpoint. + * Access API methods under the "custom_emojis" endpoint. */ @Suppress("unused") // public API @get:JvmName("customEmojis") val customEmojis: CustomEmojiMethods by lazy { CustomEmojiMethods(this) } /** - * Access API methods under "api/vX/directory" endpoint. + * Access API methods under the "directory" endpoint. */ @Suppress("unused") // public API @get:JvmName("directories") val directories: DirectoryMethods by lazy { DirectoryMethods(this) } /** - * Access API methods under "api/vX/domain_blocks" endpoint. + * Access API methods under the "domain_blocks" endpoint. */ @Suppress("unused") // public API @get:JvmName("domainBlocks") val domainBlocks: DomainBlockMethods by lazy { DomainBlockMethods(this) } /** - * Access API methods under "api/vX/endorsements" endpoint. + * Access API methods under the "endorsements" endpoint. */ @Suppress("unused") // public API @get:JvmName("endorsements") val endorsements: EndorsementMethods by lazy { EndorsementMethods(this) } /** - * Access API methods under "api/vX/favourites" endpoint. + * Access API methods under the "favourites" endpoint. */ @Suppress("unused") // public API @get:JvmName("favourites") val favourites: FavouriteMethods by lazy { FavouriteMethods(this) } /** - * Access API methods under "api/vX/featured_tags" endpoint. + * Access API methods under the "featured_tags" endpoint. */ @Suppress("unused") // public API @get:JvmName("featuredTags") val featuredTags: FeaturedTagMethods by lazy { FeaturedTagMethods(this) } /** - * Access API methods under "api/vX/filters" endpoint. + * Access API methods under the "filters" endpoint. */ @Suppress("unused") // public API @get:JvmName("filters") val filters: FilterMethods by lazy { FilterMethods(this) } /** - * Access API methods under "api/vX/follow_requests" endpoint. + * Access API methods under the "follow_requests" endpoint. */ @Suppress("unused") // public API @get:JvmName("followRequests") val followRequests: FollowRequestMethods by lazy { FollowRequestMethods(this) } /** - * Access API methods under "api/vX/instance" endpoint. + * Access API methods under the "instance" endpoint. */ @Suppress("unused") // public API @get:JvmName("instances") val instances: InstanceMethods by lazy { InstanceMethods(this) } /** - * Access API methods under "api/vX/lists" endpoint. + * Access API methods under the "lists" endpoint. */ @Suppress("unused") // public API @get:JvmName("lists") val lists: ListMethods by lazy { ListMethods(this) } /** - * Save and restore your position in timelines. + * Access API methods under the "markers" endpoint. */ @Suppress("unused") // public API @get:JvmName("markers") val markers: MarkerMethods by lazy { MarkerMethods(this) } /** - * Access API methods under "api/vX/media" endpoint. + * Access API methods under the "media" endpoint. */ @Suppress("unused") // public API @get:JvmName("media") val media: MediaMethods by lazy { MediaMethods(this) } /** - * Access API methods under "api/vX/mutes" endpoint. + * Access API methods under the "mutes" endpoint. */ @Suppress("unused") // public API @get:JvmName("mutes") val mutes: MuteMethods by lazy { MuteMethods(this) } /** - * Access API methods under "api/vX/notification[s]" endpoint. + * Access API methods under the "notification" endpoint. */ @Suppress("unused") // public API @get:JvmName("notifications") val notifications: NotificationMethods by lazy { NotificationMethods(this) } /** - * Access API methods under "oauth" endpoint. + * Access API methods under the "oauth" endpoint. */ @Suppress("unused") // public API @get:JvmName("oauth") val oauth: OAuthMethods by lazy { OAuthMethods(this) } /** - * Access API methods under "polls" endpoint. + * Access API methods under the "oembed" endpoint. + */ + @Suppress("unused") // public API + @get:JvmName("oembed") + val oembed: OEmbedMethods by lazy { OEmbedMethods(this) } + + /** + * Access API methods under the "polls" endpoint. */ @Suppress("unused") // public API @get:JvmName("polls") val polls: PollMethods by lazy { PollMethods(this) } /** - * Access API methods under "api/vX/reports" endpoint. + * Access API methods under the "preferences" endpoint. + */ + @Suppress("unused") // public API + @get:JvmName("preferences") + val preferences: PreferenceMethods by lazy { PreferenceMethods(this) } + + /** + * Access API methods under the "reports" endpoint. */ @Suppress("unused") // public API @get:JvmName("reports") val reports: ReportMethods by lazy { ReportMethods(this) } /** - * Access API methods under "api/vX/search" endpoint. + * Access API methods under the "search" endpoint. */ @Suppress("unused") // public API @get:JvmName("search") val search: SearchMethods by lazy { SearchMethods(this) } /** - * Access API methods under "api/vX/statuses" endpoint. + * Access API methods under the "statuses" endpoint. */ @Suppress("unused") // public API @get:JvmName("statuses") val statuses: StatusMethods by lazy { StatusMethods(this) } /** - * Access API methods under "api/vX/streaming" endpoint. + * Access API methods under the "streaming" endpoint. */ @Suppress("unused") // public API @get:JvmName("streaming") val streaming: StreamingMethods by lazy { StreamingMethods(this) } /** - * Access API methods under "api/vX/tags" endpoint. - */ - @Suppress("unused") // public API - @get:JvmName("tags") - val tags: TagMethods by lazy { TagMethods(this) } - - /** - * Access API methods under "api/vX/timelines" endpoint. + * Access API methods under the "suggestions" endpoint. */ @Suppress("unused") // public API - @get:JvmName("timelines") - val timelines: TimelineMethods by lazy { TimelineMethods(this) } + @get:JvmName("suggestions") + val suggestions: SuggestionMethods by lazy { SuggestionMethods(this) } /** - * Access API methods under "preferences" endpoint. + * Access API methods under the "tags" endpoint. */ @Suppress("unused") // public API - @get:JvmName("preferences") - val preferences: PreferenceMethods by lazy { PreferenceMethods(this) } + @get:JvmName("tags") + val tags: TagMethods by lazy { TagMethods(this) } /** - * Access API methods under "api/vX/suggestions" endpoint. + * Access API methods under the "timelines" endpoint. */ @Suppress("unused") // public API - @get:JvmName("suggestions") - val suggestions: SuggestionMethods by lazy { SuggestionMethods(this) } + @get:JvmName("timelines") + val timelines: TimelineMethods by lazy { TimelineMethods(this) } /** * Specifies the HTTP methods / HTTP verb that can be used by this class. diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/OEmbedMetadata.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/OEmbedMetadata.kt new file mode 100644 index 000000000..7c2238b31 --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/OEmbedMetadata.kt @@ -0,0 +1,83 @@ +package social.bigbone.api.entity + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import social.bigbone.api.method.OEmbedMethods + +/** + * oEmbed metadata returned by [OEmbedMethods]. + * @see OEmbed response parameters documentation + */ +@Serializable +data class OEmbedMetadata( + /** + * The resource type. + * For now, this will always be "rich". + */ + @SerialName("type") + val type: String = "rich", + + /** + * The oEmbed version number. Must be 1.0. + */ + @SerialName("version") + val version: String = "1.0", + + /** + * A text title, describing this resource. + */ + @SerialName("title") + val title: String? = "", + + /** + * The name of the author/owner of the resource. + */ + @SerialName("author_name") + val authorName: String? = "", + + /** + * A URL for the other/owner of the resource. + */ + @SerialName("author_url") + val authorUrl: String? = "", + + /** + * The name of the resource provider. + */ + @SerialName("provider_name") + val providerName: String? = "", + + /** + * The URL of the resource provider. + */ + @SerialName("provider_url") + val providerUrl: String? = "", + + /** + * The suggested cache lifetime for this resource, in seconds. + * Consumers may choose to use this value or not. + */ + @SerialName("cache_age") + val cacheAge: Int? = null, + + /** + * The HTML required to display the resource. + * The HTML should have no padding or margins. + * Consumers may wish to load the HTML in an off-domain iframe to avoid XSS vulnerabilities. + * The markup should be valid XHTML 1.0 Basic. + */ + @SerialName("html") + val html: String? = "", + + /** + * The width in pixels required to display the HTML. + */ + @SerialName("width") + val width: Int? = null, + + /** + * The height in pixels required to display the HTML. + */ + @SerialName("height") + val height: Int? = null +) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/OEmbedMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/OEmbedMethods.kt new file mode 100644 index 000000000..fb539b26d --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/method/OEmbedMethods.kt @@ -0,0 +1,39 @@ +package social.bigbone.api.method + +import social.bigbone.MastodonClient +import social.bigbone.MastodonRequest +import social.bigbone.Parameters +import social.bigbone.api.entity.OEmbedMetadata + +/** + * For generating OEmbed previews. + * @see Mastodon oembed API methods + */ +class OEmbedMethods(private val mastodonClient: MastodonClient) { + + private val oEmbedEndpoint = "api/oembed" + + /** + * Get oEmbed info as JSON. + * @see Mastodon API documentation: methods/oembed/#get + * @param urlOfStatus URL of a status for which to return oEmbed info. + * @param maxWidth Width of the iframe. Defaults to 400. + * @param maxHeight Height of the iframe. Defaults to null. + */ + @JvmOverloads + fun getOEmbedInfoAsJson( + urlOfStatus: String, + maxWidth: Int = 400, + maxHeight: Int? = null + ): MastodonRequest { + return mastodonClient.getMastodonRequest( + endpoint = oEmbedEndpoint, + method = MastodonClient.Method.GET, + parameters = Parameters().apply { + append("url", urlOfStatus) + append("maxwidth", maxWidth) + maxHeight?.let { append("maxheight", it) } + } + ) + } +} diff --git a/bigbone/src/test/assets/oembed_get_success.json b/bigbone/src/test/assets/oembed_get_success.json new file mode 100644 index 000000000..9a0534877 --- /dev/null +++ b/bigbone/src/test/assets/oembed_get_success.json @@ -0,0 +1,13 @@ +{ + "type": "rich", + "version": "1.0", + "title": "New status by trwnh", + "author_name": "infinite love ⴳ", + "author_url": "https://mastodon.social/@trwnh", + "provider_name": "mastodon.social", + "provider_url": "https://mastodon.social/", + "cache_age": 86400, + "html": "", + "width": 400, + "height": null +} diff --git a/bigbone/src/test/kotlin/social/bigbone/api/method/OEmbedMethodsTest.kt b/bigbone/src/test/kotlin/social/bigbone/api/method/OEmbedMethodsTest.kt new file mode 100644 index 000000000..8e00faf86 --- /dev/null +++ b/bigbone/src/test/kotlin/social/bigbone/api/method/OEmbedMethodsTest.kt @@ -0,0 +1,51 @@ +package social.bigbone.api.method + +import io.mockk.verify +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull +import org.junit.jupiter.api.Test +import social.bigbone.Parameters +import social.bigbone.api.entity.OEmbedMetadata +import social.bigbone.testtool.MockClient + +class OEmbedMethodsTest { + + @Test + fun `Given a client returning successfully, when getting oEmbed info, then ensure correct parsing and correct method was called`() { + val client = MockClient.mock("oembed_get_success.json") + val oEmbedMethods = OEmbedMethods(client) + val urlOfStatus: String = "https://mastodon.social/@trwnh/99664077509711321" + val maxWidth: Int = 640 + val maxHeight: Int? = null + + val meta: OEmbedMetadata = oEmbedMethods + .getOEmbedInfoAsJson(urlOfStatus = urlOfStatus, maxWidth = maxWidth, maxHeight = maxHeight) + .execute() + + with(meta) { + type shouldBeEqualTo "rich" + version shouldBeEqualTo "1.0" + title shouldBeEqualTo "New status by trwnh" + authorName shouldBeEqualTo "infinite love ⴳ" + authorUrl shouldBeEqualTo "https://mastodon.social/@trwnh" + providerName shouldBeEqualTo "mastodon.social" + providerUrl shouldBeEqualTo "https://mastodon.social/" + cacheAge shouldBeEqualTo 86_400 + html shouldBeEqualTo "" + + "" + width shouldBeEqualTo 400 + height.shouldBeNull() + } + + verify { + client.get( + path = "api/oembed", + query = any() + ) + } + } +} diff --git a/sample-java/src/main/java/social/bigbone/sample/GetOEmbedMetadata.java b/sample-java/src/main/java/social/bigbone/sample/GetOEmbedMetadata.java new file mode 100644 index 000000000..d18beab12 --- /dev/null +++ b/sample-java/src/main/java/social/bigbone/sample/GetOEmbedMetadata.java @@ -0,0 +1,20 @@ +package social.bigbone.sample; + +import social.bigbone.MastodonClient; +import social.bigbone.api.entity.OEmbedMetadata; +import social.bigbone.api.exception.BigBoneRequestException; + +public class GetOEmbedMetadata { + + public static void main(final String[] args) throws BigBoneRequestException { + final String instance = args[0]; + final String statusUrl = args[1]; + + // Instantiate client + final MastodonClient client = new MastodonClient.Builder(instance).build(); + + // Get oEmbed metadata for [statusUrl] + final OEmbedMetadata oEmbedMetadata = client.oembed().getOEmbedInfoAsJson(statusUrl).execute(); + System.out.println(oEmbedMetadata); + } +} diff --git a/sample-kotlin/src/main/kotlin/social/bigbone/sample/GetOEmbedMetadata.kt b/sample-kotlin/src/main/kotlin/social/bigbone/sample/GetOEmbedMetadata.kt new file mode 100644 index 000000000..903ad70dd --- /dev/null +++ b/sample-kotlin/src/main/kotlin/social/bigbone/sample/GetOEmbedMetadata.kt @@ -0,0 +1,20 @@ +package social.bigbone.sample + +import social.bigbone.MastodonClient +import social.bigbone.api.entity.OEmbedMetadata + +object GetOEmbedMetadata { + + @JvmStatic + fun main(args: Array) { + val instance = args[0] + val statusUrl = args[1] + + // Instantiate client + val client = MastodonClient.Builder(instance).build() + + // Get oEmbed metadata for [statusUrl] + val oEmbedMetadata: OEmbedMetadata = client.oembed.getOEmbedInfoAsJson(urlOfStatus = statusUrl).execute() + println(oEmbedMetadata) + } +}