Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ package io.getstream.chat.android.client.api2.endpoint

import io.getstream.chat.android.client.api.AuthenticatedApi
import io.getstream.chat.android.client.api.QueryParams
import io.getstream.chat.android.client.api2.model.dto.AttachmentDto
import io.getstream.chat.android.client.call.RetrofitCall
import io.getstream.chat.android.network.models.GetOGResponse
import retrofit2.http.GET
import retrofit2.http.Query

Expand All @@ -30,5 +30,5 @@ import retrofit2.http.Query
internal interface OpenGraphApi {

@GET("/og")
fun get(@Query(QueryParams.URL) url: String): RetrofitCall<AttachmentDto>
fun get(@Query(QueryParams.URL) url: String): RetrofitCall<GetOGResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ import io.getstream.chat.android.network.models.ChannelResponse
import io.getstream.chat.android.network.models.ChatPreferencesResponse
import io.getstream.chat.android.network.models.DeviceResponse
import io.getstream.chat.android.network.models.GetApplicationResponse
import io.getstream.chat.android.network.models.GetOGResponse
import io.getstream.chat.android.network.models.PollOptionResponseData
import io.getstream.chat.android.network.models.PushPreferencesResponse
import io.getstream.chat.android.network.models.UnreadCountsChannel
Expand Down Expand Up @@ -747,6 +748,41 @@ internal class DomainMapping(
extraData = extraData.toMutableMap(),
)

internal fun GetOGResponse.toDomain(): Attachment {
// The spec does not declare file_size/image/mime_type/name, so when the wire sends them they
// arrive in `custom`. Read them back out and remove them, or they would also sit in extraData
// under their wire names, which the hand-written DTO never did.
val extras = custom.toMutableMap()
val fileSize = (extras.remove("file_size") as? Number)?.toInt() ?: 0
val image = extras.remove("image") as? String
val mimeType = extras.remove("mime_type") as? String
val name = extras.remove("name") as? String
return Attachment(
assetUrl = assetUrl,
authorName = authorName,
authorLink = authorLink,
fallback = fallback,
fileSize = fileSize,
image = image,
imageUrl = imageUrl,
mimeType = mimeType,
name = name,
ogUrl = ogScrapeUrl,
text = text,
thumbUrl = thumbUrl,
title = title,
titleLink = titleLink,
type = type,
originalHeight = originalHeight,
originalWidth = originalWidth,
extraData = extras.mapNotNull { (key, value) -> value?.let { key to it } }.toMap(),
)
// `giphy` is deliberately not mapped: unlike the fields above, it has a single producer -- the
// giphy slash command, which writes it to a message attachment -- so it cannot reach /og. The
// slice that adopts the shared Attachment model owns it, and must re-emit it into
// extraData["giphy"] or Attachment.giphyInfo() stops finding gif urls.
}

/**
* Transforms [BannedUserResponse] to [BannedUser].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import io.getstream.chat.android.client.parser2.adapters.DownstreamUserDtoAdapte
import io.getstream.chat.android.client.parser2.adapters.EventAdapterFactory
import io.getstream.chat.android.client.parser2.adapters.EventRequestAdapter
import io.getstream.chat.android.client.parser2.adapters.ExactDateAdapter
import io.getstream.chat.android.client.parser2.adapters.GetOGResponseAdapter
import io.getstream.chat.android.client.parser2.adapters.MessageRequestAdapter
import io.getstream.chat.android.client.parser2.adapters.NullCollectionsAsEmptyFactory
import io.getstream.chat.android.client.parser2.adapters.PollOptionInputAdapter
Expand Down Expand Up @@ -123,6 +124,7 @@ internal class MoshiChatParser(
.add(PollOptionInputAdapter)
.add(PollOptionRequestAdapter)
.add(EventRequestAdapter)
.add(GetOGResponseAdapter)
.add(PollOptionResponseDataAdapter)
.add(
CreatePollRequest.VotingVisibility::class.java,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.chat.android.client.parser2.adapters

import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.ToJson
import io.getstream.chat.android.network.models.GetOGResponse

/**
* Collects the root-level custom fields of an OG scrape into `custom`, which is how the API sends them.
*
* The endpoint also ships `file_size`, `image`, `mime_type` and `name` at the root even though the spec
* does not declare them, so they arrive here too; `GetOGResponse.toDomain()` reads them back out.
*/
internal object GetOGResponseAdapter :
CustomObjectDtoAdapter<GetOGResponse>(GetOGResponse::class, extraDataPropertyName = "custom") {

@FromJson
fun fromJson(
jsonReader: JsonReader,
mapAdapter: JsonAdapter<MutableMap<String, Any>>,
valueAdapter: JsonAdapter<GetOGResponse>,
): GetOGResponse? = parseWithExtraData(jsonReader, mapAdapter, valueAdapter)

@ToJson
@Suppress("UNUSED_PARAMETER")
fun toJson(jsonWriter: JsonWriter, value: GetOGResponse): Unit = error("Can't convert this to Json")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:Suppress(
"ArrayInDataClass",
"EnumEntryName",
"RemoveRedundantQualifierName",
"UnusedImport",
)

package io.getstream.chat.android.network.models

import com.squareup.moshi.Json

/**
*
*/
@com.squareup.moshi.JsonClass(generateAdapter = true)
internal data class GetOGResponse(
@Json(name = "duration")
internal val duration: String,

@Json(name = "custom")
internal val custom: Map<String, Any?> = emptyMap(),

@Json(name = "asset_url")
internal val assetUrl: String? = null,

@Json(name = "author_icon")
internal val authorIcon: String? = null,

@Json(name = "author_link")
internal val authorLink: String? = null,

@Json(name = "author_name")
internal val authorName: String? = null,

@Json(name = "color")
internal val color: String? = null,

@Json(name = "fallback")
internal val fallback: String? = null,

@Json(name = "footer")
internal val footer: String? = null,

@Json(name = "footer_icon")
internal val footerIcon: String? = null,

@Json(name = "image_url")
internal val imageUrl: String? = null,

@Json(name = "og_scrape_url")
internal val ogScrapeUrl: String? = null,

@Json(name = "original_height")
internal val originalHeight: Int? = null,

@Json(name = "original_width")
internal val originalWidth: Int? = null,

@Json(name = "pretext")
internal val pretext: String? = null,

@Json(name = "text")
internal val text: String? = null,

@Json(name = "thumb_url")
internal val thumbUrl: String? = null,

@Json(name = "title")
internal val title: String? = null,

@Json(name = "title_link")
internal val titleLink: String? = null,

@Json(name = "type")
internal val type: String? = null,

@Json(name = "actions")
internal val actions: List<io.getstream.chat.android.network.models.Action>? = emptyList(),

@Json(name = "fields")
internal val fields: List<io.getstream.chat.android.network.models.Field>? = emptyList(),

@Json(name = "giphy")
internal val giphy: io.getstream.chat.android.network.models.Images? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import io.getstream.chat.android.network.models.DeviceResponse
import io.getstream.chat.android.network.models.FileUploadConfig
import io.getstream.chat.android.network.models.FileUploadResponse
import io.getstream.chat.android.network.models.GetApplicationResponse
import io.getstream.chat.android.network.models.GetOGResponse
import io.getstream.chat.android.network.models.PollOptionResponseData
import io.getstream.chat.android.network.models.ThreadParticipant
import io.getstream.chat.android.network.models.UnblockUsersResponse
Expand Down Expand Up @@ -790,6 +791,20 @@ internal object Mother {
last_delivered_message_id = lastDeliveredMessageId,
)

fun randomGetOGResponse(
duration: String = randomString(),
type: String? = randomString(),
ogScrapeUrl: String? = randomString(),
imageUrl: String? = randomString(),
custom: Map<String, Any?> = emptyMap(),
): GetOGResponse = GetOGResponse(
duration = duration,
type = type,
ogScrapeUrl = ogScrapeUrl,
imageUrl = imageUrl,
custom = custom,
)

fun randomAttachmentDto(
assetUrl: String? = randomString(),
authorName: String? = randomString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import io.getstream.chat.android.client.api2.endpoint.UserGroupApi
import io.getstream.chat.android.client.api2.mapping.DomainMapping
import io.getstream.chat.android.client.api2.mapping.DtoMapping
import io.getstream.chat.android.client.api2.mapping.EventMapping
import io.getstream.chat.android.client.api2.model.dto.AttachmentDto
import io.getstream.chat.android.client.api2.model.dto.DownstreamLocationDto
import io.getstream.chat.android.client.api2.model.dto.UpstreamChatPreferencesDto
import io.getstream.chat.android.client.api2.model.dto.UpstreamPushPreferenceInputDto
Expand Down Expand Up @@ -138,6 +137,7 @@ import io.getstream.chat.android.network.models.DeliveredMessagePayload
import io.getstream.chat.android.network.models.EventRequest
import io.getstream.chat.android.network.models.GetApplicationResponse
import io.getstream.chat.android.network.models.GetBlockedUsersResponse
import io.getstream.chat.android.network.models.GetOGResponse
import io.getstream.chat.android.network.models.GetUserGroupResponse
import io.getstream.chat.android.network.models.GroupedChannelsGroupRequest
import io.getstream.chat.android.network.models.GroupedQueryChannelsRequest
Expand Down Expand Up @@ -1938,7 +1938,7 @@ internal class MoshiChatApiTest {

@ParameterizedTest
@MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#ogInput")
fun testOg(call: RetrofitCall<AttachmentDto>, expected: KClass<*>) = runTest {
fun testOg(call: RetrofitCall<GetOGResponse>, expected: KClass<*>) = runTest {
// given
val api = mock<OpenGraphApi>()
whenever(api.get(any())).doReturn(call)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import io.getstream.chat.android.client.Mother.randomUnreadCountByTeamDto
import io.getstream.chat.android.client.Mother.randomUnreadDto
import io.getstream.chat.android.client.Mother.randomUnreadThreadDto
import io.getstream.chat.android.client.api.FakeResponse
import io.getstream.chat.android.client.api2.model.dto.AttachmentDto
import io.getstream.chat.android.client.api2.model.dto.DownstreamLocationDto
import io.getstream.chat.android.client.api2.model.dto.DownstreamReminderDto
import io.getstream.chat.android.client.api2.model.dto.HealthEventDto
Expand Down Expand Up @@ -74,6 +73,7 @@ import io.getstream.chat.android.network.models.CreateGuestResponse
import io.getstream.chat.android.network.models.CreateUserGroupResponse
import io.getstream.chat.android.network.models.GetApplicationResponse
import io.getstream.chat.android.network.models.GetBlockedUsersResponse
import io.getstream.chat.android.network.models.GetOGResponse
import io.getstream.chat.android.network.models.GetUserGroupResponse
import io.getstream.chat.android.network.models.ListDevicesResponse
import io.getstream.chat.android.network.models.ListUserGroupsResponse
Expand Down Expand Up @@ -431,8 +431,8 @@ internal object MoshiChatApiTestArguments {

@JvmStatic
fun ogInput() = listOf(
Arguments.of(RetroSuccess(Mother.randomAttachmentDto()).toRetrofitCall(), Result.Success::class),
Arguments.of(RetroError<AttachmentDto>(statusCode = 500).toRetrofitCall(), Result.Failure::class),
Arguments.of(RetroSuccess(Mother.randomGetOGResponse()).toRetrofitCall(), Result.Success::class),
Arguments.of(RetroError<GetOGResponse>(statusCode = 500).toRetrofitCall(), Result.Failure::class),
)

@JvmStatic
Expand Down
Loading
Loading