2018-01-04 19:52:25 +01:00
|
|
|
|
package jp.juggler.subwaytooter.api.entity
|
|
|
|
|
|
2018-12-02 08:03:54 +01:00
|
|
|
|
import android.annotation.SuppressLint
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.content.Context
|
2019-02-15 02:51:22 +01:00
|
|
|
|
import androidx.annotation.StringRes
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.text.Spannable
|
|
|
|
|
import android.text.SpannableString
|
|
|
|
|
import jp.juggler.subwaytooter.App1
|
|
|
|
|
import jp.juggler.subwaytooter.Pref
|
|
|
|
|
import jp.juggler.subwaytooter.R
|
2018-05-08 09:05:58 +02:00
|
|
|
|
import jp.juggler.subwaytooter.api.TootAccountMap
|
2018-01-12 10:01:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.api.TootApiClient
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.api.TootParser
|
|
|
|
|
import jp.juggler.subwaytooter.table.HighlightWord
|
|
|
|
|
import jp.juggler.subwaytooter.table.SavedAccount
|
|
|
|
|
import jp.juggler.subwaytooter.util.*
|
2018-12-01 00:02:18 +01:00
|
|
|
|
import jp.juggler.util.*
|
|
|
|
|
import java.lang.ref.WeakReference
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
|
import java.util.*
|
2018-12-01 00:02:18 +01:00
|
|
|
|
import java.util.regex.Pattern
|
2018-08-16 21:58:30 +02:00
|
|
|
|
import kotlin.collections.ArrayList
|
2020-01-07 09:31:05 +01:00
|
|
|
|
import kotlin.collections.LinkedHashMap
|
2018-12-26 18:08:01 +01:00
|
|
|
|
import kotlin.math.abs
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2019-10-22 06:39:00 +02:00
|
|
|
|
class FilterTrees(
|
|
|
|
|
val treeIrreversible : WordTrieTree = WordTrieTree(),
|
|
|
|
|
val treeReversible : WordTrieTree = WordTrieTree(),
|
|
|
|
|
val treeAll : WordTrieTree = WordTrieTree()
|
|
|
|
|
)
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
@Suppress("MemberVisibilityCanPrivate")
|
2020-01-07 09:03:32 +01:00
|
|
|
|
class TootStatus(parser : TootParser, src : JsonObject) : TimelineItem() {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val json : JsonObject
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// A Fediverse-unique resource ID
|
2018-01-11 10:31:25 +01:00
|
|
|
|
// MSP から取得したデータだと uri は提供されずnullになる
|
2018-12-06 09:31:50 +01:00
|
|
|
|
val uri : String
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-01-11 10:31:25 +01:00
|
|
|
|
// URL to the status page (can be remote)
|
|
|
|
|
// ブーストだとnullになる
|
|
|
|
|
val url : String?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-01-11 10:31:25 +01:00
|
|
|
|
// 投稿元タンスのホスト名
|
2018-01-10 16:47:35 +01:00
|
|
|
|
val host_original : String
|
|
|
|
|
get() = account.host
|
|
|
|
|
|
2018-01-11 10:31:25 +01:00
|
|
|
|
// 取得タンスのホスト名。トゥート検索サービスでは提供されずnullになる
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val host_access : String?
|
|
|
|
|
|
2018-01-11 10:31:25 +01:00
|
|
|
|
// ステータスID。
|
|
|
|
|
// host_access が null の場合は投稿元タンスでのIDかもしれない。
|
|
|
|
|
// 取得に失敗するとINVALID_IDになる
|
2018-08-18 12:58:14 +02:00
|
|
|
|
// Misskeyでは文字列のID。
|
|
|
|
|
val id : EntityId
|
|
|
|
|
|
|
|
|
|
// misskey ではページネーションにIDではなくエポック秒を使う
|
2018-08-24 16:38:32 +02:00
|
|
|
|
internal var _orderId : EntityId
|
2018-08-18 12:58:14 +02:00
|
|
|
|
|
|
|
|
|
override fun getOrderId() = _orderId
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
// The TootAccount which posted the status
|
2018-05-08 10:25:02 +02:00
|
|
|
|
val accountRef : TootAccountRef
|
2018-07-06 17:22:22 +02:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val account : TootAccount
|
2018-08-18 12:58:14 +02:00
|
|
|
|
get() = TootAccountMap.find(accountRef.mapId)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
//The number of reblogs for the status
|
2018-01-10 16:47:35 +01:00
|
|
|
|
var reblogs_count : Long? = null // アプリから変更する。検索サービスでは提供されない(null)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
//The number of favourites for the status
|
2018-01-10 16:47:35 +01:00
|
|
|
|
var favourites_count : Long? = null // アプリから変更する。検索サービスでは提供されない(null)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// Whether the authenticated user has reblogged the status
|
|
|
|
|
var reblogged : Boolean = false // アプリから変更する
|
|
|
|
|
|
|
|
|
|
// Whether the authenticated user has favourited the status
|
|
|
|
|
var favourited : Boolean = false // アプリから変更する
|
|
|
|
|
|
2019-11-14 01:51:17 +01:00
|
|
|
|
// Whether the authenticated user has bookmarked the status
|
|
|
|
|
var bookmarked : Boolean = false // アプリから変更する
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
// Whether the authenticated user has muted the conversation this status from
|
|
|
|
|
var muted : Boolean = false // アプリから変更する
|
|
|
|
|
|
|
|
|
|
// 固定されたトゥート
|
|
|
|
|
var pinned : Boolean = false // アプリから変更する
|
|
|
|
|
|
|
|
|
|
//Whether media attachments should be hidden by default
|
|
|
|
|
val sensitive : Boolean
|
|
|
|
|
|
|
|
|
|
// The detected language for the status, if detected
|
2019-12-13 15:48:38 +01:00
|
|
|
|
val language : String?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
//If not empty, warning text that should be displayed before the actual content
|
2018-11-30 11:54:32 +01:00
|
|
|
|
// アプリ内部では空文字列はCWなしとして扱う
|
|
|
|
|
// マストドンは「null:CWなし」「空じゃない文字列:CWあり」の2種類
|
|
|
|
|
// Pleromaは「空文字列:CWなし」「空じゃない文字列:CWあり」の2種類
|
|
|
|
|
// Misskeyは「CWなし」「空欄CW」「CWあり」の3通り。空欄CWはパース時に書き換えてしまう
|
|
|
|
|
// Misskeyで投稿が削除された時に変更されるため、val変数にできない
|
2018-12-26 18:08:01 +01:00
|
|
|
|
var spoiler_text : String = ""
|
2018-11-03 15:21:42 +01:00
|
|
|
|
var decoded_spoiler_text : Spannable
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// Body of the status; this will contain HTML (remote HTML already sanitized)
|
2018-11-03 15:21:42 +01:00
|
|
|
|
var content : String?
|
|
|
|
|
var decoded_content : Spannable
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
//Application from which the status was posted
|
|
|
|
|
val application : TootApplication?
|
|
|
|
|
|
2018-01-11 10:31:25 +01:00
|
|
|
|
val custom_emojis : HashMap<String, CustomEmoji>?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-01-11 10:31:25 +01:00
|
|
|
|
val profile_emojis : HashMap<String, NicoProfileEmoji>?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// The time the status was created
|
2018-01-21 13:46:36 +01:00
|
|
|
|
private val created_at : String?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// null or the ID of the status it replies to
|
2018-08-18 12:58:14 +02:00
|
|
|
|
val in_reply_to_id : EntityId?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// null or the ID of the account it replies to
|
2018-11-16 15:44:23 +01:00
|
|
|
|
val in_reply_to_account_id : EntityId?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// null or the reblogged Status
|
|
|
|
|
val reblog : TootStatus?
|
|
|
|
|
|
|
|
|
|
//One of: public, unlisted, private, direct
|
2018-08-20 19:37:42 +02:00
|
|
|
|
val visibility : TootVisibility
|
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
private val misskeyVisibleIds : ArrayList<String>?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// An array of Attachments
|
2018-01-10 16:47:35 +01:00
|
|
|
|
val media_attachments : ArrayList<TootAttachmentLike>?
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// An array of Mentions
|
2018-01-10 16:47:35 +01:00
|
|
|
|
var mentions : ArrayList<TootMention>? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
//An array of Tags
|
2018-01-10 16:47:35 +01:00
|
|
|
|
var tags : ArrayList<TootTag>? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// public Spannable decoded_tags;
|
|
|
|
|
var decoded_mentions : Spannable = EMPTY_SPANNABLE
|
|
|
|
|
|
|
|
|
|
var conversation_main : Boolean = false
|
|
|
|
|
|
2019-04-14 05:41:29 +02:00
|
|
|
|
var enquete : TootPolls? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-08-16 10:02:55 +02:00
|
|
|
|
//
|
2018-08-21 01:20:39 +02:00
|
|
|
|
var replies_count : Long? = null
|
|
|
|
|
|
|
|
|
|
var viaMobile : Boolean = false
|
2018-08-16 10:02:55 +02:00
|
|
|
|
|
2020-01-07 09:31:05 +01:00
|
|
|
|
var reactionCounts : LinkedHashMap<String, Int>? = null
|
2018-11-03 17:14:14 +01:00
|
|
|
|
var myReaction : String? = null
|
2018-08-21 03:53:52 +02:00
|
|
|
|
|
2018-08-21 12:19:02 +02:00
|
|
|
|
var reply : TootStatus?
|
|
|
|
|
|
2018-11-03 17:14:14 +01:00
|
|
|
|
val serviceType : ServiceType
|
2018-08-23 05:46:14 +02:00
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
private val deletedAt : String?
|
2018-08-23 05:46:14 +02:00
|
|
|
|
val time_deleted_at : Long
|
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
private var localOnly : Boolean = false
|
2018-11-16 00:43:10 +01:00
|
|
|
|
|
2019-12-08 16:46:10 +01:00
|
|
|
|
var myRenoteId : EntityId? = null
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
// 以下はentityから取得したデータではなく、アプリ内部で使う
|
|
|
|
|
|
|
|
|
|
class AutoCW(
|
|
|
|
|
var refActivity : WeakReference<Any>? = null,
|
|
|
|
|
var cell_width : Int = 0,
|
|
|
|
|
var decoded_spoiler_text : Spannable? = null,
|
|
|
|
|
var originalLineCount : Int = 0
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// アプリ内部で使うワークエリア
|
|
|
|
|
var auto_cw : AutoCW? = null
|
|
|
|
|
|
|
|
|
|
// 会話の流れビューで後から追加する
|
|
|
|
|
var card : TootCard? = null
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
var highlightSound : HighlightWord? = null
|
|
|
|
|
var highlightSpeech : HighlightWord? = null
|
|
|
|
|
var highlightAny : HighlightWord? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
val time_created_at : Long
|
|
|
|
|
|
2018-10-28 14:08:10 +01:00
|
|
|
|
var conversationSummary : TootConversationSummary? = null
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
this.json = src
|
2018-08-23 05:46:14 +02:00
|
|
|
|
this.serviceType = parser.serviceType
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
if(parser.serviceType == ServiceType.MISSKEY) {
|
2019-08-30 05:05:19 +02:00
|
|
|
|
val instance = parser.accessHost
|
2018-08-16 21:58:30 +02:00
|
|
|
|
val misskeyId = src.parseString("id")
|
2019-08-30 05:05:19 +02:00
|
|
|
|
this.host_access = parser.accessHost
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2018-08-30 14:33:22 +02:00
|
|
|
|
val uri = src.parseString("uri")
|
2018-11-03 17:14:14 +01:00
|
|
|
|
if(uri != null) {
|
2018-12-02 09:13:14 +01:00
|
|
|
|
// リモート投稿には uriが含まれる
|
2018-08-30 14:33:22 +02:00
|
|
|
|
this.uri = uri
|
|
|
|
|
this.url = uri
|
2018-11-03 17:14:14 +01:00
|
|
|
|
} else {
|
2018-12-02 09:13:14 +01:00
|
|
|
|
|
2018-08-30 14:33:22 +02:00
|
|
|
|
this.uri = "https://$instance/notes/$misskeyId"
|
|
|
|
|
this.url = "https://$instance/notes/$misskeyId"
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.created_at = src.parseString("createdAt")
|
|
|
|
|
this.time_created_at = parseTime(this.created_at)
|
2018-12-02 09:13:14 +01:00
|
|
|
|
this.id = EntityId.mayDefault(misskeyId)
|
2018-08-18 12:58:14 +02:00
|
|
|
|
|
|
|
|
|
// ページネーションには日時を使う
|
2019-04-28 13:52:38 +02:00
|
|
|
|
this._orderId = EntityId(time_created_at.toString(), fromTime = true)
|
2019-03-06 10:49:02 +01:00
|
|
|
|
|
2018-08-24 16:38:32 +02:00
|
|
|
|
// お気に入りカラムなどではパース直後に変更することがある
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
|
|
|
|
// 絵文字マップはすぐ後で使うので、最初の方で読んでおく
|
2018-11-30 11:54:32 +01:00
|
|
|
|
this.custom_emojis =
|
2020-01-07 09:03:32 +01:00
|
|
|
|
parseMapOrNull(CustomEmoji.decodeMisskey, src.parseJsonArray("emojis"), log)
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.profile_emojis = null
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val who = parser.account(src.parseJsonObject("user"))
|
2018-08-16 21:58:30 +02:00
|
|
|
|
?: throw RuntimeException("missing account")
|
|
|
|
|
|
|
|
|
|
this.accountRef = TootAccountRef(parser, who)
|
|
|
|
|
|
2018-08-20 19:37:42 +02:00
|
|
|
|
this.reblogs_count = src.parseLong("renoteCount") ?: 0L
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.favourites_count = 0L
|
2018-08-20 19:37:42 +02:00
|
|
|
|
this.replies_count = src.parseLong("repliesCount") ?: 0L
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
|
|
|
|
this.reblogged = false
|
2018-10-13 01:06:49 +02:00
|
|
|
|
this.favourited = src.optBoolean("isFavorited")
|
2018-08-21 01:20:39 +02:00
|
|
|
|
|
2018-11-16 00:43:10 +01:00
|
|
|
|
this.localOnly = src.optBoolean("localOnly")
|
2018-11-30 11:54:32 +01:00
|
|
|
|
this.visibility = TootVisibility.parseMisskey(
|
|
|
|
|
src.parseString("visibility"),
|
|
|
|
|
localOnly
|
|
|
|
|
) ?: TootVisibility.Public
|
2018-08-20 19:37:42 +02:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.misskeyVisibleIds = parseStringArray(src.parseJsonArray("visibleUserIds"))
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
this.media_attachments =
|
2019-04-25 04:06:41 +02:00
|
|
|
|
parseListOrNull(
|
|
|
|
|
::TootAttachment,
|
|
|
|
|
parser,
|
2020-01-07 09:03:32 +01:00
|
|
|
|
src.parseJsonArray("files") ?: src.parseJsonArray("media") // v11,v10
|
2019-04-25 04:06:41 +02:00
|
|
|
|
)
|
2018-08-18 12:58:14 +02:00
|
|
|
|
|
|
|
|
|
// Misskeyは画像毎にNSFWフラグがある。どれか1枚でもNSFWならトゥート全体がNSFWということにする
|
|
|
|
|
var bv = src.optBoolean("sensitive")
|
|
|
|
|
media_attachments?.forEach {
|
2018-08-21 01:20:39 +02:00
|
|
|
|
if((it as? TootAttachment)?.isSensitive == true) {
|
|
|
|
|
bv = true
|
2018-08-18 12:58:14 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.sensitive = bv
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.reply = parser.status(src.parseJsonObject("reply"))
|
2018-08-23 06:08:03 +02:00
|
|
|
|
this.in_reply_to_id = EntityId.mayNull(src.parseString("replyId"))
|
|
|
|
|
this.in_reply_to_account_id = reply?.account?.id
|
2018-08-22 07:11:25 +02:00
|
|
|
|
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.pinned = parser.pinned
|
|
|
|
|
this.muted = false
|
|
|
|
|
this.language = null
|
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
// "mentionedRemoteUsers" -> "[{"uri":"https:\/\/mastodon.juggler.jp\/users\/tateisu","username":"tateisu","host":"mastodon.juggler.jp"}]"
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.tags = parseMisskeyTags(src.parseJsonArray("tags"))
|
2018-08-21 01:20:39 +02:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.application = parseItem(::TootApplication, parser, src.parseJsonObject("app"), log)
|
2018-08-21 01:20:39 +02:00
|
|
|
|
|
|
|
|
|
this.viaMobile = src.optBoolean("viaMobile")
|
|
|
|
|
|
2018-08-16 21:58:30 +02:00
|
|
|
|
// this.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
|
|
|
|
|
|
|
|
|
|
// content
|
|
|
|
|
this.content = src.parseString("text")
|
|
|
|
|
|
|
|
|
|
var options = DecodeOptions(
|
|
|
|
|
parser.context,
|
|
|
|
|
parser.linkHelper,
|
|
|
|
|
short = true,
|
|
|
|
|
decodeEmoji = true,
|
|
|
|
|
emojiMapCustom = custom_emojis,
|
|
|
|
|
emojiMapProfile = profile_emojis,
|
|
|
|
|
attachmentList = media_attachments,
|
2019-09-12 13:59:33 +02:00
|
|
|
|
highlightTrie = parser.highlightTrie,
|
|
|
|
|
mentions = null // MisskeyはMFMをパースし終わるまでメンションが分からない
|
2018-08-16 21:58:30 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
this.decoded_content = options.decodeHTML(content)
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(this.highlightSound == null) this.highlightSound = options.highlightSound
|
|
|
|
|
if(this.highlightSpeech == null) this.highlightSpeech = options.highlightSpeech
|
|
|
|
|
if(this.highlightAny == null) this.highlightAny = options.highlightAny
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2018-08-22 07:11:25 +02:00
|
|
|
|
// Markdownのデコード結果からmentionsを読むのだった
|
2019-10-22 06:39:00 +02:00
|
|
|
|
val mentions1 =
|
|
|
|
|
(decoded_content as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions
|
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
val sv = src.parseString("cw")?.cleanCW()
|
2018-12-26 18:08:01 +01:00
|
|
|
|
this.spoiler_text = when {
|
2018-11-30 11:54:32 +01:00
|
|
|
|
sv == null -> "" // CWなし
|
|
|
|
|
sv.isBlank() -> parser.context.getString(R.string.blank_cw)
|
2018-12-26 18:08:01 +01:00
|
|
|
|
else -> sv
|
2018-11-30 11:54:32 +01:00
|
|
|
|
}
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2019-09-12 13:59:33 +02:00
|
|
|
|
// ハイライト検出のためにDecodeOptionsを作り直す?
|
2018-08-16 21:58:30 +02:00
|
|
|
|
options = DecodeOptions(
|
|
|
|
|
parser.context,
|
2018-12-02 10:35:04 +01:00
|
|
|
|
parser.linkHelper,
|
|
|
|
|
short = true,
|
|
|
|
|
decodeEmoji = true,
|
2018-08-16 21:58:30 +02:00
|
|
|
|
emojiMapCustom = custom_emojis,
|
|
|
|
|
emojiMapProfile = profile_emojis,
|
2018-12-02 10:35:04 +01:00
|
|
|
|
attachmentList = media_attachments,
|
2019-09-12 13:59:33 +02:00
|
|
|
|
highlightTrie = parser.highlightTrie,
|
|
|
|
|
mentions = null // MisskeyはMFMをパースし終わるまでメンションが分からない
|
2018-08-16 21:58:30 +02:00
|
|
|
|
)
|
2018-12-02 10:35:04 +01:00
|
|
|
|
this.decoded_spoiler_text = options.decodeHTML(spoiler_text)
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(this.highlightSound == null) this.highlightSound = options.highlightSound
|
|
|
|
|
if(this.highlightSpeech == null) this.highlightSpeech = options.highlightSpeech
|
|
|
|
|
if(this.highlightAny == null) this.highlightAny = options.highlightAny
|
2019-09-12 13:59:33 +02:00
|
|
|
|
|
2019-10-22 06:39:00 +02:00
|
|
|
|
val mentions2 =
|
|
|
|
|
(decoded_spoiler_text as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions
|
|
|
|
|
|
|
|
|
|
this.mentions = mergeMentions(mentions1, mentions2)
|
2019-09-12 13:59:33 +02:00
|
|
|
|
this.decoded_mentions = HTMLDecoder.decodeMentions(
|
|
|
|
|
parser.linkHelper,
|
|
|
|
|
this.mentions,
|
|
|
|
|
this
|
|
|
|
|
) ?: EMPTY_SPANNABLE
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
// contentを読んだ後にアンケートのデコード
|
2019-04-14 05:41:29 +02:00
|
|
|
|
this.enquete = TootPolls.parse(
|
2018-08-21 01:20:39 +02:00
|
|
|
|
parser,
|
|
|
|
|
this,
|
|
|
|
|
media_attachments,
|
2020-01-07 09:03:32 +01:00
|
|
|
|
src.parseJsonObject("poll"),
|
2019-04-14 05:41:29 +02:00
|
|
|
|
TootPollsType.Misskey
|
2018-08-21 01:20:39 +02:00
|
|
|
|
)
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2019-04-28 13:52:38 +02:00
|
|
|
|
this.reactionCounts = parseReactionCounts(
|
2020-01-07 09:03:32 +01:00
|
|
|
|
src.parseJsonObject("reactions") ?: src.parseJsonObject("reactionCounts")
|
2019-04-28 13:52:38 +02:00
|
|
|
|
)
|
2018-08-23 06:32:55 +02:00
|
|
|
|
this.myReaction = src.parseString("myReaction")
|
2019-12-08 16:46:10 +01:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val reblog = parser.status(src.parseJsonObject("renote"))
|
2019-12-08 16:46:10 +01:00
|
|
|
|
this.reblog = reblog
|
2020-01-07 09:03:32 +01:00
|
|
|
|
|
2019-12-08 16:46:10 +01:00
|
|
|
|
// めいめいフォークでは myRenoteIdというものがあるらしい
|
|
|
|
|
// https://github.com/mei23/misskey/blob/mei-m544/src/models/note.ts#L384-L394
|
|
|
|
|
// 直近の一つのrenoteのIdを得られるらしい。
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.myRenoteId = EntityId.mayNull(src.parseString("myRenoteId"))
|
2019-12-08 16:46:10 +01:00
|
|
|
|
if(myRenoteId != null) reblogged = true
|
|
|
|
|
|
|
|
|
|
// しかしTLにRenoteが露出してるならそのIDを使う方が賢明であろう
|
|
|
|
|
// 外側ステータスが自分なら、内側ステータスのmyRenoteIdを設定する
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(reblog != null && parser.linkHelper.cast<SavedAccount>()?.isMe(account) == true) {
|
2019-12-08 16:46:10 +01:00
|
|
|
|
reblog.myRenoteId = id
|
|
|
|
|
reblog.reblogged = true
|
|
|
|
|
}
|
2018-08-21 01:20:39 +02:00
|
|
|
|
|
2018-08-23 05:46:14 +02:00
|
|
|
|
this.deletedAt = src.parseString("deletedAt")
|
|
|
|
|
this.time_deleted_at = parseTime(deletedAt)
|
2018-11-12 18:19:57 +01:00
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
if(card == null) {
|
2018-11-12 18:19:57 +01:00
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
if(reblog != null && hasAnyContent()) {
|
2018-11-12 18:19:57 +01:00
|
|
|
|
// 引用Renoteにプレビューカードをでっちあげる
|
|
|
|
|
card = TootCard(parser, reblog)
|
2018-11-30 11:54:32 +01:00
|
|
|
|
} else if(reply != null) {
|
2018-11-12 18:19:57 +01:00
|
|
|
|
// 返信にプレビューカードをでっちあげる
|
2018-11-30 11:54:32 +01:00
|
|
|
|
card = TootCard(parser, reply !!)
|
2018-11-12 18:19:57 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
} else {
|
2018-08-20 19:37:42 +02:00
|
|
|
|
misskeyVisibleIds = null
|
2018-08-21 12:19:02 +02:00
|
|
|
|
reply = null
|
2018-08-23 05:46:14 +02:00
|
|
|
|
deletedAt = null
|
2018-11-03 17:14:14 +01:00
|
|
|
|
time_deleted_at = 0L
|
|
|
|
|
|
2018-12-06 09:31:50 +01:00
|
|
|
|
this.url = src.parseString("url") // ブースト等では頻繁にnullになる
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.created_at = src.parseString("created_at")
|
|
|
|
|
|
|
|
|
|
// 絵文字マップはすぐ後で使うので、最初の方で読んでおく
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.custom_emojis =
|
|
|
|
|
parseMapOrNull(CustomEmoji.decode, src.parseJsonArray("emojis"), log)
|
2019-04-28 13:52:38 +02:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.profile_emojis = when(val o = src.get("profile_emojis")) {
|
|
|
|
|
is JsonArray -> parseMapOrNull(::NicoProfileEmoji, o, log)
|
|
|
|
|
is JsonObject -> parseProfileEmoji2(::NicoProfileEmoji, o, log)
|
2019-04-28 13:52:38 +02:00
|
|
|
|
else -> null
|
|
|
|
|
}
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val who = parser.account(src.parseJsonObject("account"))
|
2018-08-16 21:58:30 +02:00
|
|
|
|
?: throw RuntimeException("missing account")
|
|
|
|
|
|
|
|
|
|
this.accountRef = TootAccountRef(parser, who)
|
|
|
|
|
|
|
|
|
|
this.reblogs_count = src.parseLong("reblogs_count")
|
|
|
|
|
this.favourites_count = src.parseLong("favourites_count")
|
|
|
|
|
this.replies_count = src.parseLong("replies_count")
|
|
|
|
|
|
|
|
|
|
when(parser.serviceType) {
|
|
|
|
|
ServiceType.MASTODON -> {
|
2019-08-30 05:05:19 +02:00
|
|
|
|
this.host_access = parser.accessHost
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
|
this.id = EntityId.mayDefault(src.parseString("id"))
|
2018-12-06 09:31:50 +01:00
|
|
|
|
this.uri = src.parseString("uri") ?: error("missing uri")
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
|
|
|
|
this.reblogged = src.optBoolean("reblogged")
|
|
|
|
|
this.favourited = src.optBoolean("favourited")
|
2019-11-14 01:51:17 +01:00
|
|
|
|
this.bookmarked = src.optBoolean("bookmarked")
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
|
|
|
|
this.time_created_at = parseTime(this.created_at)
|
|
|
|
|
this.media_attachments =
|
2018-08-21 01:20:39 +02:00
|
|
|
|
parseListOrNull(
|
|
|
|
|
::TootAttachment,
|
|
|
|
|
parser,
|
2020-01-07 09:03:32 +01:00
|
|
|
|
src.parseJsonArray("media_attachments"),
|
2018-08-21 01:20:39 +02:00
|
|
|
|
log
|
|
|
|
|
)
|
2019-03-06 10:49:02 +01:00
|
|
|
|
this.visibility = TootVisibility.parseMastodon(src.parseString("visibility"))
|
|
|
|
|
?: TootVisibility.Public
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.sensitive = src.optBoolean("sensitive")
|
|
|
|
|
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-08-16 21:58:30 +02:00
|
|
|
|
ServiceType.TOOTSEARCH -> {
|
|
|
|
|
this.host_access = null
|
|
|
|
|
|
|
|
|
|
// 投稿元タンスでのIDを調べる。失敗するかもしれない
|
2019-01-28 19:02:09 +01:00
|
|
|
|
// FIXME: Pleromaだとダメそうな印象
|
2018-12-06 09:31:50 +01:00
|
|
|
|
this.uri = src.parseString("uri") ?: error("missing uri")
|
2019-01-29 02:56:24 +01:00
|
|
|
|
this.id = findStatusIdFromUri(uri, url) ?: EntityId.DEFAULT
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2019-09-12 13:59:33 +02:00
|
|
|
|
this.time_created_at = parseTime(this.created_at)
|
2019-01-28 19:02:09 +01:00
|
|
|
|
this.media_attachments = parseListOrNull(
|
|
|
|
|
::TootAttachment,
|
|
|
|
|
parser,
|
2020-01-07 09:03:32 +01:00
|
|
|
|
src.parseJsonArray("media_attachments"),
|
2019-01-28 19:02:09 +01:00
|
|
|
|
log
|
|
|
|
|
)
|
2018-08-20 19:37:42 +02:00
|
|
|
|
this.visibility = TootVisibility.Public
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.sensitive = src.optBoolean("sensitive")
|
|
|
|
|
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-08-16 21:58:30 +02:00
|
|
|
|
ServiceType.MSP -> {
|
2019-08-30 05:05:19 +02:00
|
|
|
|
this.host_access = parser.accessHost
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
|
|
|
|
// MSPのデータはLTLから呼んだものなので、常に投稿元タンスでのidが得られる
|
2019-01-28 19:02:09 +01:00
|
|
|
|
this.id = EntityId.mayDefault(src.parseString("id"))
|
2018-12-06 09:31:50 +01:00
|
|
|
|
// MSPだとuriは提供されない。LTL限定なのでURL的なものを作れるはず
|
2018-12-26 18:08:01 +01:00
|
|
|
|
this.uri =
|
2019-08-30 05:05:19 +02:00
|
|
|
|
"https://${parser.accessHost}/users/${who.username}/statuses/$id"
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.time_created_at = parseTimeMSP(created_at)
|
|
|
|
|
this.media_attachments =
|
2020-01-07 09:03:32 +01:00
|
|
|
|
TootAttachmentMSP.parseList(src.parseJsonArray("media_attachments"))
|
2018-08-20 19:37:42 +02:00
|
|
|
|
this.visibility = TootVisibility.Public
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.sensitive = src.optInt("sensitive", 0) != 0
|
|
|
|
|
}
|
2018-08-18 12:58:14 +02:00
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
ServiceType.MISSKEY -> error("will not happen")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
|
|
2018-08-18 12:58:14 +02:00
|
|
|
|
this._orderId = this.id
|
2019-01-28 19:02:09 +01:00
|
|
|
|
this.in_reply_to_id = EntityId.mayNull(src.parseString("in_reply_to_id"))
|
2019-03-06 10:49:02 +01:00
|
|
|
|
this.in_reply_to_account_id =
|
|
|
|
|
EntityId.mayNull(src.parseString("in_reply_to_account_id"))
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.mentions = parseListOrNull(::TootMention, src.parseJsonArray("mentions"), log)
|
|
|
|
|
this.tags = parseListOrNull(::TootTag, src.parseJsonArray("tags"))
|
2018-08-21 01:20:39 +02:00
|
|
|
|
this.application =
|
2020-01-07 09:03:32 +01:00
|
|
|
|
parseItem(::TootApplication, parser, src.parseJsonObject("application"), log)
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.pinned = parser.pinned || src.optBoolean("pinned")
|
|
|
|
|
this.muted = src.optBoolean("muted")
|
2019-12-13 15:48:38 +01:00
|
|
|
|
this.language = src.parseString("language")?.notEmpty()
|
2018-08-16 21:58:30 +02:00
|
|
|
|
this.decoded_mentions = HTMLDecoder.decodeMentions(
|
|
|
|
|
parser.linkHelper,
|
|
|
|
|
this.mentions,
|
|
|
|
|
this
|
|
|
|
|
) ?: EMPTY_SPANNABLE
|
|
|
|
|
// this.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
|
|
|
|
|
|
|
|
|
|
// content
|
|
|
|
|
this.content = src.parseString("content")
|
|
|
|
|
|
|
|
|
|
var options = DecodeOptions(
|
|
|
|
|
parser.context,
|
|
|
|
|
parser.linkHelper,
|
|
|
|
|
short = true,
|
|
|
|
|
decodeEmoji = true,
|
|
|
|
|
emojiMapCustom = custom_emojis,
|
|
|
|
|
emojiMapProfile = profile_emojis,
|
|
|
|
|
attachmentList = media_attachments,
|
2019-09-12 13:59:33 +02:00
|
|
|
|
highlightTrie = parser.highlightTrie,
|
|
|
|
|
mentions = mentions
|
2018-08-16 21:58:30 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
this.decoded_content = options.decodeHTML(content)
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(this.highlightSound == null) this.highlightSound = options.highlightSound
|
|
|
|
|
if(this.highlightSpeech == null) this.highlightSpeech = options.highlightSpeech
|
|
|
|
|
if(this.highlightAny == null) this.highlightAny = options.highlightAny
|
2018-01-10 16:47:35 +01:00
|
|
|
|
|
2019-03-06 10:49:02 +01:00
|
|
|
|
var sv = (src.parseString("spoiler_text") ?: "").cleanCW()
|
2018-12-26 18:08:01 +01:00
|
|
|
|
this.spoiler_text = when {
|
|
|
|
|
sv.isEmpty() -> "" // CWなし
|
|
|
|
|
sv.isBlank() -> parser.context.getString(R.string.blank_cw)
|
|
|
|
|
else -> sv
|
2018-12-01 02:25:58 +01:00
|
|
|
|
}
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2019-09-12 13:59:33 +02:00
|
|
|
|
// ハイライト検出のためにDecodeOptionsを作り直す?
|
2018-08-16 21:58:30 +02:00
|
|
|
|
options = DecodeOptions(
|
|
|
|
|
parser.context,
|
|
|
|
|
emojiMapCustom = custom_emojis,
|
|
|
|
|
emojiMapProfile = profile_emojis,
|
2019-09-12 13:59:33 +02:00
|
|
|
|
highlightTrie = parser.highlightTrie,
|
|
|
|
|
mentions = mentions
|
2018-08-16 21:58:30 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
this.decoded_spoiler_text = options.decodeEmoji(spoiler_text)
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(this.highlightSound == null) this.highlightSound = options.highlightSound
|
|
|
|
|
if(this.highlightSpeech == null) this.highlightSpeech = options.highlightSpeech
|
|
|
|
|
if(this.highlightAny == null) this.highlightAny = options.highlightAny
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2019-03-06 10:49:02 +01:00
|
|
|
|
this.enquete = try {
|
|
|
|
|
sv = src.parseString("enquete") ?: ""
|
|
|
|
|
if(sv.isNotEmpty()) {
|
2019-04-14 05:41:29 +02:00
|
|
|
|
TootPolls.parse(
|
2019-03-06 10:49:02 +01:00
|
|
|
|
parser,
|
|
|
|
|
this,
|
|
|
|
|
media_attachments,
|
2020-01-08 03:57:10 +01:00
|
|
|
|
sv.decodeJsonObject(),
|
2019-04-14 05:41:29 +02:00
|
|
|
|
TootPollsType.FriendsNico
|
2019-03-06 10:49:02 +01:00
|
|
|
|
)
|
|
|
|
|
} else {
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val ov = src.parseJsonObject("poll")
|
2019-04-14 05:41:29 +02:00
|
|
|
|
TootPolls.parse(
|
2019-03-06 10:49:02 +01:00
|
|
|
|
parser,
|
|
|
|
|
this,
|
|
|
|
|
media_attachments,
|
|
|
|
|
ov,
|
2019-04-14 05:41:29 +02:00
|
|
|
|
TootPollsType.Mastodon
|
2019-03-06 10:49:02 +01:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
|
log.trace(ex)
|
|
|
|
|
null
|
|
|
|
|
}
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
|
|
|
|
// Pinned TL を取得した時にreblogが登場することはないので、reblogについてpinned 状態を気にする必要はない
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.reblog = parser.status(src.parseJsonObject("reblog"))
|
2018-08-16 21:58:30 +02:00
|
|
|
|
|
2018-10-28 14:08:10 +01:00
|
|
|
|
// 2.6.0からステータスにもカード情報が含まれる
|
2020-01-07 09:03:32 +01:00
|
|
|
|
this.card = parseItem(::TootCard, src.parseJsonObject("card"))
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-08-16 21:58:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 13:59:33 +02:00
|
|
|
|
private fun mergeMentions(
|
|
|
|
|
mentions1 : java.util.ArrayList<TootMention>?,
|
|
|
|
|
mentions2 : java.util.ArrayList<TootMention>?
|
|
|
|
|
) : java.util.ArrayList<TootMention>? {
|
2019-10-22 06:39:00 +02:00
|
|
|
|
val size = (mentions1?.size ?: 0) + (mentions2?.size ?: 0)
|
|
|
|
|
if(size == 0) return null
|
2019-09-12 13:59:33 +02:00
|
|
|
|
val dst = ArrayList<TootMention>(size)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(mentions1 != null) dst.addAll(mentions1)
|
|
|
|
|
if(mentions2 != null) dst.addAll(mentions2)
|
2019-09-12 13:59:33 +02:00
|
|
|
|
return dst
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
|
// ユーティリティ
|
|
|
|
|
|
|
|
|
|
val hostAccessOrOriginal : String
|
|
|
|
|
get() = validHost(host_access) ?: validHost(host_original) ?: "(null)"
|
|
|
|
|
|
|
|
|
|
val busyKey : String
|
2018-08-20 02:07:55 +02:00
|
|
|
|
get() = "$hostAccessOrOriginal:$id"
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
|
fun checkMuted() : Boolean {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// app mute
|
2019-09-12 13:59:33 +02:00
|
|
|
|
val muted_app = muted_app
|
2018-01-10 16:47:35 +01:00
|
|
|
|
if(muted_app != null) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val name = application?.name
|
|
|
|
|
if(name != null && muted_app.contains(name)) return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// word mute
|
2019-09-12 13:59:33 +02:00
|
|
|
|
val muted_word = muted_word
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(muted_word != null) {
|
|
|
|
|
if(muted_word.matchShort(decoded_content)) return true
|
|
|
|
|
if(muted_word.matchShort(decoded_spoiler_text)) return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reblog
|
2018-09-17 20:06:15 +02:00
|
|
|
|
return true == reblog?.checkMuted()
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun hasMedia() : Boolean {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
return (media_attachments?.size ?: 0) > 0
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun canPin(access_info : SavedAccount) : Boolean {
|
2018-08-20 19:37:42 +02:00
|
|
|
|
return reblog == null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
&& access_info.isMe(account)
|
2018-08-21 01:20:39 +02:00
|
|
|
|
&& visibility.canPin(access_info.isMisskey)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-06 17:22:22 +02:00
|
|
|
|
// 内部で使う
|
2020-01-07 09:03:32 +01:00
|
|
|
|
private var _filteredWord : String? = null
|
2019-12-06 07:45:04 +01:00
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val filteredWord : String?
|
2019-12-06 07:45:04 +01:00
|
|
|
|
get() = _filteredWord ?: reblog?._filteredWord
|
2018-07-06 17:22:22 +02:00
|
|
|
|
|
2018-08-21 01:20:39 +02:00
|
|
|
|
val filtered : Boolean
|
2019-12-06 07:45:04 +01:00
|
|
|
|
get() = filteredWord != null
|
2018-08-24 07:04:55 +02:00
|
|
|
|
|
2018-11-03 17:14:14 +01:00
|
|
|
|
private fun hasReceipt(access_info : SavedAccount) : TootVisibility {
|
2018-08-24 10:03:20 +02:00
|
|
|
|
val fullAcctMe = access_info.getFullAcct(account)
|
|
|
|
|
|
|
|
|
|
val reply_account = reply?.account
|
2018-11-03 17:14:14 +01:00
|
|
|
|
if(reply_account != null && fullAcctMe != access_info.getFullAcct(reply_account)) {
|
2018-08-24 07:04:55 +02:00
|
|
|
|
return TootVisibility.DirectSpecified
|
|
|
|
|
}
|
2018-08-24 10:03:20 +02:00
|
|
|
|
|
2018-08-24 07:04:55 +02:00
|
|
|
|
val in_reply_to_account_id = this.in_reply_to_account_id
|
2018-11-03 17:14:14 +01:00
|
|
|
|
if(in_reply_to_account_id != null && in_reply_to_account_id != account.id) {
|
2018-08-24 07:04:55 +02:00
|
|
|
|
return TootVisibility.DirectSpecified
|
|
|
|
|
}
|
2018-08-24 10:03:20 +02:00
|
|
|
|
|
2018-11-03 17:14:14 +01:00
|
|
|
|
mentions?.forEach {
|
2018-08-24 10:03:20 +02:00
|
|
|
|
if(fullAcctMe != access_info.getFullAcct(it.acct))
|
2018-08-24 07:04:55 +02:00
|
|
|
|
return@hasReceipt TootVisibility.DirectSpecified
|
|
|
|
|
}
|
2018-08-24 10:03:20 +02:00
|
|
|
|
|
2018-08-24 07:04:55 +02:00
|
|
|
|
return TootVisibility.DirectPrivate
|
|
|
|
|
}
|
2018-11-03 17:14:14 +01:00
|
|
|
|
|
|
|
|
|
fun getBackgroundColorType(access_info : SavedAccount) =
|
|
|
|
|
when(visibility) {
|
2018-08-24 07:04:55 +02:00
|
|
|
|
TootVisibility.DirectPrivate,
|
|
|
|
|
TootVisibility.DirectSpecified -> hasReceipt(access_info)
|
2018-11-03 17:14:14 +01:00
|
|
|
|
else -> visibility
|
2018-08-24 07:04:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-22 06:39:00 +02:00
|
|
|
|
fun updateKeywordFilteredFlag(
|
|
|
|
|
accessInfo : SavedAccount,
|
|
|
|
|
trees : FilterTrees?,
|
|
|
|
|
checkIrreversible : Boolean = false
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
trees ?: return
|
|
|
|
|
|
|
|
|
|
// status from me or boosted by me is not filtered.
|
|
|
|
|
if(accessInfo.isMe(account)) {
|
2019-12-06 07:45:04 +01:00
|
|
|
|
_filteredWord = null
|
2019-10-22 06:39:00 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 07:45:04 +01:00
|
|
|
|
_filteredWord =
|
2019-10-22 06:39:00 +02:00
|
|
|
|
isKeywordFilteredSub(if(checkIrreversible) trees.treeAll else trees.treeReversible)
|
2019-12-06 07:45:04 +01:00
|
|
|
|
?.joinToString(", ")
|
2019-10-22 06:39:00 +02:00
|
|
|
|
|
|
|
|
|
reblog?.updateKeywordFilteredFlag(accessInfo, trees, checkIrreversible)
|
2018-07-06 17:22:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-22 06:39:00 +02:00
|
|
|
|
fun isKeywordFiltered(accessInfo : SavedAccount, tree : WordTrieTree?) : Boolean {
|
|
|
|
|
tree ?: return false
|
|
|
|
|
|
|
|
|
|
// status from me or boosted by me is not filtered.
|
|
|
|
|
if(accessInfo.isMe(account)) return false
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(isKeywordFilteredSub(tree) != null) return true
|
|
|
|
|
if(reblog?.isKeywordFilteredSub(tree) != null) return true
|
2019-10-22 06:39:00 +02:00
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 07:45:04 +01:00
|
|
|
|
private fun isKeywordFilteredSub(tree : WordTrieTree) : ArrayList<String>? {
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
var list : ArrayList<String>? = null
|
|
|
|
|
|
|
|
|
|
fun check(t : CharSequence?) {
|
|
|
|
|
if(t?.isEmpty() != false) return
|
2019-12-06 07:45:04 +01:00
|
|
|
|
val matches = tree.matchList(t) ?: return
|
|
|
|
|
var dst = list
|
2020-01-07 09:03:32 +01:00
|
|
|
|
if(dst == null) {
|
2019-12-06 07:45:04 +01:00
|
|
|
|
dst = ArrayList()
|
|
|
|
|
list = dst
|
|
|
|
|
}
|
2020-01-07 09:03:32 +01:00
|
|
|
|
for(m in matches)
|
|
|
|
|
dst.add(m.word)
|
2019-12-06 07:45:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
check(decoded_spoiler_text)
|
2019-12-06 07:45:04 +01:00
|
|
|
|
check(decoded_content)
|
2020-01-07 09:03:32 +01:00
|
|
|
|
|
2019-12-06 07:45:04 +01:00
|
|
|
|
return list
|
2018-07-06 17:22:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-03 17:14:14 +01:00
|
|
|
|
fun hasAnyContent() = when {
|
2018-08-23 05:46:14 +02:00
|
|
|
|
reblog == null -> true // reblog以外はオリジナルコンテンツがあると見なす
|
|
|
|
|
serviceType != ServiceType.MISSKEY -> false // misskey以外のreblogはコンテンツがないと見なす
|
2018-11-03 17:14:14 +01:00
|
|
|
|
content?.isNotEmpty() == true
|
2018-11-30 11:54:32 +01:00
|
|
|
|
|| spoiler_text.isNotEmpty()
|
2018-11-03 17:14:14 +01:00
|
|
|
|
|| media_attachments?.isNotEmpty() == true
|
2018-08-23 05:46:14 +02:00
|
|
|
|
|| enquete != null -> true
|
2018-11-03 17:14:14 +01:00
|
|
|
|
else -> false
|
2018-08-23 05:46:14 +02:00
|
|
|
|
}
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2018-11-03 15:21:42 +01:00
|
|
|
|
// return true if updated
|
2018-11-30 11:54:32 +01:00
|
|
|
|
fun increaseReaction(reaction : String?, byMe : Boolean, caller : String) : Boolean {
|
2018-11-03 17:14:14 +01:00
|
|
|
|
reaction ?: return false
|
2018-11-30 11:54:32 +01:00
|
|
|
|
|
2018-11-03 15:21:42 +01:00
|
|
|
|
MisskeyReaction.shortcodeMap[reaction] ?: return false
|
2018-11-03 15:42:24 +01:00
|
|
|
|
|
2018-11-03 17:14:14 +01:00
|
|
|
|
synchronized(this) {
|
|
|
|
|
|
|
|
|
|
if(byMe) {
|
|
|
|
|
if(myReaction != null) {
|
|
|
|
|
// 自分でリアクションしたらUIで更新した後にストリーミングイベントが届くことがある
|
|
|
|
|
return false
|
|
|
|
|
}
|
2018-11-03 15:42:24 +01:00
|
|
|
|
myReaction = reaction
|
|
|
|
|
}
|
2018-11-03 17:14:14 +01:00
|
|
|
|
log.d("increaseReaction noteId=$id byMe=$byMe caller=$caller")
|
|
|
|
|
|
|
|
|
|
// カウントを増やす
|
|
|
|
|
var map = this.reactionCounts
|
|
|
|
|
if(map == null) {
|
2020-01-07 09:31:05 +01:00
|
|
|
|
map = LinkedHashMap()
|
2018-11-03 17:14:14 +01:00
|
|
|
|
this.reactionCounts = map
|
|
|
|
|
}
|
|
|
|
|
map[reaction] = (map[reaction] ?: 0) + 1
|
|
|
|
|
|
|
|
|
|
return true
|
2018-11-03 15:42:24 +01:00
|
|
|
|
}
|
2018-11-03 15:21:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 19:05:14 +01:00
|
|
|
|
fun decreaseReaction(reaction : String?, byMe : Boolean, caller : String) : Boolean {
|
2018-12-29 17:38:52 +01:00
|
|
|
|
reaction ?: return false
|
|
|
|
|
|
|
|
|
|
MisskeyReaction.shortcodeMap[reaction] ?: return false
|
2019-01-18 19:05:14 +01:00
|
|
|
|
|
2018-12-29 17:38:52 +01:00
|
|
|
|
synchronized(this) {
|
|
|
|
|
|
2019-01-18 19:05:14 +01:00
|
|
|
|
if(byMe) {
|
|
|
|
|
if(this.myReaction != reaction) {
|
2018-12-29 17:38:52 +01:00
|
|
|
|
// 自分でリアクションしたらUIで更新した後にストリーミングイベントが届くことがある
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
myReaction = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.d("decreaseReaction noteId=$id byMe=$byMe caller=$caller")
|
2019-01-18 19:05:14 +01:00
|
|
|
|
|
2018-12-29 17:38:52 +01:00
|
|
|
|
// カウントを減らす
|
|
|
|
|
var map = this.reactionCounts
|
|
|
|
|
if(map == null) {
|
2020-01-07 09:31:05 +01:00
|
|
|
|
map = LinkedHashMap()
|
2018-12-29 17:38:52 +01:00
|
|
|
|
this.reactionCounts = map
|
|
|
|
|
}
|
|
|
|
|
map[reaction] = (map[reaction] ?: 1) - 1
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-03 17:14:14 +01:00
|
|
|
|
fun markDeleted(context : Context, deletedAt : Long?) : Boolean? {
|
|
|
|
|
|
2019-03-06 10:49:02 +01:00
|
|
|
|
if(Pref.bpDontRemoveDeletedToot(App1.getAppState(context).pref)) return false
|
2019-02-23 10:45:54 +01:00
|
|
|
|
|
2018-11-03 15:21:42 +01:00
|
|
|
|
var sv = if(deletedAt != null) {
|
2018-11-03 17:14:14 +01:00
|
|
|
|
context.getString(R.string.status_deleted_at, formatTime(context, deletedAt, false))
|
|
|
|
|
} else {
|
2018-11-03 15:21:42 +01:00
|
|
|
|
context.getString(R.string.status_deleted)
|
|
|
|
|
}
|
|
|
|
|
this.content = sv
|
|
|
|
|
this.decoded_content = SpannableString(sv)
|
2018-11-03 17:14:14 +01:00
|
|
|
|
|
2018-11-03 15:21:42 +01:00
|
|
|
|
sv = ""
|
|
|
|
|
this.spoiler_text = sv
|
|
|
|
|
this.decoded_spoiler_text = SpannableString(sv)
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-30 13:02:39 +02:00
|
|
|
|
class FindStatusIdFromUrlResult(
|
|
|
|
|
val statusId : EntityId?, // may null
|
2019-10-22 06:39:00 +02:00
|
|
|
|
val host : String,
|
|
|
|
|
val url : String
|
2019-09-30 13:02:39 +02:00
|
|
|
|
)
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
|
|
internal val log = LogCategory("TootStatus")
|
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
|
@Volatile
|
|
|
|
|
internal var muted_app : HashSet<String>? = null
|
|
|
|
|
|
|
|
|
|
@Volatile
|
|
|
|
|
internal var muted_word : WordTrieTree? = null
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
const val LANGUAGE_CODE_UNKNOWN = "unknown"
|
|
|
|
|
const val LANGUAGE_CODE_DEFAULT = "default"
|
2019-12-13 15:48:38 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val EMPTY_SPANNABLE = SpannableString("")
|
|
|
|
|
|
|
|
|
|
// OStatus
|
2018-01-21 13:46:36 +01:00
|
|
|
|
private val reTootUriOS = Pattern.compile(
|
2019-01-28 19:02:09 +01:00
|
|
|
|
"tag:([^,]*),[^:]*:objectId=([^:?#/\\s]+):objectType=Status",
|
2018-01-21 13:46:36 +01:00
|
|
|
|
Pattern.CASE_INSENSITIVE
|
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// ActivityPub 1
|
2018-01-21 13:46:36 +01:00
|
|
|
|
private val reTootUriAP1 =
|
2019-01-28 19:02:09 +01:00
|
|
|
|
Pattern.compile("https?://([^/]+)/users/[A-Za-z0-9_]+/statuses/([^?#/\\s]+)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
// ActivityPub 2
|
2019-01-28 19:02:09 +01:00
|
|
|
|
private val reTootUriAP2 =
|
|
|
|
|
Pattern.compile("https?://([^/]+)/@[A-Za-z0-9_]+/([^?#/\\s]+)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-11-27 17:53:27 +01:00
|
|
|
|
// 公開ステータスページのURL マストドン
|
2019-09-30 13:02:39 +02:00
|
|
|
|
private val reStatusPage =
|
2019-01-28 19:02:09 +01:00
|
|
|
|
Pattern.compile("""\Ahttps://([^/]+)/@([A-Za-z0-9_]+)/([^?#/\s]+)(?:\z|[?#])""")
|
2018-11-27 17:53:27 +01:00
|
|
|
|
|
|
|
|
|
// 公開ステータスページのURL Misskey
|
2019-06-03 21:05:49 +02:00
|
|
|
|
internal val reStatusPageMisskey = Pattern.compile(
|
|
|
|
|
"""\Ahttps://([^/]+)/notes/([0-9a-f]{24}|[0-9a-z]{10})\b""",
|
2018-12-26 18:08:01 +01:00
|
|
|
|
Pattern.CASE_INSENSITIVE
|
|
|
|
|
)
|
2019-03-06 10:49:02 +01:00
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
|
// PleromaのStatusのUri
|
2019-09-30 13:02:39 +02:00
|
|
|
|
private val reStatusPageObjects =
|
2019-01-28 19:02:09 +01:00
|
|
|
|
Pattern.compile("""\Ahttps://([^/]+)/objects/([^?#/\s]+)(?:\z|[?#])""")
|
|
|
|
|
|
|
|
|
|
// PleromaのStatusの公開ページ
|
2019-09-30 13:02:39 +02:00
|
|
|
|
private val reStatusPageNotice =
|
2019-01-28 19:02:09 +01:00
|
|
|
|
Pattern.compile("""\Ahttps://([^/]+)/notice/([^?#/\s]+)(?:\z|[?#])""")
|
2019-10-22 06:39:00 +02:00
|
|
|
|
|
2019-09-30 13:02:39 +02:00
|
|
|
|
// PixelfedのStatusの公開ページ
|
|
|
|
|
// https://pixelfed.tokyo/p/tateisu/84169185147621376
|
|
|
|
|
private val reStatusPagePixelfed =
|
|
|
|
|
Pattern.compile("""\Ahttps://([^/]+)/p/([A-Za-z0-9_]+)/([^?#/\s]+)(?:\z|[?#])""")
|
|
|
|
|
|
|
|
|
|
// returns null or pair( status_id, host ,url )
|
2019-10-22 06:39:00 +02:00
|
|
|
|
fun String.findStatusIdFromUrl() : FindStatusIdFromUrlResult? {
|
2019-09-30 13:02:39 +02:00
|
|
|
|
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
|
|
|
|
var m = reStatusPage.matcher(this)
|
|
|
|
|
if(m.find()) {
|
2019-10-22 06:39:00 +02:00
|
|
|
|
return FindStatusIdFromUrlResult(EntityId(m.groupEx(3) !!), m.groupEx(1) !!, this)
|
2019-09-30 13:02:39 +02:00
|
|
|
|
}
|
2019-10-22 06:39:00 +02:00
|
|
|
|
|
2019-09-30 13:02:39 +02:00
|
|
|
|
// https://misskey.xyz/notes/(id)
|
|
|
|
|
m = reStatusPageMisskey.matcher(this)
|
|
|
|
|
if(m.find()) {
|
2019-10-22 06:39:00 +02:00
|
|
|
|
return FindStatusIdFromUrlResult(EntityId(m.groupEx(2) !!), m.groupEx(1) !!, this)
|
2019-09-30 13:02:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://misskey.xyz/objects/(id)
|
|
|
|
|
m = reStatusPageObjects.matcher(this)
|
|
|
|
|
if(m.find()) {
|
|
|
|
|
return FindStatusIdFromUrlResult(
|
|
|
|
|
null, // ステータスIDではないのでどのタンスで開くにせよ検索APIを通すことになる
|
2019-10-22 06:39:00 +02:00
|
|
|
|
m.groupEx(1) !!,
|
2019-09-30 13:02:39 +02:00
|
|
|
|
this
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://pl.telteltel.com/notice/9fGFPu4LAgbrTby0xc
|
|
|
|
|
m = reStatusPageNotice.matcher(this)
|
|
|
|
|
if(m.find()) {
|
|
|
|
|
return FindStatusIdFromUrlResult(
|
|
|
|
|
EntityId(m.groupEx(2) !!),
|
2019-10-22 06:39:00 +02:00
|
|
|
|
m.groupEx(1) !!,
|
2019-09-30 13:02:39 +02:00
|
|
|
|
this
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m = reStatusPagePixelfed.matcher(this)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) {
|
2019-09-30 13:02:39 +02:00
|
|
|
|
return FindStatusIdFromUrlResult(
|
|
|
|
|
EntityId(m.groupEx(3) !!),
|
2019-10-22 06:39:00 +02:00
|
|
|
|
m.groupEx(1) !!,
|
2019-09-30 13:02:39 +02:00
|
|
|
|
this
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
fun parseListTootsearch(
|
|
|
|
|
parser : TootParser,
|
2020-01-07 09:03:32 +01:00
|
|
|
|
root : JsonObject
|
2018-01-21 17:47:13 +01:00
|
|
|
|
) : ArrayList<TootStatus> {
|
|
|
|
|
|
|
|
|
|
parser.serviceType = ServiceType.TOOTSEARCH
|
|
|
|
|
|
|
|
|
|
val result = ArrayList<TootStatus>()
|
2018-01-12 10:01:25 +01:00
|
|
|
|
val array = TootApiClient.getTootsearchHits(root)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(array != null) {
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val array_size = array.size
|
2018-01-04 19:52:25 +01:00
|
|
|
|
result.ensureCapacity(array_size)
|
2020-01-07 09:03:32 +01:00
|
|
|
|
for(i in 0 until array.size) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
try {
|
2020-01-07 09:03:32 +01:00
|
|
|
|
val src = array.parseJsonObject(i)?.parseJsonObject("_source") ?: continue
|
2018-01-21 17:47:13 +01:00
|
|
|
|
result.add(TootStatus(parser, src))
|
2018-01-04 19:52:25 +01:00
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
|
log.trace(ex)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val tz_utc = TimeZone.getTimeZone("UTC")
|
|
|
|
|
|
2018-01-21 13:46:36 +01:00
|
|
|
|
private val reTime =
|
|
|
|
|
Pattern.compile("\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-01-21 13:46:36 +01:00
|
|
|
|
private val reMSPTime =
|
|
|
|
|
Pattern.compile("\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
fun parseTime(strTime : String?) : Long {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
if(strTime != null && strTime.isNotEmpty()) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
try {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
val m = reTime.matcher(strTime)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(! m.find()) {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
log.d("invalid time format: %s", strTime)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
} else {
|
|
|
|
|
val g = GregorianCalendar(tz_utc)
|
|
|
|
|
g.set(
|
2019-09-12 16:05:18 +02:00
|
|
|
|
m.groupEx(1).optInt() ?: 1,
|
|
|
|
|
(m.groupEx(2).optInt() ?: 1) - 1,
|
|
|
|
|
m.groupEx(3).optInt() ?: 1,
|
|
|
|
|
m.groupEx(4).optInt() ?: 0,
|
|
|
|
|
m.groupEx(5).optInt() ?: 0,
|
|
|
|
|
m.groupEx(6).optInt() ?: 0
|
2018-01-04 19:52:25 +01:00
|
|
|
|
)
|
2019-09-12 16:05:18 +02:00
|
|
|
|
g.set(Calendar.MILLISECOND, m.groupEx(7).optInt() ?: 0)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
return g.timeInMillis
|
|
|
|
|
}
|
|
|
|
|
} catch(ex : Throwable) { // ParseException, ArrayIndexOutOfBoundsException
|
|
|
|
|
log.trace(ex)
|
|
|
|
|
log.e(ex, "TootStatus.parseTime failed. src=%s", strTime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return 0L
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun parseTimeMSP(strTime : String?) : Long {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
if(strTime != null && strTime.isNotEmpty()) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
try {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
val m = reMSPTime.matcher(strTime)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(! m.find()) {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
log.d("invalid time format: %s", strTime)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
} else {
|
|
|
|
|
val g = GregorianCalendar(tz_utc)
|
|
|
|
|
g.set(
|
2019-09-12 16:05:18 +02:00
|
|
|
|
m.groupEx(1).optInt() ?: 1,
|
|
|
|
|
(m.groupEx(2).optInt() ?: 1) - 1,
|
|
|
|
|
m.groupEx(3).optInt() ?: 1,
|
|
|
|
|
m.groupEx(4).optInt() ?: 0,
|
|
|
|
|
m.groupEx(5).optInt() ?: 0,
|
|
|
|
|
m.groupEx(6).optInt() ?: 0
|
2018-01-04 19:52:25 +01:00
|
|
|
|
)
|
|
|
|
|
g.set(Calendar.MILLISECOND, 500)
|
|
|
|
|
return g.timeInMillis
|
|
|
|
|
}
|
|
|
|
|
} catch(ex : Throwable) { // ParseException, ArrayIndexOutOfBoundsException
|
|
|
|
|
log.trace(ex)
|
|
|
|
|
log.e(ex, "parseTimeMSP failed. src=%s", strTime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return 0L
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-02 08:03:54 +01:00
|
|
|
|
@SuppressLint("SimpleDateFormat")
|
2019-01-30 17:20:49 +01:00
|
|
|
|
internal val date_format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
fun formatTime(context : Context, t : Long, bAllowRelative : Boolean) : String {
|
2018-01-17 02:16:26 +01:00
|
|
|
|
if(bAllowRelative && Pref.bpRelativeTimestamp(App1.pref)) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val now = System.currentTimeMillis()
|
|
|
|
|
var delta = now - t
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
|
|
|
|
@StringRes val phraseId = if(delta >= 0)
|
|
|
|
|
R.string.relative_time_phrase_past
|
|
|
|
|
else
|
|
|
|
|
R.string.relative_time_phrase_future
|
|
|
|
|
|
|
|
|
|
delta = abs(delta)
|
|
|
|
|
|
|
|
|
|
fun f(v : Long, unit1 : Int, units : Int) : String {
|
|
|
|
|
val vi = v.toInt()
|
|
|
|
|
return context.getString(
|
|
|
|
|
phraseId,
|
|
|
|
|
vi,
|
|
|
|
|
context.getString(if(vi <= 1) unit1 else units)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
when {
|
|
|
|
|
delta < 1000L -> return context.getString(R.string.time_within_second)
|
|
|
|
|
|
2018-12-26 18:08:01 +01:00
|
|
|
|
delta < 60000L -> return f(
|
|
|
|
|
delta / 1000L,
|
|
|
|
|
R.string.relative_time_unit_second1,
|
|
|
|
|
R.string.relative_time_unit_seconds
|
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-12-26 18:08:01 +01:00
|
|
|
|
delta < 3600000L -> return f(
|
|
|
|
|
delta / 60000L,
|
|
|
|
|
R.string.relative_time_unit_minute1,
|
|
|
|
|
R.string.relative_time_unit_minutes
|
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-12-26 18:08:01 +01:00
|
|
|
|
delta < 86400000L -> return f(
|
|
|
|
|
delta / 3600000L,
|
|
|
|
|
R.string.relative_time_unit_hour1,
|
|
|
|
|
R.string.relative_time_unit_hours
|
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-12-26 18:08:01 +01:00
|
|
|
|
delta < 40 * 86400000L -> return f(
|
|
|
|
|
delta / 86400000L,
|
|
|
|
|
R.string.relative_time_unit_day1,
|
|
|
|
|
R.string.relative_time_unit_days
|
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
else -> {
|
2018-12-26 18:08:01 +01:00
|
|
|
|
// fall back to absolute time
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-30 17:20:49 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
return date_format.format(Date(t))
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
fun parseStringArray(src : JsonArray?) : ArrayList<String>? {
|
2018-08-20 19:37:42 +02:00
|
|
|
|
var rv : ArrayList<String>? = null
|
2018-08-21 01:20:39 +02:00
|
|
|
|
if(src != null) {
|
2020-01-07 09:03:32 +01:00
|
|
|
|
for(i in src.indices) {
|
|
|
|
|
val s = src.parseString(i)
|
2018-08-21 01:20:39 +02:00
|
|
|
|
if(s?.isNotEmpty() == true) {
|
|
|
|
|
if(rv == null) rv = ArrayList()
|
2018-08-20 19:37:42 +02:00
|
|
|
|
rv.add(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-08-20 19:37:42 +02:00
|
|
|
|
return rv
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
|
|
2020-01-07 09:31:05 +01:00
|
|
|
|
private fun parseReactionCounts(src : JsonObject?) : LinkedHashMap<String, Int>? {
|
|
|
|
|
|
|
|
|
|
// カスタム絵文字などが含まれるようになったので、内容のバリデーションはできない
|
|
|
|
|
var rv : LinkedHashMap<String, Int>? = null
|
|
|
|
|
src?.entries?.forEach { entry ->
|
|
|
|
|
val key = entry.key.notEmpty() ?: return@forEach
|
|
|
|
|
val v = src.parseInt(key)?.notZero() ?: return@forEach
|
|
|
|
|
if(rv == null) rv = LinkedHashMap()
|
|
|
|
|
rv!![key] = v
|
2018-08-21 03:53:52 +02:00
|
|
|
|
}
|
|
|
|
|
return rv
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-07 09:03:32 +01:00
|
|
|
|
private fun parseMisskeyTags(src : JsonArray?) : ArrayList<TootTag>? {
|
2018-08-21 03:53:52 +02:00
|
|
|
|
var rv : ArrayList<TootTag>? = null
|
|
|
|
|
if(src != null) {
|
2020-01-07 09:03:32 +01:00
|
|
|
|
for(i in src.indices) {
|
|
|
|
|
val sv = src.parseString(i)
|
2018-08-21 03:53:52 +02:00
|
|
|
|
if(sv?.isNotEmpty() == true) {
|
|
|
|
|
if(rv == null) rv = ArrayList()
|
|
|
|
|
rv.add(TootTag(name = sv))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return rv
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
|
private fun validHost(host : String?) : String? {
|
|
|
|
|
return if(host != null && host.isNotEmpty() && host != "?") host else null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
|
fun validStatusId(src : EntityId?) : EntityId? =
|
2019-03-06 10:49:02 +01:00
|
|
|
|
when {
|
2019-01-28 19:02:09 +01:00
|
|
|
|
src == null -> null
|
2019-01-29 02:56:24 +01:00
|
|
|
|
src == EntityId.DEFAULT -> null
|
2019-01-28 19:02:09 +01:00
|
|
|
|
src.toString().startsWith("-") -> null
|
2018-11-03 17:14:14 +01:00
|
|
|
|
else -> src
|
2018-08-25 10:05:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-30 11:54:32 +01:00
|
|
|
|
private fun String.cleanCW() =
|
|
|
|
|
CharacterGroup.reWhitespace.matcher(this).replaceAll(" ").sanitizeBDI()
|
|
|
|
|
/* 空欄かどうかがCW判定条件に影響するので、trimしてはいけない */
|
2018-12-26 18:08:01 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
// 投稿元タンスでのステータスIDを調べる
|
2018-11-03 17:14:14 +01:00
|
|
|
|
fun findStatusIdFromUri(
|
|
|
|
|
uri : String?,
|
2019-01-28 19:02:09 +01:00
|
|
|
|
url : String?
|
2018-11-03 17:14:14 +01:00
|
|
|
|
) : EntityId? {
|
2018-01-11 10:31:25 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
try {
|
2018-01-11 10:31:25 +01:00
|
|
|
|
if(uri?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
// https://friends.nico/users/(who)/statuses/(status_id)
|
|
|
|
|
var m = reTootUriAP1.matcher(uri)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
|
// https://server/@user/(status_id)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
m = reTootUriAP2.matcher(uri)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
|
// https://misskey.xyz/notes/5b802367744b650030a13640
|
|
|
|
|
m = reStatusPageMisskey.matcher(uri)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2019-01-28 19:02:09 +01:00
|
|
|
|
|
|
|
|
|
// https://pl.at7s.me/objects/feeb4399-cd7a-48c8-8999-b58868daaf43
|
|
|
|
|
// tootsearch中の投稿からIDを読めるようにしたい
|
|
|
|
|
// しかしこのURL中のuuidはステータスIDではないので、無意味
|
|
|
|
|
// m = reObjects.matcher(uri)
|
2019-09-12 16:05:18 +02:00
|
|
|
|
// if(m.find()) return EntityId(m.groupEx(2))
|
2019-01-28 19:02:09 +01:00
|
|
|
|
|
|
|
|
|
// https://pl.telteltel.com/notice/9fGFPu4LAgbrTby0xc
|
|
|
|
|
m = reStatusPageNotice.matcher(uri)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2019-01-28 19:02:09 +01:00
|
|
|
|
|
|
|
|
|
// tag:mstdn.osaka,2017-12-19:objectId=5672321:objectType=Status
|
|
|
|
|
m = reTootUriOS.matcher(uri)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2018-08-25 10:05:50 +02:00
|
|
|
|
|
2019-09-22 07:54:46 +02:00
|
|
|
|
log.w("findStatusIdFromUri: unsupported uri. $uri")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-01-11 10:31:25 +01:00
|
|
|
|
|
|
|
|
|
if(url?.isNotEmpty() == true) {
|
|
|
|
|
|
|
|
|
|
// https://friends.nico/users/(who)/statuses/(status_id)
|
|
|
|
|
var m = reTootUriAP1.matcher(url)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2018-01-11 10:31:25 +01:00
|
|
|
|
|
|
|
|
|
// https://friends.nico/@(who)/(status_id)
|
|
|
|
|
m = reTootUriAP2.matcher(url)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2019-01-28 19:02:09 +01:00
|
|
|
|
|
|
|
|
|
// https://misskey.xyz/notes/5b802367744b650030a13640
|
|
|
|
|
m = reStatusPageMisskey.matcher(url)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2019-03-06 10:49:02 +01:00
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
|
// https://pl.at7s.me/objects/feeb4399-cd7a-48c8-8999-b58868daaf43
|
|
|
|
|
// tootsearch中の投稿からIDを読めるようにしたい
|
|
|
|
|
// しかしこのURL中のuuidはステータスIDではないので、無意味
|
|
|
|
|
// m = reObjects.matcher(url)
|
2019-09-12 16:05:18 +02:00
|
|
|
|
// if(m.find()) return EntityId(m.groupEx(2))
|
2019-01-28 19:02:09 +01:00
|
|
|
|
|
|
|
|
|
// https://pl.telteltel.com/notice/9fGFPu4LAgbrTby0xc
|
|
|
|
|
m = reStatusPageNotice.matcher(url)
|
2019-10-22 06:39:00 +02:00
|
|
|
|
if(m.find()) return EntityId(m.groupEx(2) !!)
|
2018-01-11 10:31:25 +01:00
|
|
|
|
|
|
|
|
|
|
2019-09-22 07:54:46 +02:00
|
|
|
|
log.w("findStatusIdFromUri: unsupported url. $url")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch(ex : Throwable) {
|
2018-08-28 15:53:30 +02:00
|
|
|
|
log.e(ex, "can't parse status from: $uri,$url")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-18 12:58:14 +02:00
|
|
|
|
return null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 19:05:14 +01:00
|
|
|
|
private val reLinkUrl = Pattern.compile("""(https?://[\w/:%#@${'$'}&?!()\[\]~.=+\-]+)""")
|
|
|
|
|
private val reMention = Pattern.compile(
|
|
|
|
|
"""(?<=^|[^/\w\p{Pc}])@((\w+([\w.-]+\w+)?)(?:@[a-z0-9.\-]+[a-z0-9]+)?)""",
|
|
|
|
|
Pattern.CASE_INSENSITIVE
|
|
|
|
|
)
|
|
|
|
|
private val strUrlReplacement = (0 until 23).map { ' ' }.joinToString()
|
|
|
|
|
|
|
|
|
|
fun countText(s : String) : Int {
|
|
|
|
|
return s
|
|
|
|
|
.replaceAll(reLinkUrl, strUrlReplacement)
|
|
|
|
|
.replaceAll(reMention, "@$2")
|
|
|
|
|
.codePointCount()
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-07-06 17:22:22 +02:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|