Merge branch 'dev' into m/11.1.0

This commit is contained in:
xtaodada 2024-10-08 12:00:10 +08:00
commit 2c42605d38
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
11 changed files with 437 additions and 11 deletions

View File

@ -1460,11 +1460,11 @@ public class LocaleController {
}
}
}
if (value == null || "".equals(value)) {
if (value == null || value.isEmpty()) {
value = "LOC_ERR:" + key;
if (getFallbackResources() != null)
value = getFallbackResources().getString(res);
} else {
} else if (NekoConfig.localeToDBC.Bool()) {
value = LocFiltersKt.filter(value);
}
return value;

View File

@ -42,6 +42,7 @@ import androidx.annotation.NonNull;
import androidx.collection.LongSparseArray;
import androidx.core.graphics.ColorUtils;
import top.qwq2333.nullgram.utils.StringUtils;
import xyz.nextalone.nagram.NaConfig;
import xyz.nextalone.nagram.helper.MessageHelper;
import xyz.nextalone.nagram.ui.syntaxhighlight.SyntaxHighlight;
@ -1754,6 +1755,12 @@ public class MessageObject {
fromUser = getUser(users, sUsers, message.from_id.user_id);
}
if (generateLayout && messageOwner.message != null && NaConfig.INSTANCE.getEnablePanguOnReceiving().Bool()) {
var pair = StringUtils.spacingText(messageOwner.message, messageOwner.entities);
messageOwner.message = pair.getFirst();
messageOwner.entities = pair.getSecond();
}
updateMessageText(users, chats, sUsers, sChats);
setType();
if (generateLayout) {

View File

@ -102,6 +102,8 @@ import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import kotlin.Pair;
import top.qwq2333.nullgram.utils.StringUtils;
import xyz.nextalone.nagram.NaConfig;
public class SendMessagesHelper extends BaseController implements NotificationCenter.NotificationCenterDelegate {
@ -1945,7 +1947,16 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
if (msgObj.getId() <= 0 || msgObj.needDrawBluredPreview()) {
if (msgObj.type == MessageObject.TYPE_TEXT && !TextUtils.isEmpty(msgObj.messageText)) {
TLRPC.WebPage webPage = msgObj.messageOwner.media != null ? msgObj.messageOwner.media.webpage : null;
SendMessageParams params = SendMessageParams.of(msgObj.messageText.toString(), peer, null, replyToTopMsg, webPage, webPage != null, msgObj.messageOwner.entities, null, null, notify, scheduleDate, null, false);
var messageText = msgObj.messageText.toString();
var entities = msgObj.messageOwner.entities;
if (!msgObj.isForwarded() && NaConfig.INSTANCE.getEnablePanguOnSending().Bool()) {
var pair = StringUtils.spacingText(messageText, msgObj.messageOwner.entities);
messageText = pair.getFirst();
entities = pair.getSecond();
}
SendMessageParams params = SendMessageParams.of(messageText, peer, null, replyToTopMsg, webPage, webPage != null, entities, null, null, notify, scheduleDate, null, false);
params.quick_reply_shortcut = msgObj.getQuickReplyName();
params.quick_reply_shortcut_id = msgObj.getQuickReplyId();
sendMessage(params);
@ -2107,9 +2118,18 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
if (newMsg.message == null) {
newMsg.message = "";
}
if (!((newMsg.params.containsKey("fwd_id") || newMsg.params.containsKey("fwd_peer")) || msgObj.isForwarded() || MessageObject.isForwardedMessage(newMsg)) && NaConfig.INSTANCE.getEnablePanguOnSending().Bool()) {
var pair = StringUtils.spacingText(newMsg.message, msgObj.messageOwner.entities);
newMsg.message = pair.getFirst();
newMsg.entities = pair.getSecond();
} else {
newMsg.entities = msgObj.messageOwner.entities;
}
newMsg.fwd_msg_id = msgObj.getId();
newMsg.attachPath = msgObj.messageOwner.attachPath;
newMsg.entities = msgObj.messageOwner.entities;
// newMsg.entities = msgObj.messageOwner.entities;
if (msgObj.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) {
newMsg.reply_markup = new TLRPC.TL_replyInlineMarkup();
boolean dropMarkup = false;
@ -3760,6 +3780,14 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
} else {
newMsg = new TLRPC.TL_message();
}
if (poll.poll != null && NaConfig.INSTANCE.getEnablePanguOnSending().Bool()) {
poll.poll.question = StringUtils.spacingText(poll.poll.question);
for (int i = 0; i < poll.poll.answers.size(); i++) {
poll.poll.answers.get(i).text = StringUtils.spacingText(poll.poll.answers.get(i).text);
}
}
newMsg.media = poll;
type = 10;
} else if (location != null) {
@ -4227,6 +4255,21 @@ public class SendMessagesHelper extends BaseController implements NotificationCe
isFinalGroupMedia = params.get("final") != null;
}
if (!((params != null && params.containsKey("fwd_id")) || MessageObject.isForwardedMessage(newMsg)) && NaConfig.INSTANCE.getEnablePanguOnSending().Bool()) {
Pair<String, ArrayList<TLRPC.MessageEntity>> pair;
if (caption != null) {
pair = StringUtils.spacingText(caption, entities);
caption = pair.getFirst();
} else {
pair = StringUtils.spacingText(message, entities);
message = pair.getFirst();
}
entities = pair.getSecond();
newMsg.message = pair.getFirst();
newMsg.entities = pair.getSecond();
}
if (stars > 0) {
TLRPC.MessageMedia media = newMsg.media;
TLRPC.TL_messageMediaPaidMedia paidMedia = new TLRPC.TL_messageMediaPaidMedia();

View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2019-2024 qwq233 <qwq233@qwq2333.top>
* https://github.com/qwq233/Nullgram
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this software.
* If not, see
* <https://www.gnu.org/licenses/>
*/
package top.qwq2333.nullgram.utils
import org.telegram.tgnet.TLRPC
import ws.vinta.pangu.Pangu
import kotlin.math.ceil
object StringUtils {
private val pangu = Pangu()
/**
*
* 字符串是否为空白空白的定义如下
*
* 1. `null`
* 1. 空字符串`""`
* 1. 空格全角空格制表符换行符等不可见字符
*
*
*
*
*
* * `StringUtils.isBlank(null) // true`
* * `StringUtils.isBlank("") // true`
* * `StringUtils.isBlank(" \t\n") // true`
* * `StringUtils.isBlank("abc") // false`
*
*
* @param str 被检测的字符串
* @return 若为空白则返回 true
*/
@JvmStatic
fun isBlank(str: CharSequence?): Boolean {
if (str.isNullOrEmpty()) {
return true
}
val length: Int = str.length
for (i in 0 until length) {
// 只要有一个非空字符即为非空字符串
if (!isBlankChar(str[i])) {
return false
}
}
return true
}
/**
* 是否空白符<br></br>
* 空白符包括空格制表符全角空格和不间断空格<br></br>
*
* @param c 字符
* @return 是否空白符
* @see Character.isWhitespace
* @see Character.isSpaceChar
*/
fun isBlankChar(c: Int): Boolean {
return Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\ufeff'.code || c == '\u202a'.code || c == '\u0000'.code
}
/**
* 是否空白符<br></br>
* 空白符包括空格制表符全角空格和不间断空格<br></br>
*
* @param c 字符
* @return 是否空白符
* @see Character.isWhitespace
* @see Character.isSpaceChar
*/
fun isBlankChar(c: Char): Boolean {
return isBlankChar(c.code)
}
/**
* Return a string with a maximum length of `length` characters.
* If there are more than `length` characters, then string ends with an ellipsis ("...").
*
* @param text text
* @param length maximum length you want
* @return Return a string with a maximum length of `length` characters.
*/
@JvmStatic
@Suppress("NAME_SHADOWING")
fun ellipsis(text: String, length: Int): String {
// The letters [iIl1] are slim enough to only count as half a character.
var length = length
length += ceil(text.replace("[^iIl]".toRegex(), "").length / 2.0).toInt()
return if (text.length > length) {
text.substring(0, length - 3) + "..."
} else text
}
@JvmStatic
fun spacingText(message: TLRPC.TL_textWithEntities): TLRPC.TL_textWithEntities {
return TLRPC.TL_textWithEntities().apply {
val pair = spacingText(message.text, message.entities)
text = pair.first
entities = pair.second
}
}
@JvmStatic
fun spacingText(text: String, entities: ArrayList<TLRPC.MessageEntity>?): Pair<String, ArrayList<TLRPC.MessageEntity>?> {
if (text.startsWith("/")) return Pair(text, entities) // command
if (entities.isNullOrEmpty()) return Pair(pangu.spacingText(text), entities)
val panguText = pangu.spacingText(text)
if (panguText.length == text.length) return Pair(panguText, entities) // processed or unnecessary
var skip = 0
for (i in text.indices) {
if (i + skip >= panguText.length) break
if (text[i] == panguText[i + skip]) continue
entities.forEach {
if (it.offset >= i + skip) { // text is after this entity
it.offset += 1
} else if (it.offset + it.length >= i + skip) { // text is in this entity
it.length += 1
} // text is before this entity
}
skip += 1
}
// prevent out of bound
entities.forEach {
if (it.offset >= panguText.length) {
it.offset = panguText.length - 1
}
if (it.offset + it.length > panguText.length) {
it.length = panguText.length - it.offset
}
}
return Pair(panguText, entities)
}
}

View File

@ -196,6 +196,8 @@ public class NekoConfig {
// priv branch changes
public static ConfigItem localPremium = addConfig("localPremium", configTypeBool, false);
public static ConfigItem localeToDBC = addConfig("LocaleToDBC", configTypeBool, false);
static {
loadConfig(false);
checkMigrate(false);

View File

@ -1,12 +1,12 @@
package tw.nekomimi.nekogram.parts
private val mapArr = mapOf(
'' to '(',
'' to ')',
'。' to '.',
'' to ',',
'' to '?',
'' to ';'
'' to '(',
'' to ')',
'。' to '.',
'' to ',',
'' to '?',
'' to ';'
)
fun filter(input: String) = input.toCharArray().let { c ->
@ -21,4 +21,4 @@ fun filter(input: String) = input.toCharArray().let { c ->
listOf(char)
}
}.filterNotNull().toCharArray().let(::String)
}
}

View File

@ -127,6 +127,7 @@ public class NekoExperimentalSettingsActivity extends BaseNekoXSettingsActivity
private final AbstractConfigCell disableSendReadStoriesRow = cellGroup.appendCell(new ConfigCellTextCheck(NaConfig.INSTANCE.getDisableSendReadStories()));
private final AbstractConfigCell dividerStory = cellGroup.appendCell(new ConfigCellDivider());
// Sticker Cache
private final AbstractConfigCell header3 = cellGroup.appendCell(new ConfigCellHeader(LocaleController.getString(R.string.ExternalStickerCache)));
private final AbstractConfigCell externalStickerCacheRow = cellGroup.appendCell(new ConfigCellAutoTextCheck(
NaConfig.INSTANCE.getExternalStickerCache(), LocaleController.getString(R.string.ExternalStickerCacheHint), this::onExternalStickerCacheButtonClick));
@ -136,6 +137,13 @@ public class NekoExperimentalSettingsActivity extends BaseNekoXSettingsActivity
private final AbstractConfigCell externalStickerCacheDeleteAllRow = cellGroup.appendCell(new ConfigCellText("ExternalStickerCacheDeleteAll", ExternalStickerCacheHelper::deleteAllCaches));
private final AbstractConfigCell divider2 = cellGroup.appendCell(new ConfigCellDivider());
// Pangu
private final AbstractConfigCell header4 = cellGroup.appendCell(new ConfigCellHeader(LocaleController.getString(R.string.Pangu)));
private final AbstractConfigCell enablePanguOnSendingRow = cellGroup.appendCell(new ConfigCellTextCheck(NaConfig.INSTANCE.getEnablePanguOnSending(), LocaleController.getString(R.string.PanguInfo)));
private final AbstractConfigCell enablePanguOnReceivingRow = cellGroup.appendCell(new ConfigCellTextCheck(NaConfig.INSTANCE.getEnablePanguOnReceiving()));
private final AbstractConfigCell localeToDBCRow = cellGroup.appendCell(new ConfigCellTextCheck(NekoConfig.localeToDBC));
private final AbstractConfigCell divider3 = cellGroup.appendCell(new ConfigCellDivider());
private UndoView tooltip;
private static final int INTENT_PICK_CUSTOM_EMOJI_PACK = 114;
@ -359,6 +367,8 @@ public class NekoExperimentalSettingsActivity extends BaseNekoXSettingsActivity
intent.setType("application/zip");
Activity act = getParentActivity();
act.startActivityFromChild(act, intent, INTENT_PICK_CUSTOM_EMOJI_PACK);
} else if (key.equals(NekoConfig.localeToDBC.getKey())) {
tooltip.showWithAction(0, UndoView.ACTION_NEED_RESATRT, null, null);
}
};

View File

@ -0,0 +1,189 @@
/*
* Created by Vinta Chen on 2014/11/05.
* Modified by qwq233 on 2023/07/17
*/
package ws.vinta.pangu
import java.io.IOException
import java.util.regex.Pattern
/**
* Paranoid text spacing for good readability, to automatically insert whitespace between
* CJK (Chinese, Japanese, Korean), half-width English, digit and symbol characters.
*
*
* These whitespaces between English and Chinese characters are called "Pangu Spacing" by sinologist, since it
* separate the confusion between full-width and half-width characters. Studies showed that who dislike to
* add whitespace between English and Chinese characters also have relationship problem. Almost 70 percent of them
* will get married to the one they don't love, the rest only can left the heritage to their cat. Indeed,
* love and writing need some space in good time.
*
* @author Vinta Chen
* @author qwq233
* @since 1.0.0
*/
class Pangu {
companion object {
/*
* Some capturing group patterns for convenience.
*
* CJK: Chinese, Japanese, Korean
* ANS: Alphabet, Number, Symbol
*/
private val CJK_ANS = Pattern.compile(
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])" +
"([a-z0-9`~@\\$%\\^&\\*\\-_\\+=\\|\\\\/])",
Pattern.CASE_INSENSITIVE
)
private val ANS_CJK = Pattern.compile(
"([a-z0-9`~!\\$%\\^&\\*\\-_\\+=\\|\\\\;:,\\./\\?])" +
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])",
Pattern.CASE_INSENSITIVE
)
private val CJK_QUOTE = Pattern.compile(
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])" +
"([\"'])"
)
private val QUOTE_CJK = Pattern.compile(
"([\"'])" +
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])"
)
private val FIX_QUOTE = Pattern.compile("([\"'])(\\s*)(.+?)(\\s*)([\"'])")
private val CJK_BRACKET_CJK = Pattern.compile(
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])" +
"([\\({\\[]+(.*?)[\\)}\\]]+)" +
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])"
)
private val CJK_BRACKET = Pattern.compile(
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])" +
"([\\(\\){}\\[\\]<>])"
)
private val BRACKET_CJK = Pattern.compile(
"([\\(\\){}\\[\\]<>])" +
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])"
)
private val FIX_BRACKET = Pattern.compile("([(\\({\\[)]+)(\\s*)(.+?)(\\s*)([\\)}\\]]+)")
private val CJK_HASH = Pattern.compile(
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])" +
"(#(\\S+))"
)
private val HASH_CJK = Pattern.compile(
"((\\S+)#)" +
"([\\p{InHiragana}\\p{InKatakana}\\p{InBopomofo}\\p{InCJKCompatibilityIdeographs}\\p{InCJKUnifiedIdeographs}])"
)
}
private fun processUrl(text: String) = Pattern.compile("://").matcher(text).let { matcher ->
if (!matcher.find()) {
throw NullPointerException("No URL found in text")
}
var prefixOffset = 0
val prefix: String = StringBuilder().apply {
arrayListOf<Char>().apply {
for (i in matcher.start() - 1 downTo 0) {
val c = text[i]
if (c.isWhitespace()) {
prefixOffset = i + 1
break
}
add(c)
}
}.reversed().forEach { append(it) }
}.toString()
var suffixOffset = text.lastIndex
val suffix: String = StringBuilder().apply {
arrayListOf<Char>().apply {
for (i in matcher.end() until text.length) {
val c = text[i]
if (c.isWhitespace()) {
suffixOffset = i
break
}
add(c)
}
}.forEach { append(it) }
}.toString()
val url = "$prefix://$suffix"
val first: String? = if (0 != prefixOffset) text.substring(0, prefixOffset) else null
val last: String? = if (text.lastIndex != suffixOffset) text.substring(suffixOffset) else null
Triple(first, url, last)
}
/**
* Performs a paranoid text spacing on `text`.
*
* @param text the string you want to process, must not be `null`.
* @return a comfortable and readable version of `text` for paranoiac.
*/
fun spacingText(text: String): String {
var text: String = text
// URL
if (text.contains("://")) {
val (first, url, last) = processUrl(text)
var result = String()
if (first != null) {
result += spacingText(first)
}
result += url
if (last != null) {
result += spacingText(last)
}
return result
}
// CJK and quotes
val cqMatcher = CJK_QUOTE.matcher(text)
text = cqMatcher.replaceAll("$1 $2")
val qcMatcher = QUOTE_CJK.matcher(text)
text = qcMatcher.replaceAll("$1 $2")
val fixQuoteMatcher = FIX_QUOTE.matcher(text)
text = fixQuoteMatcher.replaceAll("$1$3$5")
// CJK and brackets
val oldText = text
val cbcMatcher = CJK_BRACKET_CJK.matcher(text)
val newText = cbcMatcher.replaceAll("$1 $2 $4")
text = newText
if (oldText == newText) {
val cbMatcher = CJK_BRACKET.matcher(text)
text = cbMatcher.replaceAll("$1 $2")
val bcMatcher = BRACKET_CJK.matcher(text)
text = bcMatcher.replaceAll("$1 $2")
}
val fixBracketMatcher = FIX_BRACKET.matcher(text)
text = fixBracketMatcher.replaceAll("$1$3$5")
// CJK and hash
val chMatcher = CJK_HASH.matcher(text)
text = chMatcher.replaceAll("$1 $2")
val hcMatcher = HASH_CJK.matcher(text)
text = hcMatcher.replaceAll("$1 $3")
// CJK and ANS
val caMatcher = CJK_ANS.matcher(text)
text = caMatcher.replaceAll("$1 $2")
val acMatcher = ANS_CJK.matcher(text)
text = acMatcher.replaceAll("$1 $2")
return text
}
private fun log(text: String) {
println("panguTrace: $text")
}
}
internal object Test {
@Throws(IOException::class)
@JvmStatic
fun main(args: Array<String>) {
val pangu = Pangu()
println(pangu.spacingText("當你凝視著 https://telegra.ph/八尋ぽち-ひみチュッ-中国翻訳-無修正-DL版-06-17-3 bug也凝視著 https://telegra.ph/ASDF-DL版-06-17-3"))
}
}

