1
0
mirror of https://github.com/tateisu/SubwayTooter synced 2025-02-05 21:23:26 +01:00

notestockカラムの微調整。

This commit is contained in:
tateisu 2020-12-07 21:53:55 +09:00
parent 1916fe0d8e
commit 17f11fd196
14 changed files with 557 additions and 518 deletions

View File

@ -1139,6 +1139,17 @@ class ActMain : AsyncActivity(), Column.Callback, View.OnClickListener,
text
)
}
ActText.RESULT_SEARCH_NOTESTOCK -> {
val text = data?.getStringExtra(Intent.EXTRA_TEXT) ?: ""
addColumn(
false,
defaultInsertPosition,
SavedAccount.na,
ColumnType.SEARCH_NOTESTOCK,
text
)
}
}
}

View File

@ -28,7 +28,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
internal const val RESULT_SEARCH_MSP = RESULT_FIRST_USER + 1
internal const val RESULT_SEARCH_TS = RESULT_FIRST_USER + 2
internal const val RESULT_SEARCH_NOTESTOCK = RESULT_FIRST_USER + 2
internal const val RESULT_SEARCH_NOTESTOCK = RESULT_FIRST_USER + 3
internal const val EXTRA_TEXT = "text"
internal const val EXTRA_CONTENT_START = "content_start"

View File

