- Profile metadata のfield name のカスタム絵文字対応

- TimelineItemのアカウント表示でnoteのデコード結果が空の場合は空行を追加しない
- fix #108, メンション解読の改善
This commit is contained in:
tateisu 2019-09-12 20:59:33 +09:00
parent 739e7b78f2
commit 0b692a773f
12 changed files with 161 additions and 106 deletions

View File

@ -257,11 +257,11 @@ class ActAccountSetting
setContentView(R.layout.act_account_setting)
App1.initEdgeToEdge(this)
val root :View = findViewById(R.id.svContent)
val root : View = findViewById(R.id.svContent)
Styler.fixHorizontalPadding(root)
ActAppSettingChild.setSwitchColor(this,pref,root)
ActAppSettingChild.setSwitchColor(this, pref, root)
tvInstance = findViewById(R.id.tvInstance)
tvUser = findViewById(R.id.tvUser)
@ -368,7 +368,7 @@ class ActAccountSetting
name_invalidator = NetworkEmojiInvalidator(handler, etDisplayName)
note_invalidator = NetworkEmojiInvalidator(handler, etNote)
default_text_invalidator = NetworkEmojiInvalidator(handler, etDefaultText)
listFieldNameInvalidator = listEtFieldName.map {
NetworkEmojiInvalidator(handler, it)
}
@ -418,7 +418,7 @@ class ActAccountSetting
override fun afterTextChanged(s : Editable?) {
val num = etMaxTootChars.parseInt()
if( num != null && num >= 0){
if(num != null && num >= 0) {
saveUIToData()
}
}
@ -426,11 +426,11 @@ class ActAccountSetting
}
private fun EditText.parseInt():Int?{
private fun EditText.parseInt() : Int? {
val sv = this.text?.toString() ?: return null
return try{
Integer.parseInt(sv,10)
}catch(ex:Throwable){
return try {
Integer.parseInt(sv, 10)
} catch(ex : Throwable) {
null
}
}
@ -472,7 +472,7 @@ class ActAccountSetting
etDefaultText.setText(a.default_text)
etMaxTootChars.setText(a.max_toot_chars.toString())
loading = false
val enabled = ! a.isPseudo
@ -543,14 +543,14 @@ class ActAccountSetting
account.confirm_unfavourite = cbConfirmUnfavourite.isChecked
account.confirm_post = cbConfirmToot.isChecked
account.default_text = etDefaultText.text.toString()
val num = etMaxTootChars.parseInt()
account.max_toot_chars = if( num != null && num >= 0){
account.max_toot_chars = if(num != null && num >= 0) {
num
}else{
} else {
0
}
account.saveSetting()
}
@ -677,7 +677,7 @@ class ActAccountSetting
var bChanged = false
try {
loading = true
val tmpVisibility =
TootVisibility.parseMastodon(json.parseString("posting:default:visibility"))
if(tmpVisibility != null) {
@ -953,12 +953,6 @@ class ActAccountSetting
emojiMapProfile = src.profile_emojis,
emojiMapCustom = src.custom_emojis
)
// fieldsのnameにはカスタム絵文字が適用されない
val decodeOptionsNoCustomEmoji = DecodeOptions(
context = this@ActAccountSetting,
linkHelper = account,
emojiMapProfile = src.profile_emojis
)
val display_name = src.display_name
val name = decodeOptions.decodeEmoji(display_name)
@ -993,7 +987,10 @@ class ActAccountSetting
if(src.source?.fields != null) {
val fields = src.source.fields
listEtFieldName.forEachIndexed { i, et ->
val text = decodeOptionsNoCustomEmoji.decodeEmoji(
// いつからかfields name にもカスタム絵文字が使えるようになった
// https://github.com/tootsuite/mastodon/pull/11350
// しかし
val text = decodeOptions.decodeEmoji(
when {
i >= fields.size -> ""
else -> fields[i].name
@ -1022,7 +1019,9 @@ class ActAccountSetting
val fields = src.fields
listEtFieldName.forEachIndexed { i, et ->
val text = decodeOptionsNoCustomEmoji.decodeEmoji(
// いつからかfields name にもカスタム絵文字が使えるようになった
// https://github.com/tootsuite/mastodon/pull/11350
val text = decodeOptions.decodeEmoji(
when {
fields == null || i >= fields.size -> ""
else -> fields[i].name
@ -1576,11 +1575,13 @@ class ActAccountSetting
}
@Suppress("DEPRECATION")
progress.isIndeterminate = true
@Suppress("DEPRECATION")
progress.setMessage("preparing image…")
progress.setOnCancelListener {
task.cancel(true)
}
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)

View File

@ -844,7 +844,11 @@ class ActPost : AppCompatActivity(),
// 再編集の場合はdefault_textは反映されない
val decodeOptions = DecodeOptions(this, mentionFullAcct = true)
val decodeOptions = DecodeOptions(
this,
mentionFullAcct = true,
mentions = base_status.mentions
)
var text : CharSequence = if(account.isMisskey) {
base_status.content ?: ""
@ -862,12 +866,14 @@ class ActPost : AppCompatActivity(),
val src_enquete = base_status.enquete
val src_items = src_enquete?.items
when {
src_items == null ->{
src_items == null -> {
}
src_enquete.pollType == TootPollsType.FriendsNico && src_enquete.type != TootPolls.TYPE_ENQUETE -> {
// フレニコAPIのアンケート結果は再編集の対象外
}
else -> {
spEnquete.setSelection(
if(src_enquete.pollType == TootPollsType.FriendsNico) {
@ -888,6 +894,7 @@ class ActPost : AppCompatActivity(),
src_index == src_items.size - 1 && choice.text == "\uD83E\uDD14" -> {
// :thinking_face: は再現しない
}
else -> {
et.setText(decodeOptions.decodeEmoji(choice.text))
++ src_index
@ -1590,6 +1597,7 @@ class ActPost : AppCompatActivity(),
pa.attachment?.isAudio == true -> {
// can't set focus
}
else -> a.addAction(getString(R.string.set_focus_point)) {
openFocusPoint(pa)
}

View File

@ -1156,7 +1156,7 @@ internal class ItemViewHolder(
setAcct(tvFollowerAcct, access_info.getFullAcct(who), who.acct)
who.setLastStatusText(tvLastStatusAt,access_info)
who.setAccountExtra(tvLastStatusAt,access_info)
val relation = UserRelation.load(access_info.db_id, who.id)

View File

@ -216,7 +216,7 @@ internal class ViewHolderHeaderProfile(
tvCreated.text =
TootStatus.formatTime(tvCreated.context, (whoDetail ?: who).time_created_at, true)
who.setLastStatusText(tvLastStatusAt,access_info,fromProfileHeader = true)
who.setAccountExtra(tvLastStatusAt,access_info,fromProfileHeader = true)
ivBackground.setImageUrl(
@ -338,17 +338,10 @@ internal class ViewHolderHeaderProfile(
llFields.visibility = View.VISIBLE
// fieldsのnameにはカスタム絵文字が適用されない
val nameDecodeOptions = DecodeOptions(
context = activity,
decodeEmoji = true,
linkHelper = access_info,
short = true,
emojiMapProfile = who.profile_emojis
)
// valueはMisskeyならMFM、MastodonならHTML
val valueDecodeOptions = DecodeOptions(
// fieldsのnameにはカスタム絵文字が適用されるようになった
// https://github.com/tootsuite/mastodon/pull/11350
// fieldsのvalueはMisskeyならMFM、MastodonならHTML
val fieldDecodeOptions = DecodeOptions(
context = activity,
decodeEmoji = true,
linkHelper = access_info,
@ -368,7 +361,7 @@ internal class ViewHolderHeaderProfile(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val nameText = nameDecodeOptions.decodeEmoji(item.name)
val nameText = fieldDecodeOptions.decodeEmoji(item.name)
val nameInvalidator = NetworkEmojiInvalidator(activity.handler, nameView)
nameInvalidator.register(nameText)
@ -387,7 +380,7 @@ internal class ViewHolderHeaderProfile(
LinearLayout.LayoutParams.WRAP_CONTENT
)
val valueText = valueDecodeOptions.decodeHTML(item.value)
val valueText = fieldDecodeOptions.decodeHTML(item.value)
if(item.verified_at > 0L) {
valueText.append('\n')

View File

@ -26,11 +26,9 @@ internal class ViewHolderHeaderSearch(
override fun bindData(column : Column) {
super.bindData(column)
val html = column.getHeaderDesc() ?: ""
val sv = DecodeOptions(activity, access_info,decodeEmoji = true).decodeHTML( html)
tvSearchDesc.text = sv
tvSearchDesc.textColor = column.getContentColor()
tvSearchDesc.text = DecodeOptions(activity, access_info, decodeEmoji = true)
.decodeHTML(column.getHeaderDesc() ?: "")
}
override fun onViewRecycled() {

View File

@ -378,13 +378,13 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
}
}
fun setLastStatusText(
tvLastStatusAt : TextView,
fun setAccountExtra(
tv : TextView,
accessInfo : SavedAccount,
fromProfileHeader : Boolean = false
) {
val pref = App1.pref
val context = tvLastStatusAt.context
val context = tv.context
var sb : SpannableStringBuilder? = null
fun prepareSb() = sb?.apply { append('\n') } ?: SpannableStringBuilder().also { sb = it }
@ -416,19 +416,21 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
.decodeHTML(note)
.replaceAllEx(reNoteLineFeed, " ")
.trimEx()
prepareSb().append(
if(decodedNote is SpannableStringBuilder && decodedNote.length > 200) {
decodedNote.replace(200, decodedNote.length, "")
} else {
decodedNote
}
)
if(decodedNote.isNotBlank()) {
prepareSb().append(
if(decodedNote is SpannableStringBuilder && decodedNote.length > 200) {
decodedNote.replace(200, decodedNote.length, "")
} else {
decodedNote
}
)
}
}
}
if(vg(tvLastStatusAt, sb != null)) {
tvLastStatusAt.text = sb
tvLastStatusAt.movementMethod = MyLinkMovementMethod
if(vg(tv, sb != null)) {
tv.text = sb
tv.movementMethod = MyLinkMovementMethod
}
}
@ -449,28 +451,37 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
internal val reAccountUrl : Pattern =
Pattern.compile("""\Ahttps://(\w[\w.-]*\w)/@(\w+[\w-]*)(?:@(\w[\w.-]*\w))?(?=\z|[?#])""")
// host,user
internal val reAccountUrl2 : Pattern =
Pattern.compile("""\Ahttps://(\w[\w.-]*\w)/users/(\w|\w+[\w-]*\w)(?=\z|[?#])""")
fun getAcctFromUrl(url : String?) : String? {
url ?: return null
val m = reAccountUrl.matcher(url)
return if(m.find()) {
val host = m.group(1)
val user = m.group(2).decodePercent()
var m = reAccountUrl.matcher(url)
if(m.find()) {
val host = m.groupOrNull(1)
val user = m.groupOrNull(2)?.decodePercent()
val instance = m.groupOrNull(3)?.decodePercent()
if(instance?.isNotEmpty() == true) {
return if(instance?.isNotEmpty() == true) {
"$user@$instance"
} else {
"$user@$host"
}
} else {
null
}
m = reAccountUrl2.matcher(url)
if(m.find()) {
val host = m.groupOrNull(1)
val user = m.groupOrNull(2)?.decodePercent()
return "$user@$host"
}
return null
}
// host,user
internal val reAccountUrl2 : Pattern =
Pattern.compile("""\Ahttps://(\w[\w.-]*\w)/users/(\w|\w+[\w-]*\w)(?=\z|[?#])""")
private fun parseSource(src : JSONObject?) : Source? {
src ?: return null

View File

@ -107,7 +107,8 @@ class TootPolls private constructor(
attachmentList = list_attachment,
linkTag = status,
emojiMapCustom = status.custom_emojis,
emojiMapProfile = status.profile_emojis
emojiMapProfile = status.profile_emojis,
mentions = status.mentions
).decodeHTML(this.question ?: "?")
}
@ -124,7 +125,8 @@ class TootPolls private constructor(
attachmentList = list_attachment,
linkTag = status,
emojiMapCustom = status.custom_emojis,
emojiMapProfile = status.profile_emojis
emojiMapProfile = status.profile_emojis,
mentions = status.mentions
).decodeHTML(this.question ?: "?")
this.items = parseChoiceListMastodon(
@ -178,7 +180,8 @@ class TootPolls private constructor(
attachmentList = list_attachment,
linkTag = status,
emojiMapCustom = status.custom_emojis,
emojiMapProfile = status.profile_emojis
emojiMapProfile = status.profile_emojis,
mentions = status.mentions
).decodeHTML(this.question ?: "?")
this.items = parseChoiceListFriendsNico(

View File

@ -280,7 +280,8 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
emojiMapCustom = custom_emojis,
emojiMapProfile = profile_emojis,
attachmentList = media_attachments,
highlightTrie = parser.highlightTrie
highlightTrie = parser.highlightTrie,
mentions = null // MisskeyはMFMをパースし終わるまでメンションが分からない
)
this.decoded_content = options.decodeHTML(content)
@ -290,14 +291,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
}
// Markdownのデコード結果からmentionsを読むのだった
this.mentions =
(decoded_content as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions
this.decoded_mentions = HTMLDecoder.decodeMentions(
parser.linkHelper,
this.mentions,
this
) ?: EMPTY_SPANNABLE
val mentions1 = (decoded_content as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions
val sv = src.parseString("cw")?.cleanCW()
this.spoiler_text = when {
sv == null -> "" // CWなし
@ -305,6 +301,7 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
else -> sv
}
// ハイライト検出のためにDecodeOptionsを作り直す
options = DecodeOptions(
parser.context,
parser.linkHelper,
@ -313,7 +310,8 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
emojiMapCustom = custom_emojis,
emojiMapProfile = profile_emojis,
attachmentList = media_attachments,
highlightTrie = parser.highlightTrie
highlightTrie = parser.highlightTrie,
mentions = null // MisskeyはMFMをパースし終わるまでメンションが分からない
)
this.decoded_spoiler_text = options.decodeHTML(spoiler_text)
@ -321,6 +319,15 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
if(options.highlight_sound != null && this.highlight_sound == null) {
this.highlight_sound = options.highlight_sound
}
val mentions2 = (decoded_spoiler_text as? MisskeyMarkdownDecoder.SpannableStringBuilderEx)?.mentions
this.mentions = mergeMentions(mentions1,mentions2)
this.decoded_mentions = HTMLDecoder.decodeMentions(
parser.linkHelper,
this.mentions,
this
) ?: EMPTY_SPANNABLE
// contentを読んだ後にアンケートのデコード
this.enquete = TootPolls.parse(
@ -410,7 +417,7 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
this.uri = src.parseString("uri") ?: error("missing uri")
this.id = findStatusIdFromUri(uri, url) ?: EntityId.DEFAULT
this.time_created_at = TootStatus.parseTime(this.created_at)
this.time_created_at = parseTime(this.created_at)
this.media_attachments = parseListOrNull(
::TootAttachment,
parser,
@ -470,7 +477,8 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
emojiMapCustom = custom_emojis,
emojiMapProfile = profile_emojis,
attachmentList = media_attachments,
highlightTrie = parser.highlightTrie
highlightTrie = parser.highlightTrie,
mentions = mentions
)
this.decoded_content = options.decodeHTML(content)
@ -486,11 +494,13 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
else -> sv
}
// ハイライト検出のためにDecodeOptionsを作り直す
options = DecodeOptions(
parser.context,
emojiMapCustom = custom_emojis,
emojiMapProfile = profile_emojis,
highlightTrie = parser.highlightTrie
highlightTrie = parser.highlightTrie,
mentions = mentions
)
this.decoded_spoiler_text = options.decodeEmoji(spoiler_text)
@ -533,6 +543,18 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
}
}
private fun mergeMentions(
mentions1 : java.util.ArrayList<TootMention>?,
mentions2 : java.util.ArrayList<TootMention>?
) : java.util.ArrayList<TootMention>? {
val size = (mentions1?.size?:0) + (mentions2?.size?:0)
if( size == 0) return null
val dst = ArrayList<TootMention>(size)
if(mentions1!=null) dst.addAll(mentions1)
if(mentions2!=null) dst.addAll(mentions2)
return dst
}
///////////////////////////////////////////////////
// ユーティリティ
@ -545,14 +567,14 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
fun checkMuted() : Boolean {
// app mute
val muted_app = TootStatus.muted_app
val muted_app = muted_app
if(muted_app != null) {
val name = application?.name
if(name != null && muted_app.contains(name)) return true
}
// word mute
val muted_word = TootStatus.muted_word
val muted_word = muted_word
if(muted_word != null) {
if(muted_word.matchShort(decoded_content)) return true
if(muted_word.matchShort(decoded_spoiler_text)) return true
@ -971,15 +993,15 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
if(uri?.isNotEmpty() == true) {
// https://friends.nico/users/(who)/statuses/(status_id)
var m = reTootUriAP1.matcher(uri)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// https://server/@user/(status_id)
m = reTootUriAP2.matcher(uri)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// https://misskey.xyz/notes/5b802367744b650030a13640
m = reStatusPageMisskey.matcher(uri)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// https://pl.at7s.me/objects/feeb4399-cd7a-48c8-8999-b58868daaf43
// tootsearch中の投稿からIDを読めるようにしたい
@ -989,11 +1011,11 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
// https://pl.telteltel.com/notice/9fGFPu4LAgbrTby0xc
m = reStatusPageNotice.matcher(uri)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// tag:mstdn.osaka,2017-12-19:objectId=5672321:objectType=Status
m = reTootUriOS.matcher(uri)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
log.w("can't parse status uri: $uri")
}
@ -1002,15 +1024,15 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
// https://friends.nico/users/(who)/statuses/(status_id)
var m = reTootUriAP1.matcher(url)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// https://friends.nico/@(who)/(status_id)
m = reTootUriAP2.matcher(url)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// https://misskey.xyz/notes/5b802367744b650030a13640
m = reStatusPageMisskey.matcher(url)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
// https://pl.at7s.me/objects/feeb4399-cd7a-48c8-8999-b58868daaf43
// tootsearch中の投稿からIDを読めるようにしたい
@ -1020,7 +1042,7 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
// https://pl.telteltel.com/notice/9fGFPu4LAgbrTby0xc
m = reStatusPageNotice.matcher(url)
if(m.find()) return EntityId(m.group(2))
if(m.find()) return EntityId(m.group(2)!!)
log.w("can't parse status URL: $url")

View File

@ -5,6 +5,7 @@ import android.text.Spannable
import jp.juggler.subwaytooter.api.entity.CustomEmoji
import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji
import jp.juggler.subwaytooter.api.entity.TootAttachmentLike
import jp.juggler.subwaytooter.api.entity.TootMention
import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.util.WordTrieTree
import java.util.*
@ -22,7 +23,8 @@ class DecodeOptions(
var unwrapEmojiImageTag : Boolean = false,
var enlargeCustomEmoji : Float = 1f,
var forceHtml : Boolean = false, // force use HTML instead of Misskey Markdown
var mentionFullAcct : Boolean = false
var mentionFullAcct : Boolean = false,
var mentions: ArrayList<TootMention>? = null
) {
internal fun isMediaAttachment(url : String?) : Boolean {

View File

@ -435,12 +435,15 @@ object HTMLDecoder {
for(item in src_list) {
if(sb.isNotEmpty()) sb.append(" ")
val start = sb.length
sb.append('@')
if(Pref.bpMentionFullAcct(App1.pref)) {
sb.append(linkHelper.getFullAcct(item.acct))
} else {
sb.append(item.acct)
}
.append(
if(Pref.bpMentionFullAcct(App1.pref)) {
linkHelper.getFullAcct(item.acct)
} else {
item.acct
}
)
val end = sb.length
val url = item.url
if(end > start) {
@ -510,6 +513,20 @@ object HTMLDecoder {
'@' -> {
// @mention
if(options.mentionFullAcct || Pref.bpMentionFullAcct(App1.pref)) {
// https://github.com/tateisu/SubwayTooter/issues/108
// check mentions to skip getAcctFromUrl
val mentions = options.mentions
if( mentions != null){
for( it in mentions){
if( it.url == href ){
val acct = it.acct
if( acct.contains('@')) return "@$acct"
}
}
}
// Account.note does not have mentions metadata. we can't drop resolving by mention URL.
val acct = TootAccount.getAcctFromUrl(href)
if(acct != null) return "@$acct"
}

View File

@ -63,7 +63,7 @@ object TootTextEncoder {
sb.addAfterLine( "\n")
intent.putExtra(ActText.EXTRA_CONTENT_START, sb.length)
sb.append(DecodeOptions(context, access_info).decodeHTML(status.content))
sb.append(DecodeOptions(context, access_info,mentions = status.mentions).decodeHTML(status.content))
encodePolls(sb,context,status)
@ -90,7 +90,7 @@ object TootTextEncoder {
sb.append(sv).append("\n\n")
}
sb.append(DecodeOptions(context, access_info).decodeHTML(status.content))
sb.append(DecodeOptions(context, access_info,mentions=status.mentions).decodeHTML(status.content))
encodePolls(sb,context,status)

View File

@ -327,7 +327,7 @@ fun Bundle.parseString(key : String) : String? {
// Pattern
fun Matcher.groupOrNull(g : Int) : String? =
if(groupCount() >= g) {
if(g in 0 until groupCount() ) {
group(g)
} else {
null