507 lines
17 KiB
Scala
507 lines
17 KiB
Scala
package com.twitter.tweetypie.util
|
|
|
|
import com.twitter.dataproducts.enrichments.thriftscala.ProfileGeoEnrichment
|
|
import com.twitter.expandodo.thriftscala._
|
|
import com.twitter.mediaservices.commons.thriftscala.MediaKey
|
|
import com.twitter.mediaservices.commons.tweetmedia.thriftscala._
|
|
import com.twitter.servo.data.Lens
|
|
import com.twitter.spam.rtf.thriftscala.SafetyLabel
|
|
import com.twitter.tseng.withholding.thriftscala.TakedownReason
|
|
import com.twitter.tweetypie.thriftscala._
|
|
import com.twitter.tweetypie.unmentions.thriftscala.UnmentionData
|
|
|
|
object TweetLenses {
|
|
import Lens.checkEq
|
|
|
|
def requireSome[A, B](l: Lens[A, Option[B]]): Lens[A, B] =
|
|
checkEq[A, B](
|
|
a => l.get(a).get,
|
|
(a, b) => l.set(a, Some(b))
|
|
)
|
|
|
|
def tweetLens[A](get: Tweet => A, set: (Tweet, A) => Tweet): Lens[Tweet, A] =
|
|
checkEq[Tweet, A](get, set)
|
|
|
|
val id: Lens[Tweet, TweetId] =
|
|
tweetLens[TweetId](_.id, (t, id) => t.copy(id = id))
|
|
|
|
val coreData: Lens[Tweet, Option[TweetCoreData]] =
|
|
tweetLens[Option[TweetCoreData]](_.coreData, (t, coreData) => t.copy(coreData = coreData))
|
|
|
|
val requiredCoreData: Lens[Tweet, TweetCoreData] =
|
|
requireSome(coreData)
|
|
|
|
val optUrls: Lens[Tweet, Option[Seq[UrlEntity]]] =
|
|
tweetLens[Option[Seq[UrlEntity]]](_.urls, (t, urls) => t.copy(urls = urls))
|
|
|
|
val urls: Lens[Tweet, Seq[UrlEntity]] =
|
|
tweetLens[Seq[UrlEntity]](_.urls.toSeq.flatten, (t, urls) => t.copy(urls = Some(urls)))
|
|
|
|
val optMentions: Lens[Tweet, Option[Seq[MentionEntity]]] =
|
|
tweetLens[Option[Seq[MentionEntity]]](_.mentions, (t, v) => t.copy(mentions = v))
|
|
|
|
val mentions: Lens[Tweet, Seq[MentionEntity]] =
|
|
tweetLens[Seq[MentionEntity]](_.mentions.toSeq.flatten, (t, v) => t.copy(mentions = Some(v)))
|
|
|
|
val unmentionData: Lens[Tweet, Option[UnmentionData]] =
|
|
tweetLens[Option[UnmentionData]](_.unmentionData, (t, v) => t.copy(unmentionData = v))
|
|
|
|
val optHashtags: Lens[Tweet, Option[Seq[HashtagEntity]]] =
|
|
tweetLens[Option[Seq[HashtagEntity]]](_.hashtags, (t, v) => t.copy(hashtags = v))
|
|
|
|
val hashtags: Lens[Tweet, Seq[HashtagEntity]] =
|
|
tweetLens[Seq[HashtagEntity]](_.hashtags.toSeq.flatten, (t, v) => t.copy(hashtags = Some(v)))
|
|
|
|
val optCashtags: Lens[Tweet, Option[Seq[CashtagEntity]]] =
|
|
tweetLens[Option[Seq[CashtagEntity]]](_.cashtags, (t, v) => t.copy(cashtags = v))
|
|
|
|
val cashtags: Lens[Tweet, Seq[CashtagEntity]] =
|
|
tweetLens[Seq[CashtagEntity]](_.cashtags.toSeq.flatten, (t, v) => t.copy(cashtags = Some(v)))
|
|
|
|
val optMedia: Lens[Tweet, Option[Seq[MediaEntity]]] =
|
|
tweetLens[Option[Seq[MediaEntity]]](_.media, (t, v) => t.copy(media = v))
|
|
|
|
val media: Lens[Tweet, Seq[MediaEntity]] =
|
|
tweetLens[Seq[MediaEntity]](_.media.toSeq.flatten, (t, v) => t.copy(media = Some(v)))
|
|
|
|
val mediaKeys: Lens[Tweet, Seq[MediaKey]] =
|
|
tweetLens[Seq[MediaKey]](
|
|
_.mediaKeys.toSeq.flatten,
|
|
{
|
|
case (t, v) => t.copy(mediaKeys = Some(v))
|
|
})
|
|
|
|
val place: Lens[Tweet, Option[Place]] =
|
|
tweetLens[Option[Place]](
|
|
_.place,
|
|
{
|
|
case (t, v) => t.copy(place = v)
|
|
})
|
|
|
|
val quotedTweet: Lens[Tweet, Option[QuotedTweet]] =
|
|
tweetLens[Option[QuotedTweet]](
|
|
_.quotedTweet,
|
|
{
|
|
case (t, v) => t.copy(quotedTweet = v)
|
|
})
|
|
|
|
val selfThreadMetadata: Lens[Tweet, Option[SelfThreadMetadata]] =
|
|
tweetLens[Option[SelfThreadMetadata]](
|
|
_.selfThreadMetadata,
|
|
{
|
|
case (t, v) => t.copy(selfThreadMetadata = v)
|
|
})
|
|
|
|
val composerSource: Lens[Tweet, Option[ComposerSource]] =
|
|
tweetLens[Option[ComposerSource]](
|
|
_.composerSource,
|
|
{
|
|
case (t, v) => t.copy(composerSource = v)
|
|
})
|
|
|
|
val deviceSource: Lens[Tweet, Option[DeviceSource]] =
|
|
tweetLens[Option[DeviceSource]](
|
|
_.deviceSource,
|
|
{
|
|
case (t, v) => t.copy(deviceSource = v)
|
|
})
|
|
|
|
val perspective: Lens[Tweet, Option[StatusPerspective]] =
|
|
tweetLens[Option[StatusPerspective]](
|
|
_.perspective,
|
|
{
|
|
case (t, v) => t.copy(perspective = v)
|
|
})
|
|
|
|
val cards: Lens[Tweet, Option[Seq[Card]]] =
|
|
tweetLens[Option[Seq[Card]]](
|
|
_.cards,
|
|
{
|
|
case (t, v) => t.copy(cards = v)
|
|
})
|
|
|
|
val card2: Lens[Tweet, Option[Card2]] =
|
|
tweetLens[Option[Card2]](
|
|
_.card2,
|
|
{
|
|
case (t, v) => t.copy(card2 = v)
|
|
})
|
|
|
|
val cardReference: Lens[Tweet, Option[CardReference]] =
|
|
tweetLens[Option[CardReference]](
|
|
_.cardReference,
|
|
{
|
|
case (t, v) => t.copy(cardReference = v)
|
|
})
|
|
|
|
val spamLabel: Lens[Tweet, Option[SafetyLabel]] =
|
|
tweetLens[Option[SafetyLabel]](
|
|
_.spamLabel,
|
|
{
|
|
case (t, v) => t.copy(spamLabel = v)
|
|
})
|
|
|
|
val lowQualityLabel: Lens[Tweet, Option[SafetyLabel]] =
|
|
tweetLens[Option[SafetyLabel]](
|
|
_.lowQualityLabel,
|
|
{
|
|
case (t, v) => t.copy(lowQualityLabel = v)
|
|
})
|
|
|
|
val nsfwHighPrecisionLabel: Lens[Tweet, Option[SafetyLabel]] =
|
|
tweetLens[Option[SafetyLabel]](
|
|
_.nsfwHighPrecisionLabel,
|
|
{
|
|
case (t, v) => t.copy(nsfwHighPrecisionLabel = v)
|
|
})
|
|
|
|
val bounceLabel: Lens[Tweet, Option[SafetyLabel]] =
|
|
tweetLens[Option[SafetyLabel]](
|
|
_.bounceLabel,
|
|
{
|
|
case (t, v) => t.copy(bounceLabel = v)
|
|
})
|
|
|
|
val takedownCountryCodes: Lens[Tweet, Option[Seq[String]]] =
|
|
tweetLens[Option[Seq[String]]](
|
|
_.takedownCountryCodes,
|
|
{
|
|
case (t, v) => t.copy(takedownCountryCodes = v)
|
|
})
|
|
|
|
val takedownReasons: Lens[Tweet, Option[Seq[TakedownReason]]] =
|
|
tweetLens[Option[Seq[TakedownReason]]](
|
|
_.takedownReasons,
|
|
{
|
|
case (t, v) => t.copy(takedownReasons = v)
|
|
})
|
|
|
|
val contributor: Lens[Tweet, Option[Contributor]] =
|
|
tweetLens[Option[Contributor]](
|
|
_.contributor,
|
|
{
|
|
case (t, v) => t.copy(contributor = v)
|
|
})
|
|
|
|
val mediaTags: Lens[Tweet, Option[TweetMediaTags]] =
|
|
tweetLens[Option[TweetMediaTags]](
|
|
_.mediaTags,
|
|
{
|
|
case (t, v) => t.copy(mediaTags = v)
|
|
})
|
|
|
|
val mediaTagMap: Lens[Tweet, Map[MediaId, Seq[MediaTag]]] =
|
|
tweetLens[Map[MediaId, Seq[MediaTag]]](
|
|
_.mediaTags.map { case TweetMediaTags(tagMap) => tagMap.toMap }.getOrElse(Map.empty),
|
|
(t, v) => {
|
|
val cleanMap = v.filter { case (_, tags) => tags.nonEmpty }
|
|
t.copy(mediaTags = if (cleanMap.nonEmpty) Some(TweetMediaTags(cleanMap)) else None)
|
|
}
|
|
)
|
|
|
|
val escherbirdEntityAnnotations: Lens[Tweet, Option[EscherbirdEntityAnnotations]] =
|
|
tweetLens[Option[EscherbirdEntityAnnotations]](
|
|
_.escherbirdEntityAnnotations,
|
|
{
|
|
case (t, v) => t.copy(escherbirdEntityAnnotations = v)
|
|
})
|
|
|
|
val communities: Lens[Tweet, Option[Communities]] =
|
|
tweetLens[Option[Communities]](
|
|
_.communities,
|
|
{
|
|
case (t, v) => t.copy(communities = v)
|
|
})
|
|
|
|
val tweetypieOnlyTakedownCountryCodes: Lens[Tweet, Option[Seq[String]]] =
|
|
tweetLens[Option[Seq[String]]](
|
|
_.tweetypieOnlyTakedownCountryCodes,
|
|
{
|
|
case (t, v) => t.copy(tweetypieOnlyTakedownCountryCodes = v)
|
|
})
|
|
|
|
val tweetypieOnlyTakedownReasons: Lens[Tweet, Option[Seq[TakedownReason]]] =
|
|
tweetLens[Option[Seq[TakedownReason]]](
|
|
_.tweetypieOnlyTakedownReasons,
|
|
{
|
|
case (t, v) => t.copy(tweetypieOnlyTakedownReasons = v)
|
|
})
|
|
|
|
val profileGeo: Lens[Tweet, Option[ProfileGeoEnrichment]] =
|
|
tweetLens[Option[ProfileGeoEnrichment]](
|
|
_.profileGeoEnrichment,
|
|
(t, v) => t.copy(profileGeoEnrichment = v)
|
|
)
|
|
|
|
val visibleTextRange: Lens[Tweet, Option[TextRange]] =
|
|
tweetLens[Option[TextRange]](
|
|
_.visibleTextRange,
|
|
{
|
|
case (t, v) => t.copy(visibleTextRange = v)
|
|
})
|
|
|
|
val selfPermalink: Lens[Tweet, Option[ShortenedUrl]] =
|
|
tweetLens[Option[ShortenedUrl]](
|
|
_.selfPermalink,
|
|
{
|
|
case (t, v) => t.copy(selfPermalink = v)
|
|
})
|
|
|
|
val extendedTweetMetadata: Lens[Tweet, Option[ExtendedTweetMetadata]] =
|
|
tweetLens[Option[ExtendedTweetMetadata]](
|
|
_.extendedTweetMetadata,
|
|
{
|
|
case (t, v) => t.copy(extendedTweetMetadata = v)
|
|
})
|
|
|
|
object TweetCoreData {
|
|
val userId: Lens[TweetCoreData, UserId] = checkEq[TweetCoreData, UserId](
|
|
_.userId,
|
|
{ (c, v) =>
|
|
// Pleases the compiler: https://github.com/scala/bug/issues/9171
|
|
val userId = v
|
|
c.copy(userId = userId)
|
|
})
|
|
val text: Lens[TweetCoreData, String] = checkEq[TweetCoreData, String](
|
|
_.text,
|
|
{ (c, v) =>
|
|
// Pleases the compiler: https://github.com/scala/bug/issues/9171
|
|
val text = v
|
|
c.copy(text = text)
|
|
})
|
|
val createdAt: Lens[TweetCoreData, TweetId] =
|
|
checkEq[TweetCoreData, Long](_.createdAtSecs, (c, v) => c.copy(createdAtSecs = v))
|
|
val createdVia: Lens[TweetCoreData, String] =
|
|
checkEq[TweetCoreData, String](
|
|
_.createdVia,
|
|
{
|
|
case (c, v) => c.copy(createdVia = v)
|
|
})
|
|
val hasTakedown: Lens[TweetCoreData, Boolean] =
|
|
checkEq[TweetCoreData, Boolean](
|
|
_.hasTakedown,
|
|
{
|
|
case (c, v) => c.copy(hasTakedown = v)
|
|
})
|
|
val nullcast: Lens[TweetCoreData, Boolean] =
|
|
checkEq[TweetCoreData, Boolean](
|
|
_.nullcast,
|
|
{
|
|
case (c, v) => c.copy(nullcast = v)
|
|
})
|
|
val nsfwUser: Lens[TweetCoreData, Boolean] =
|
|
checkEq[TweetCoreData, Boolean](
|
|
_.nsfwUser,
|
|
{
|
|
case (c, v) => c.copy(nsfwUser = v)
|
|
})
|
|
val nsfwAdmin: Lens[TweetCoreData, Boolean] =
|
|
checkEq[TweetCoreData, Boolean](
|
|
_.nsfwAdmin,
|
|
{
|
|
case (c, v) => c.copy(nsfwAdmin = v)
|
|
})
|
|
val reply: Lens[TweetCoreData, Option[Reply]] =
|
|
checkEq[TweetCoreData, Option[Reply]](
|
|
_.reply,
|
|
{
|
|
case (c, v) => c.copy(reply = v)
|
|
})
|
|
val share: Lens[TweetCoreData, Option[Share]] =
|
|
checkEq[TweetCoreData, Option[Share]](
|
|
_.share,
|
|
{
|
|
case (c, v) => c.copy(share = v)
|
|
})
|
|
val narrowcast: Lens[TweetCoreData, Option[Narrowcast]] =
|
|
checkEq[TweetCoreData, Option[Narrowcast]](
|
|
_.narrowcast,
|
|
{
|
|
case (c, v) => c.copy(narrowcast = v)
|
|
})
|
|
val directedAtUser: Lens[TweetCoreData, Option[DirectedAtUser]] =
|
|
checkEq[TweetCoreData, Option[DirectedAtUser]](
|
|
_.directedAtUser,
|
|
{
|
|
case (c, v) => c.copy(directedAtUser = v)
|
|
})
|
|
val conversationId: Lens[TweetCoreData, Option[ConversationId]] =
|
|
checkEq[TweetCoreData, Option[ConversationId]](
|
|
_.conversationId,
|
|
{
|
|
case (c, v) => c.copy(conversationId = v)
|
|
})
|
|
val placeId: Lens[TweetCoreData, Option[String]] =
|
|
checkEq[TweetCoreData, Option[String]](
|
|
_.placeId,
|
|
{
|
|
case (c, v) => c.copy(placeId = v)
|
|
})
|
|
val geoCoordinates: Lens[TweetCoreData, Option[GeoCoordinates]] =
|
|
checkEq[TweetCoreData, Option[GeoCoordinates]](
|
|
_.coordinates,
|
|
(c, v) => c.copy(coordinates = v)
|
|
)
|
|
val trackingId: Lens[TweetCoreData, Option[TweetId]] =
|
|
checkEq[TweetCoreData, Option[Long]](
|
|
_.trackingId,
|
|
{
|
|
case (c, v) => c.copy(trackingId = v)
|
|
})
|
|
val hasMedia: Lens[TweetCoreData, Option[Boolean]] =
|
|
checkEq[TweetCoreData, Option[Boolean]](
|
|
_.hasMedia,
|
|
{
|
|
case (c, v) => c.copy(hasMedia = v)
|
|
})
|
|
}
|
|
|
|
val counts: Lens[Tweet, Option[StatusCounts]] =
|
|
tweetLens[Option[StatusCounts]](
|
|
_.counts,
|
|
{
|
|
case (t, v) => t.copy(counts = v)
|
|
})
|
|
|
|
object StatusCounts {
|
|
val retweetCount: Lens[StatusCounts, Option[TweetId]] =
|
|
checkEq[StatusCounts, Option[Long]](
|
|
_.retweetCount,
|
|
(c, retweetCount) => c.copy(retweetCount = retweetCount)
|
|
)
|
|
|
|
val replyCount: Lens[StatusCounts, Option[TweetId]] =
|
|
checkEq[StatusCounts, Option[Long]](
|
|
_.replyCount,
|
|
(c, replyCount) => c.copy(replyCount = replyCount)
|
|
)
|
|
|
|
val favoriteCount: Lens[StatusCounts, Option[TweetId]] =
|
|
checkEq[StatusCounts, Option[Long]](
|
|
_.favoriteCount,
|
|
{
|
|
case (c, v) => c.copy(favoriteCount = v)
|
|
})
|
|
|
|
val quoteCount: Lens[StatusCounts, Option[TweetId]] =
|
|
checkEq[StatusCounts, Option[Long]](
|
|
_.quoteCount,
|
|
{
|
|
case (c, v) => c.copy(quoteCount = v)
|
|
})
|
|
}
|
|
|
|
val userId: Lens[Tweet, UserId] = requiredCoreData andThen TweetCoreData.userId
|
|
val text: Lens[Tweet, String] = requiredCoreData andThen TweetCoreData.text
|
|
val createdVia: Lens[Tweet, String] = requiredCoreData andThen TweetCoreData.createdVia
|
|
val createdAt: Lens[Tweet, ConversationId] = requiredCoreData andThen TweetCoreData.createdAt
|
|
val reply: Lens[Tweet, Option[Reply]] = requiredCoreData andThen TweetCoreData.reply
|
|
val share: Lens[Tweet, Option[Share]] = requiredCoreData andThen TweetCoreData.share
|
|
val narrowcast: Lens[Tweet, Option[Narrowcast]] =
|
|
requiredCoreData andThen TweetCoreData.narrowcast
|
|
val directedAtUser: Lens[Tweet, Option[DirectedAtUser]] =
|
|
requiredCoreData andThen TweetCoreData.directedAtUser
|
|
val conversationId: Lens[Tweet, Option[ConversationId]] =
|
|
requiredCoreData andThen TweetCoreData.conversationId
|
|
val placeId: Lens[Tweet, Option[String]] = requiredCoreData andThen TweetCoreData.placeId
|
|
val geoCoordinates: Lens[Tweet, Option[GeoCoordinates]] =
|
|
requiredCoreData andThen TweetCoreData.geoCoordinates
|
|
val hasTakedown: Lens[Tweet, Boolean] = requiredCoreData andThen TweetCoreData.hasTakedown
|
|
val nsfwAdmin: Lens[Tweet, Boolean] = requiredCoreData andThen TweetCoreData.nsfwAdmin
|
|
val nsfwUser: Lens[Tweet, Boolean] = requiredCoreData andThen TweetCoreData.nsfwUser
|
|
val nullcast: Lens[Tweet, Boolean] = requiredCoreData andThen TweetCoreData.nullcast
|
|
val trackingId: Lens[Tweet, Option[ConversationId]] =
|
|
requiredCoreData andThen TweetCoreData.trackingId
|
|
val hasMedia: Lens[Tweet, Option[Boolean]] = requiredCoreData andThen TweetCoreData.hasMedia
|
|
|
|
object CashtagEntity {
|
|
val indices: Lens[CashtagEntity, (Short, Short)] =
|
|
checkEq[CashtagEntity, (Short, Short)](
|
|
t => (t.fromIndex, t.toIndex),
|
|
(t, v) => t.copy(fromIndex = v._1, toIndex = v._2)
|
|
)
|
|
val text: Lens[CashtagEntity, String] =
|
|
checkEq[CashtagEntity, String](_.text, (t, text) => t.copy(text = text))
|
|
}
|
|
|
|
object HashtagEntity {
|
|
val indices: Lens[HashtagEntity, (Short, Short)] =
|
|
checkEq[HashtagEntity, (Short, Short)](
|
|
t => (t.fromIndex, t.toIndex),
|
|
(t, v) => t.copy(fromIndex = v._1, toIndex = v._2)
|
|
)
|
|
val text: Lens[HashtagEntity, String] =
|
|
checkEq[HashtagEntity, String](_.text, (t, text) => t.copy(text = text))
|
|
}
|
|
|
|
object MediaEntity {
|
|
val indices: Lens[MediaEntity, (Short, Short)] =
|
|
checkEq[MediaEntity, (Short, Short)](
|
|
t => (t.fromIndex, t.toIndex),
|
|
(t, v) => t.copy(fromIndex = v._1, toIndex = v._2)
|
|
)
|
|
val mediaSizes: Lens[MediaEntity, collection.Set[MediaSize]] =
|
|
checkEq[MediaEntity, scala.collection.Set[MediaSize]](
|
|
_.sizes,
|
|
(m, sizes) => m.copy(sizes = sizes)
|
|
)
|
|
val url: Lens[MediaEntity, String] =
|
|
checkEq[MediaEntity, String](
|
|
_.url,
|
|
{
|
|
case (t, v) => t.copy(url = v)
|
|
})
|
|
val mediaInfo: Lens[MediaEntity, Option[MediaInfo]] =
|
|
checkEq[MediaEntity, Option[MediaInfo]](
|
|
_.mediaInfo,
|
|
{
|
|
case (t, v) => t.copy(mediaInfo = v)
|
|
})
|
|
}
|
|
|
|
object MentionEntity {
|
|
val indices: Lens[MentionEntity, (Short, Short)] =
|
|
checkEq[MentionEntity, (Short, Short)](
|
|
t => (t.fromIndex, t.toIndex),
|
|
(t, v) => t.copy(fromIndex = v._1, toIndex = v._2)
|
|
)
|
|
val screenName: Lens[MentionEntity, String] =
|
|
checkEq[MentionEntity, String](
|
|
_.screenName,
|
|
(t, screenName) => t.copy(screenName = screenName)
|
|
)
|
|
}
|
|
|
|
object UrlEntity {
|
|
val indices: Lens[UrlEntity, (Short, Short)] =
|
|
checkEq[UrlEntity, (Short, Short)](
|
|
t => (t.fromIndex, t.toIndex),
|
|
(t, v) => t.copy(fromIndex = v._1, toIndex = v._2)
|
|
)
|
|
val url: Lens[UrlEntity, String] =
|
|
checkEq[UrlEntity, String](_.url, (t, url) => t.copy(url = url))
|
|
}
|
|
|
|
object Contributor {
|
|
val screenName: Lens[Contributor, Option[String]] =
|
|
checkEq[Contributor, Option[String]](
|
|
_.screenName,
|
|
(c, screenName) => c.copy(screenName = screenName)
|
|
)
|
|
}
|
|
|
|
object Reply {
|
|
val inReplyToScreenName: Lens[Reply, Option[String]] =
|
|
checkEq[Reply, Option[String]](
|
|
_.inReplyToScreenName,
|
|
(c, inReplyToScreenName) => c.copy(inReplyToScreenName = inReplyToScreenName)
|
|
)
|
|
|
|
val inReplyToStatusId: Lens[Reply, Option[TweetId]] =
|
|
checkEq[Reply, Option[TweetId]](
|
|
_.inReplyToStatusId,
|
|
(c, inReplyToStatusId) => c.copy(inReplyToStatusId = inReplyToStatusId)
|
|
)
|
|
}
|
|
}
|