@ -643,7 +643,7 @@ class Column(
val isSearchColumn: Boolean
get() {
return when (type) {
ColumnType.SEARCH, ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS -> true
ColumnType.SEARCH, ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS ,ColumnType.SEARCH_NOTESTOCK-> true
else -> false
}
}
@ -694,7 +694,7 @@ class Column(
search_resolve = getParamAt(params, 1)
}
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS ->
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS,ColumnType.SEARCH_NOTESTOCK ->
search_query = getParamString(params, 0)
ColumnType.INSTANCE_INFORMATION ->
@ -808,7 +808,7 @@ class Column(
search_resolve = src.optBoolean(KEY_SEARCH_RESOLVE, false)
}
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS -> search_query =
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS ,ColumnType.SEARCH_NOTESTOCK-> search_query =
src.optString(KEY_SEARCH_QUERY)
ColumnType.INSTANCE_INFORMATION -> instance_uri = src.optString(KEY_INSTANCE_URI)
@ -927,7 +927,7 @@ class Column(
dst[KEY_SEARCH_RESOLVE] = search_resolve
}
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS -> {
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS,ColumnType.SEARCH_NOTESTOCK -> {
dst[KEY_SEARCH_QUERY] = search_query
}
@ -998,7 +998,7 @@ class Column(
getParamString(params, 0) == search_query &&
getParamAtNullable<Boolean>(params, 1) == search_resolve
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS ->
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS,ColumnType.SEARCH_NOTESTOCK ->
getParamString(params, 0) == search_query
ColumnType.INSTANCE_INFORMATION -> getParamString(params, 0) == instance_uri
@ -2235,6 +2235,10 @@ class Column(
R.raw.search_desc_ts_en,
R.raw.search_desc_ts_ja
)
ColumnType.SEARCH_NOTESTOCK -> loadSearchDesc(
R.raw.search_desc_notestock_en,
R.raw.search_desc_notestock_ja
)
else -> ""
}
cacheHeaderDesc = cache
@ -2302,7 +2306,7 @@ class Column(
if (getFilterContext() != TootFilter.CONTEXT_NONE) return true
return when (type) {
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS -> true
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS,ColumnType.SEARCH_NOTESTOCK -> true
else -> false
}
}
@ -2424,6 +2428,7 @@ class Column(
ColumnType.SEARCH,
ColumnType.SEARCH_MSP,
ColumnType.SEARCH_TS,
ColumnType.SEARCH_NOTESTOCK,
ColumnType.CONVERSATION,
ColumnType.LIST_LIST,
ColumnType.TREND_TAG,

View File

@ -1,10 +1,7 @@
package jp.juggler.subwaytooter
import android.os.SystemClock
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.util.ScrollPosition
import jp.juggler.util.*
@ -1230,7 +1227,7 @@ class ColumnTask_Refresh(
if(jsonArray != null) {
// max_id の更新
column.idOld = EntityId.mayNull(
TootApiClient.getMspMaxId(
getMspMaxId(
jsonArray,
column.idOld?.toString()
)
@ -1257,7 +1254,7 @@ class ColumnTask_Refresh(
if(jsonObject != null) {
// max_id の更新
column.idOld = EntityId.mayNull(
TootApiClient.getTootsearchMaxId(
getTootsearchMaxId(
jsonObject,
old
)?.toString()
@ -1284,7 +1281,7 @@ class ColumnTask_Refresh(
if(jsonObject != null) {
// max_id の更新
column.idOld = EntityId.mayNull(
TootApiClient.getNotestockMaxDt(
getNotestockMaxDt(
jsonObject
)?.toString()
)

File diff suppressed because it is too large Load Diff

View File

@ -1929,7 +1929,8 @@ internal class ItemViewHolder(
s != null -> Action_Toot.conversation(activity, pos, access_info, s)
// tootsearchは返信元のIDを取得するのにひと手間必要
column.type == ColumnType.SEARCH_TS ->
column.type == ColumnType.SEARCH_TS ||
column.type == ColumnType.SEARCH_NOTESTOCK ->
Action_Toot.showReplyTootsearch(activity, pos, status_showing)
else -> {
@ -2165,7 +2166,8 @@ internal class ItemViewHolder(
// それ以外はコンテキストメニューではなく会話を開く
// tootsearchは返信元のIDを取得するのにひと手間必要
column.type == ColumnType.SEARCH_TS ->
column.type == ColumnType.SEARCH_TS ||
column.type == ColumnType.SEARCH_NOTESTOCK ->
Action_Toot.showReplyTootsearch(
activity,
activity.nextPosition(column),

View File

@ -0,0 +1,100 @@
package jp.juggler.subwaytooter.api
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.put
import jp.juggler.util.*
import okhttp3.Request
private const val mspTokenUrl = "http://mastodonsearch.jp/api/v1.0.1/utoken"
private const val mspSearchUrl = "http://mastodonsearch.jp/api/v1.0.1/cross"
private const val mspApiKey = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc"
fun getMspMaxId(array: JsonArray, old: String?): String? {
// max_id の更新
val size = array.size
if (size > 0) {
val sv = array[size - 1].cast<JsonObject>()?.string("msp_id")?.notEmpty()
if (sv != null) return sv
}
// MSPでは終端は分からず、何度もリトライする
return old
}
fun TootApiClient.searchMsp(query: String, max_id: String?): TootApiResult? {
// ユーザトークンを読む
var user_token: String? = Pref.spMspUserToken(pref)
for (nTry in 0 until 3) {
if (callback.isApiCancelled) return null
// ユーザトークンがなければ取得する
if (user_token == null || user_token.isEmpty()) {
callback.publishApiProgress("get MSP user token...")
val result: TootApiResult = TootApiResult.makeWithCaption("Mastodon Search Portal")
if (result.error != null) return result
if (!sendRequest(result) {
Request.Builder()
.url(mspTokenUrl + "?apikey=" + mspApiKey.encodePercent())
.build()
}) return result
val r2 = parseJson(result) { json ->
val error = json.string("error")
if (error == null) {
null
} else {
val type = json.string("type")
"error: $type $error"
}
}
val jsonObject = r2?.jsonObject ?: return r2
user_token = jsonObject.jsonObject("result")?.string("token")
if (user_token?.isEmpty() != false) {
return result.setError("Can't get MSP user token. response=${result.bodyString}")
} else {
pref.edit().put(Pref.spMspUserToken, user_token).apply()
}
}
// ユーザトークンを使って検索APIを呼び出す
val result: TootApiResult = TootApiResult.makeWithCaption("Mastodon Search Portal")
if (result.error != null) return result
if (!sendRequest(result) {
val url = StringBuilder()
.append(mspSearchUrl)
.append("?apikey=").append(mspApiKey.encodePercent())
.append("&utoken=").append(user_token.encodePercent())
.append("&q=").append(query.encodePercent())
.append("&max=").append(max_id?.encodePercent() ?: "")
Request.Builder().url(url.toString()).build()
}) return result
var isUserTokenError = false
val r2 = parseJson(result) { json ->
val error = json.string("error")
if (error == null) {
null
} else {
// ユーザトークンがダメなら生成しなおす
val detail = json.string("detail")
if ("utoken" == detail) {
isUserTokenError = true
}
val type = json.string("type")
"API returns error: $type $error"
}
}
if (r2 == null || !isUserTokenError) return r2
}
return TootApiResult("MSP user token retry exceeded.")
}

View File

@ -0,0 +1,46 @@
package jp.juggler.subwaytooter.api
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.util.JsonArray
import jp.juggler.util.JsonObject
import jp.juggler.util.cast
import jp.juggler.util.encodePercent
import okhttp3.Request
fun getNotestockStatuses(root: JsonObject): JsonArray? =
root["statuses"].cast()
// notestock の検索結果からmax_dtを抽出します。
// 次ページがない場合はnullを返します。
fun getNotestockMaxDt(root: JsonObject)=
root.jsonArray("statuses")
?.mapNotNull{ it.cast<JsonObject>()?.string("published")}
?.map{ Pair(it, TootStatus.parseTime(it))}
?.filter { it.second != 0L }
?.minByOrNull { it.second }
?.first
fun TootApiClient.searchNotestock(
query: String,
max_dt: String?
): TootApiResult? {
val result = TootApiResult.makeWithCaption("Notestock")
if (result.error != null) return result
if (!sendRequest(result) {
val url = StringBuilder().apply {
append("https://notestock.osa-p.net/api/v1/search.json?q=")
append(query.encodePercent())
if (max_dt != null) append("&max_dt=").append(max_dt.encodePercent())
}.toString()
Request.Builder()
.url(url)
.build()
}) return result
return parseJson(result)
}

View File

@ -70,48 +70,6 @@ class TootApiClient(
private val reStartJsonObject = """\A\s*\{""".asciiPattern()
private val reWhiteSpace = """\s+""".asciiPattern()
private const val mspTokenUrl = "http://mastodonsearch.jp/api/v1.0.1/utoken"
private const val mspSearchUrl = "http://mastodonsearch.jp/api/v1.0.1/cross"
private const val mspApiKey = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc"
fun getMspMaxId(array: JsonArray, old: String?): String? {
// max_id の更新
val size = array.size
if (size > 0) {
val sv = array[size - 1].cast<JsonObject>()?.string("msp_id")?.notEmpty()
if (sv != null) return sv
}
// MSPでは終端は分からず、何度もリトライする
return old
}
fun getTootsearchHits(root: JsonObject): JsonArray? {
return root["hits"].cast<JsonObject>()?.get("hits")?.cast()
}
// returns the number for "from" parameter of next page.
// returns null if no more next page.
fun getTootsearchMaxId(root: JsonObject, old: Long?): Long? {
val size = getTootsearchHits(root)?.size ?: 0
return when {
size <= 0 -> null
else -> (old ?: 0L) + size.toLong()
}
}
fun getNotestockStatuses(root: JsonObject): JsonArray? {
return root["statuses"].cast()
}
// notestock の検索結果からmax_dtを抽出します。
// 次ページがない場合はnullを返します。
fun getNotestockMaxDt(root: JsonObject)=
root.jsonArray("statuses")
?.mapNotNull{ it.cast<JsonObject>()?.string("published")}
?.map{ Pair(it,TootStatus.parseTime(it))}
?.filter { it.second != 0L }
?.minByOrNull { it.second }
?.first
val DEFAULT_JSON_ERROR_PARSER =
{ json: JsonObject -> json["error"]?.toString() }
@ -1204,132 +1162,6 @@ class TootApiClient(
return parseJson(result)
}
fun searchMsp(query: String, max_id: String?): TootApiResult? {
// ユーザトークンを読む
var user_token: String? = Pref.spMspUserToken(pref)
for (nTry in 0 until 3) {
if (callback.isApiCancelled) return null
// ユーザトークンがなければ取得する
if (user_token == null || user_token.isEmpty()) {
callback.publishApiProgress("get MSP user token...")
val result: TootApiResult = TootApiResult.makeWithCaption("Mastodon Search Portal")
if (result.error != null) return result
if (!sendRequest(result) {
Request.Builder()
.url(mspTokenUrl + "?apikey=" + mspApiKey.encodePercent())
.build()
}) return result
val r2 = parseJson(result) { json ->
val error = json.string("error")
if (error == null) {
null
} else {
val type = json.string("type")
"error: $type $error"
}
}
val jsonObject = r2?.jsonObject ?: return r2
user_token = jsonObject.jsonObject("result")?.string("token")
if (user_token?.isEmpty() != false) {
return result.setError("Can't get MSP user token. response=${result.bodyString}")
} else {
pref.edit().put(Pref.spMspUserToken, user_token).apply()
}
}
// ユーザトークンを使って検索APIを呼び出す
val result: TootApiResult = TootApiResult.makeWithCaption("Mastodon Search Portal")
if (result.error != null) return result
if (!sendRequest(result) {
val url = StringBuilder()
.append(mspSearchUrl)
.append("?apikey=").append(mspApiKey.encodePercent())
.append("&utoken=").append(user_token.encodePercent())
.append("&q=").append(query.encodePercent())
.append("&max=").append(max_id?.encodePercent() ?: "")
Request.Builder().url(url.toString()).build()
}) return result
var isUserTokenError = false
val r2 = parseJson(result) { json ->
val error = json.string("error")
if (error == null) {
null
} else {
// ユーザトークンがダメなら生成しなおす
val detail = json.string("detail")
if ("utoken" == detail) {
isUserTokenError = true
}
val type = json.string("type")
"API returns error: $type $error"
}
}
if (r2 == null || !isUserTokenError) return r2
}
return TootApiResult("MSP user token retry exceeded.")
}
fun searchTootsearch(
query: String,
from: Long?
): TootApiResult? {
val result = TootApiResult.makeWithCaption("Tootsearch")
if (result.error != null) return result
if (!sendRequest(result) {
val sb = StringBuilder()
.append("https://tootsearch.chotto.moe/api/v1/search?sort=")
.append("created_at:desc".encodePercent())
.append("&q=").append(query.encodePercent())
if (from != null) {
sb.append("&from=").append(from.toString().encodePercent())
}
Request.Builder()
.url(sb.toString())
.build()
}) return result
return parseJson(result)
}
fun searchNotestock(
query: String,
max_dt: String?
): TootApiResult? {
val result = TootApiResult.makeWithCaption("Notestock")
if (result.error != null) return result
if (!sendRequest(result) {
val url = StringBuilder().apply {
append("https://notestock.osa-p.net/api/v1/search.json?q=")
append(query.encodePercent())
if (max_dt != null) append("&max_dt=").append(max_dt.encodePercent())
}.toString()
Request.Builder()
.url(url)
.build()
}) return result
return parseJson(result)
}
////////////////////////////////////////////////////////////////////////
// JSONデータ以外を扱うリクエスト

View File

@ -0,0 +1,48 @@
package jp.juggler.subwaytooter.api
import jp.juggler.util.JsonArray
import jp.juggler.util.JsonObject
import jp.juggler.util.cast
import jp.juggler.util.encodePercent
import okhttp3.Request
fun getTootsearchHits(root: JsonObject): JsonArray? {
return root["hits"].cast<JsonObject>()?.get("hits")?.cast()
}
// returns the number for "from" parameter of next page.
// returns null if no more next page.
fun getTootsearchMaxId(root: JsonObject, old: Long?): Long? {
val size = getTootsearchHits(root)?.size ?: 0
return when {
size <= 0 -> null
else -> (old ?: 0L) + size.toLong()
}
}
fun TootApiClient.searchTootsearch(
query: String,
from: Long?
): TootApiResult? {
val result = TootApiResult.makeWithCaption("Tootsearch")
if (result.error != null) return result
if (!sendRequest(result) {
val sb = StringBuilder()
.append("https://tootsearch.chotto.moe/api/v1/search?sort=")
.append("created_at:desc".encodePercent())
.append("&q=").append(query.encodePercent())
if (from != null) {
sb.append("&from=").append(from.toString().encodePercent())
}
Request.Builder()
.url(sb.toString())
.build()
}) return result
return parseJson(result)
}

View File

@ -238,7 +238,7 @@ class TootPolls (
this.expired_at =
TootStatus.parseTime(src.string("endTime")).notZero() ?: Long.MAX_VALUE
this.expired = expired_at >= System.currentTimeMillis()
this.multiple = false // TODO
this.multiple = src.containsKey("anyOf")
this.votes_count = items?.sumBy{ it.votes?: 0 }?.notZero()
this.ownVoted = false

View File

@ -8,9 +8,7 @@ import android.text.SpannableString
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootAccountMap
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.*
@ -543,7 +541,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
if (this.highlightSpeech == null) this.highlightSpeech = options.highlightSpeech
if (this.highlightAny == null) this.highlightAny = options.highlightAny
this.enquete = src.jsonArray("oneOf")?.let {
this.enquete = (src.jsonArray("oneOf")?: src.jsonArray("anyOf")) ?.let {
try {
TootPolls(
parser,
@ -1119,7 +1117,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
parser.serviceType = ServiceType.TOOTSEARCH
val result = ArrayList<TootStatus>()
val array = TootApiClient.getTootsearchHits(root)
val array = getTootsearchHits(root)
if (array != null) {
val array_size = array.size
result.ensureCapacity(array_size)
@ -1143,7 +1141,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
parser.serviceType = ServiceType.NOTESTOCK // TODO
val result = ArrayList<TootStatus>()
val array = TootApiClient.getNotestockStatuses(root)
val array = getNotestockStatuses(root)
if (array != null) {
val array_size = array.size
result.ensureCapacity(array_size)
@ -1161,7 +1159,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
private val tz_utc = TimeZone.getTimeZone("UTC")
private val reTime = """\A(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)"""
private val reTime = """\A(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)(?:\D+(\d+))?"""
.asciiPattern()
private val reMSPTime = """\A(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)"""
@ -1190,7 +1188,6 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
log.trace(ex)
log.e(ex, "TootStatus.parseTime failed. src=%s", strTime)
}
}
return 0L
}

View File

@ -0,0 +1 @@
Powered by <a href="https://notestock.osa-p.net/">notestock</a>, it indexes toots of users registered to that site.

View File

@ -0,0 +1 @@
Powered by <a href="https://notestock.osa-p.net/">notestock</a>.