View File

@ -640,6 +640,18 @@ object NaConfig {
ConfigItem.configTypeString,
""
)
val enablePanguOnSending =
addConfig(
"EnablePanguOnSending",
ConfigItem.configTypeBool,
false
)
val enablePanguOnReceiving =
addConfig(
"EnablePanguOnReceiving",
ConfigItem.configTypeBool,
false
)
private fun addConfig(
k: String,

View File

@ -159,4 +159,8 @@
<string name="SearchHashtagDefaultPageChannel">在频道点击标签默认搜索页面</string>
<string name="SearchHashtagDefaultPageChat">在其他对话点击标签默认搜索页面</string>
<string name="OpenUrlOutBotWebViewRegex">指定 URL Regex 跳出 bot webview</string>
<string name="LocaleToDBC">界面文本使用半角符号</string>
<string name="EnablePanguOnReceiving">接受信息 Pangu 化</string>
<string name="EnablePanguOnSending">发送信息 Pangu 化</string>
<string name="PanguInfo">使文字更具可读性。在 CJK中文、日文、韩文、半宽英文、数字和符号字符之间间隔中自动插入空格</string>
</resources>

View File

@ -159,4 +159,9 @@
<string name="SearchHashtagDefaultPageChannel">Channel Click Hashtag Default Search Page</string>
<string name="SearchHashtagDefaultPageChat">Chat Click Hashtag Default Search Page</string>
<string name="OpenUrlOutBotWebViewRegex">Url regex to jump out of bot webview</string>
<string name="LocaleToDBC">Locale text using DBC case</string>
<string name="Pangu" translatable="false">Pangu</string>
<string name="EnablePanguOnReceiving">Enable Pangu on receiving</string>
<string name="EnablePanguOnSending">Enable Pangu on sending</string>
<string name="PanguInfo">Paranoid text spacing for good readability, to automatically insert whitespace between CJK (Chinese, Japanese, Korean), half-width English, digit and symbol characters.</string>
</resources>