mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-23 11:37:38 +08:00
Compare commits
No commits in common. "49bbc03ec06034854eb14adcb2a38b0c7e7b210c" and "3661cb126481ec8e05c8603a641314f3c5fd7299" have entirely different histories.
49bbc03ec0
...
3661cb1264
@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
# IDE
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
.gradle/
|
|
||||||
.settings/
|
|
||||||
|
|
||||||
#build
|
|
||||||
/build/
|
|
||||||
/bin/
|
|
||||||
.metals/
|
|
||||||
.bloop/
|
|
||||||
.project
|
|
||||||
lcoal.properties
|
|
||||||
|
|
||||||
# debug dir
|
|
||||||
/run/
|
|
928
.editorconfig
928
.editorconfig
File diff suppressed because it is too large
Load Diff
6
.gitignore
vendored
6
.gitignore
vendored
@ -4,15 +4,13 @@
|
|||||||
.vscode/
|
.vscode/
|
||||||
.gradle/
|
.gradle/
|
||||||
.settings/
|
.settings/
|
||||||
|
/src/test/java/test/*
|
||||||
|
/src/test/resources/test/*
|
||||||
|
|
||||||
#build
|
#build
|
||||||
/build/
|
/build/
|
||||||
/bin/
|
/bin/
|
||||||
/out/
|
|
||||||
.metals/
|
|
||||||
.bloop/
|
|
||||||
.project
|
.project
|
||||||
lcoal.properties
|
|
||||||
|
|
||||||
# debug dir
|
# debug dir
|
||||||
/run/
|
/run/
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "_book"]
|
||||||
|
path = _book
|
||||||
|
url = https://storage.sukazyo.cc/Eyre_S/morny-book.git
|
21
Dockerfile
21
Dockerfile
@ -1,21 +0,0 @@
|
|||||||
FROM eclipse-temurin:20-jdk as build
|
|
||||||
LABEL authors="A.C.Sukazyo Eyre"
|
|
||||||
|
|
||||||
COPY . /app/source/
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN cd ./source \
|
|
||||||
&& ./gradlew shadowJar -PdockerBuild \
|
|
||||||
&& cd .. \
|
|
||||||
&& cp ./source/build/libs/morny-coeur-docker-build.jar ./morny-coeur.jar
|
|
||||||
#&& rm -r ./source \
|
|
||||||
#&& rm -r /root/.gradle \
|
|
||||||
|
|
||||||
|
|
||||||
FROM eclipse-temurin:20-jre
|
|
||||||
|
|
||||||
COPY --from=build /app/morny-coeur.jar /app/morny-coeur.jar
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENTRYPOINT ["java", "-jar", "morny-coeur.jar"]
|
|
||||||
CMD ["-q", "-v"]
|
|
16
README.md
16
README.md
@ -4,22 +4,19 @@
|
|||||||
[todo]: https://github.com/users/Eyre-S/projects/1
|
[todo]: https://github.com/users/Eyre-S/projects/1
|
||||||
[artifact]: https://mvn.sukazyo.cc/#/releases/cc/sukazyo/morny-coeur
|
[artifact]: https://mvn.sukazyo.cc/#/releases/cc/sukazyo/morny-coeur
|
||||||
|
|
||||||
[scala]: https://www.scala-lang.org/
|
|
||||||
[spotbugs]: https://spotbugs.github.io/
|
|
||||||
[tg4j]: https://github.com/pengrad/java-telegram-bot-api
|
[tg4j]: https://github.com/pengrad/java-telegram-bot-api
|
||||||
[okhttp]: https://square.github.io/okhttp/
|
[spotbugs]: https://spotbugs.github.io/
|
||||||
[gson]: https://github.com/google/gson
|
[junit5]: https://junit.org/junit5/
|
||||||
[scalatest]: https://scalatest.org/
|
|
||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
|
|
||||||
# ~~给所有喜欢morny的大家的~~ Morny Coeur 源代码
|
# ~~给所有喜欢morny的大家的~~ Morny Coeur 源代码
|
||||||
|
|
||||||
~~"and nobody cares."~~
|
~~"你们又有意见又不发issue这样子我很为难的啊"~~
|
||||||
|
|
||||||
![social preview card](morny-github-social-preview-card@0.75x.png)
|
![social preview card](morny-github-social-preview-card@0.75x.png)
|
||||||
|
|
||||||
一个 telegram 上的服侍 A.C.Sukazyo Eyre 和它的花宫成员的 bot 内核
|
一个 telegram 上的服侍 A.C.Sukazyo Eyre 和它的花宫成员的 bot 的内核源
|
||||||
|
|
||||||
[Task Listing][todo] | [~~BBS~~][issues] | [Published][artifact]
|
[Task Listing][todo] | [~~BBS~~][issues] | [Published][artifact]
|
||||||
|
|
||||||
@ -35,9 +32,6 @@
|
|||||||
|
|
||||||
[Java Telegram Bot API][tg4j]
|
[Java Telegram Bot API][tg4j]
|
||||||
|
|
||||||
|
[SpotBugs Annotations][spotbugs] | [JUnit 5][junit5]
|
||||||
[okhttp] | [Gson][gson]
|
|
||||||
|
|
||||||
[Scala][scala] | [SpotBugs Annotations][spotbugs] | [ScalaTest][scalatest]
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
1
_book
Submodule
1
_book
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 3072bcee8e498e87ecdd36958185ad423e80bcf3
|
226
build.gradle
226
build.gradle
@ -1,72 +1,16 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'scala'
|
id 'java'
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'application'
|
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id "io.github.ysohda.scalatest" version "0.32.1"
|
id 'application'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||||
id 'com.github.gmazzo.buildconfig' version '4.1.2'
|
|
||||||
id 'org.ajoberstar.grgit' version '5.2.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import org.ajoberstar.grgit.Status
|
group 'cc.sukazyo'
|
||||||
|
version VERSION
|
||||||
import java.nio.charset.Charset
|
project.ext.archiveBaseName = 'Coeur_Morny_Cono'
|
||||||
import java.nio.charset.StandardCharsets
|
project.ext.artifactId = 'morny-coeur'
|
||||||
|
mainClassName = 'cc.sukazyo.cono.morny.ServerMain'
|
||||||
final boolean proj_git = grgit!=null
|
|
||||||
final String proj_store = MORNY_CODE_STORE
|
|
||||||
final String proj_commit = proj_git ? grgit.head().id : null
|
|
||||||
final String proj_commit_path = MORNY_COMMIT_PATH
|
|
||||||
final boolean proj_clean = isCleanBuild()
|
|
||||||
if (!proj_git)
|
|
||||||
println "[MornyBuild] git repository not available for current working space! git version tag will be disabled."
|
|
||||||
else if (isCleanBuild()) {
|
|
||||||
println "git: clean build at ${grgit.head().id}"
|
|
||||||
} else {
|
|
||||||
final Status status = grgit.status()
|
|
||||||
println "git: non-clean-build"
|
|
||||||
if (!status.unstaged.allChanges.empty) {
|
|
||||||
println "git: unstaged changes"
|
|
||||||
listChanges(status.unstaged)
|
|
||||||
}
|
|
||||||
if (!status.staged.allChanges.empty) {
|
|
||||||
println "git: staged changes"
|
|
||||||
listChanges(status.staged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final String proj_group = 'cc.sukazyo'
|
|
||||||
final String proj_package = "${proj_group}.cono.morny"
|
|
||||||
final String proj_archive_name = MORNY_ARCHIVE_NAME
|
|
||||||
final String proj_application_main = "${proj_package}.ServerMain"
|
|
||||||
|
|
||||||
final String proj_version_base = VERSION
|
|
||||||
final String proj_version_delta = VERSION_DELTA
|
|
||||||
final boolean proj_version_use_delta = Boolean.parseBoolean(USE_DELTA)
|
|
||||||
final String proj_version = proj_version_base + (proj_version_use_delta ? "-δ${proj_version_delta}" : "")
|
|
||||||
final String proj_version_full = proj_version + (proj_git ? "+git.${proj_commit.substring(0, 8)}" + (proj_clean?"":".δ") : "")
|
|
||||||
final String proj_version_codename = CODENAME
|
|
||||||
final long proj_code_time = proj_clean ? grgit.head().dateTime.toInstant().toEpochMilli() : System.currentTimeMillis()
|
|
||||||
|
|
||||||
final JavaVersion proj_java = JavaVersion.VERSION_17
|
|
||||||
final Charset proj_file_encoding = StandardCharsets.UTF_8
|
|
||||||
final proj_scala_api = 3
|
|
||||||
//final proj_scala_lib = proj_scala_api+'.4.0-RC1-bin-20230901-89e8dba-NIGHTLY'
|
|
||||||
final proj_scala_lib = proj_scala_api+'.3.1'
|
|
||||||
String publish_local_url = null
|
|
||||||
String publish_remote_url = null
|
|
||||||
String publish_remote_username = null
|
|
||||||
String publish_remote_password = null
|
|
||||||
if (project.hasProperty("publishLocalArchiveRepoUrl")) publish_local_url = publishLocalArchiveRepoUrl
|
|
||||||
if (project.hasProperty("publishMvnRepoUrl")) {
|
|
||||||
publish_remote_url = publishMvnRepoUrl
|
|
||||||
publish_remote_username = publishMvnRepoUsername
|
|
||||||
publish_remote_password = publishMvnRepoPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
group proj_group
|
|
||||||
version proj_version_full
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -75,142 +19,86 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
api "org.scala-lang:scala3-library_3:${proj_scala_lib}"
|
compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${libSpotbugsVersion}"
|
||||||
compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${lib_spotbugs_v}"
|
|
||||||
|
|
||||||
implementation "cc.sukazyo:messiva:${lib_messiva_v}"
|
api "cc.sukazyo:messiva:${libMessivaVersion}"
|
||||||
implementation "cc.sukazyo:resource-tools:${lib_resourcetools_v}"
|
|
||||||
|
|
||||||
implementation "com.github.pengrad:java-telegram-bot-api:${lib_javatelegramapi_v}"
|
implementation "com.github.pengrad:java-telegram-bot-api:${libJavaTelegramBotApiVersion}"
|
||||||
implementation "com.squareup.okhttp3:okhttp:${lib_okhttp_v}"
|
|
||||||
implementation "com.google.code.gson:gson:${lib_gson_v}"
|
|
||||||
|
|
||||||
testImplementation "org.scalatest:scalatest_$proj_scala_api:${lib_scalatest_v}"
|
testImplementation "org.junit.jupiter:junit-jupiter-api:${libJunitVersion}"
|
||||||
testImplementation "org.scalatest:scalatest-freespec_$proj_scala_api:${lib_scalatest_v}"
|
testImplementation "org.junit.jupiter:junit-jupiter-params:${libJunitVersion}"
|
||||||
testRuntimeOnly "org.scala-lang.modules:scala-xml_$proj_scala_api:${lib_scalamodule_xml_v}"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${libJunitVersion}"
|
||||||
testRuntimeOnly 'com.vladsch.flexmark:flexmark-all:0.64.6' // for generating HTML report // required by gradle-scalatest plugin
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task updateVersionCode {
|
||||||
|
ant.replaceregexp(match:'VERSION = ["a-zA-Z0-9.\\-_+@]+;', replace:"VERSION = \"$project.version\";", flags:'g', byline:true) {
|
||||||
|
fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java')
|
||||||
|
}
|
||||||
|
ant.replaceregexp(match:'CODENAME = ["a-zA-Z0-9]+;', replace:"CODENAME = \"${CODENAME}\";", flags:'g', byline:true) {
|
||||||
|
fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java')
|
||||||
|
}
|
||||||
|
ant.replaceregexp(match:'COMPILE_TIMESTAMP = [0-9]+L;', replace:"COMPILE_TIMESTAMP = ${System.currentTimeMillis()}L;", flags:'g', byline:true) {
|
||||||
|
fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava.dependsOn updateVersionCode
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
|
||||||
|
|
||||||
sourceCompatibility proj_java.getMajorVersion()
|
|
||||||
targetCompatibility proj_java.getMajorVersion()
|
|
||||||
|
|
||||||
options.encoding = proj_file_encoding.name()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(ScalaCompile).configureEach {
|
tasks.withType(JavaCompile) {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
sourceCompatibility proj_java.getMajorVersion()
|
|
||||||
targetCompatibility proj_java.getMajorVersion()
|
|
||||||
|
|
||||||
options.encoding = proj_file_encoding.name()
|
|
||||||
scalaCompileOptions.encoding = proj_file_encoding.name()
|
|
||||||
|
|
||||||
scalaCompileOptions.additionalParameters.add "-language:postfixOps"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Javadoc).configureEach {
|
tasks.withType(Javadoc) {
|
||||||
options.encoding = proj_file_encoding.name()
|
options.encoding = 'UTF-8'
|
||||||
|
options.docEncoding = 'UTF-8'
|
||||||
|
options.charSet = 'UTF-8'
|
||||||
}
|
}
|
||||||
|
|
||||||
//tasks.withType(ScalaDoc).configureEach {
|
tasks.test {
|
||||||
//}
|
useJUnitPlatform()
|
||||||
|
|
||||||
test {
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
mainClass = proj_application_main
|
|
||||||
}
|
|
||||||
|
|
||||||
buildConfig {
|
|
||||||
|
|
||||||
packageName(proj_package)
|
|
||||||
|
|
||||||
buildConfigField('String', 'VERSION', "\"${proj_version}\"")
|
|
||||||
buildConfigField('String', 'VERSION_FULL', "\"${proj_version_full}\"")
|
|
||||||
buildConfigField('String', 'VERSION_BASE', "\"${proj_version_base}\"")
|
|
||||||
buildConfigField('String', 'VERSION_DELTA', proj_version_use_delta ? "\"${proj_version_delta}\"" : "null")
|
|
||||||
buildConfigField('String', 'CODENAME', "\"${proj_version_codename}\"")
|
|
||||||
buildConfigField('long', 'CODE_TIMESTAMP', "${proj_code_time}L")
|
|
||||||
buildConfigField('String', 'COMMIT', proj_git ? "\"${proj_commit}\"" : "null")
|
|
||||||
buildConfigField('boolean', 'CLEAN_BUILD', "${proj_clean}")
|
|
||||||
buildConfigField('String', 'CODE_STORE', proj_store==""?"null":"\"${proj_store}\"")
|
|
||||||
buildConfigField('String', 'COMMIT_PATH', proj_commit_path==""?"null":"\"${proj_commit_path}\"")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(Jar).configureEach {
|
|
||||||
archiveBaseName.set proj_archive_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
|
archiveBaseName.set("${project.ext.archiveBaseName}")
|
||||||
archiveClassifier.set "fat"
|
archiveVersion.set("${project.version}")
|
||||||
|
archiveClassifier.set("fat")
|
||||||
if (project.hasProperty("dockerBuild")) {
|
|
||||||
println "shadow-jar: using docker build name"
|
|
||||||
archiveVersion.set ""
|
|
||||||
archiveClassifier.set "docker-build"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings('GrMethodMayBeStatic')
|
|
||||||
boolean isCleanBuild () {
|
|
||||||
if (grgit == null) return false
|
|
||||||
Set<String> changes = grgit.status().unstaged.allChanges + grgit.status().staged.allChanges
|
|
||||||
for (String file in changes) {
|
|
||||||
if (file.startsWith("src/")) return false
|
|
||||||
if (file == "build.gradle") return false
|
|
||||||
if (file == "gradle.properties") return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
void listChanges (Status.Changes listing) {
|
|
||||||
for (String file in listing.added)
|
|
||||||
println " add: ${file}"
|
|
||||||
for (String file in listing.modified)
|
|
||||||
println " mod: ${file}"
|
|
||||||
for (String file in listing.removed)
|
|
||||||
println " del: ${file}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories{
|
repositories{
|
||||||
if (publish_local_url != null) maven {
|
maven {
|
||||||
name 'archives'
|
name 'builds'
|
||||||
url publish_local_url
|
url publishLocalArchiveRepoUrl
|
||||||
}
|
}
|
||||||
if (publish_remote_url != null) maven {
|
maven {
|
||||||
name '-ws-'
|
name '-ws-'
|
||||||
url publish_remote_url
|
url publishMvnRepoUrl
|
||||||
credentials {
|
credentials {
|
||||||
username publish_remote_username
|
username publishMvnRepoUsername
|
||||||
password publish_remote_password
|
password publishMvnRepoPassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
publications {
|
publications {
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
main (MavenPublication) {
|
main (MavenPublication) {
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
from components.java
|
from components.java
|
||||||
//noinspection GroovyAssignabilityCheck
|
groupId = project.group
|
||||||
groupId = proj_group
|
artifactId = project.ext.artifactId
|
||||||
//noinspection GroovyAssignabilityCheck
|
version = project.version
|
||||||
artifactId = proj_archive_name
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
version = proj_version
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
services:
|
|
||||||
coeur-app:
|
|
||||||
build: .
|
|
||||||
command: -v
|
|
@ -1,28 +1,15 @@
|
|||||||
## Core
|
## Core
|
||||||
|
|
||||||
MORNY_ARCHIVE_NAME = morny-coeur
|
VERSION = 0.8.0.11
|
||||||
|
|
||||||
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
CODENAME = putian
|
||||||
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
|
||||||
|
|
||||||
VERSION = 1.0.0
|
|
||||||
|
|
||||||
USE_DELTA = false
|
|
||||||
VERSION_DELTA =
|
|
||||||
|
|
||||||
CODENAME = beiping
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
|
||||||
lib_spotbugs_v = 4.7.3
|
libSpotbugsVersion = 4.7.2
|
||||||
lib_scalamodule_xml_v = 2.2.0
|
|
||||||
|
|
||||||
lib_messiva_v = 0.1.1
|
libMessivaVersion = 0.1.0.1
|
||||||
lib_resourcetools_v = 0.2.2
|
|
||||||
|
|
||||||
lib_javatelegramapi_v = 6.2.0
|
libJavaTelegramBotApiVersion = 5.6.0
|
||||||
|
|
||||||
lib_okhttp_v = 4.11.0
|
libJunitVersion = 5.9.0
|
||||||
lib_gson_v = 2.10.1
|
|
||||||
|
|
||||||
lib_scalatest_v = 3.2.17
|
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
0
gradlew.bat
vendored
Executable file → Normal file
0
gradlew.bat
vendored
Executable file → Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the final field that will be updated by gradle automatically.
|
||||||
|
*/
|
||||||
|
public class GradleProjectConfigures {
|
||||||
|
public static final String VERSION = "0.8.0.11";
|
||||||
|
public static final String CODENAME = "putian";
|
||||||
|
public static final long COMPILE_TIMESTAMP = 1667376095614L;
|
||||||
|
}
|
18
src/main/java/cc/sukazyo/cono/morny/Log.java
Normal file
18
src/main/java/cc/sukazyo/cono/morny/Log.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
import cc.sukazyo.messiva.Logger;
|
||||||
|
import cc.sukazyo.messiva.appender.ConsoleAppender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morny 的 log 管理器
|
||||||
|
*/
|
||||||
|
public class Log {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morny 的 Logger 实例,
|
||||||
|
* messiva 更新
|
||||||
|
* @since 0.4.1.1
|
||||||
|
*/
|
||||||
|
public static final Logger logger = new Logger(new ConsoleAppender());
|
||||||
|
|
||||||
|
}
|
320
src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java
Normal file
320
src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.OnUpdate;
|
||||||
|
import cc.sukazyo.cono.morny.bot.command.MornyCommands;
|
||||||
|
import cc.sukazyo.cono.morny.bot.event.EventListeners;
|
||||||
|
import cc.sukazyo.cono.morny.bot.query.MornyQueries;
|
||||||
|
import cc.sukazyo.cono.morny.daemon.MornyDaemons;
|
||||||
|
import cc.sukazyo.cono.morny.daemon.TrackerDataManager;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.ExtraAction;
|
||||||
|
import com.pengrad.telegrambot.TelegramBot;
|
||||||
|
import com.pengrad.telegrambot.impl.FileApi;
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
import com.pengrad.telegrambot.request.GetMe;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morny Cono 核心<br>
|
||||||
|
* - 的程序化入口类,保管着 morny 的核心属性<br>
|
||||||
|
*/
|
||||||
|
public class MornyCoeur {
|
||||||
|
|
||||||
|
/** 当前程序的 Morny Coeur 实例 */
|
||||||
|
private static MornyCoeur INSTANCE;
|
||||||
|
|
||||||
|
/** 当前 Morny 的{@link MornyTrusted 信任验证机}实例 */
|
||||||
|
private final MornyTrusted trusted;
|
||||||
|
/** 当前 Morny 的 telegram 命令管理器 */
|
||||||
|
private final MornyCommands commandManager = new MornyCommands();
|
||||||
|
private final MornyQueries queryManager = new MornyQueries();
|
||||||
|
|
||||||
|
/** morny 的 bot 账户 */
|
||||||
|
private final TelegramBot account;
|
||||||
|
private final ExtraAction extraActionInstance;
|
||||||
|
private final boolean isRemoveCommandListWhenExit;
|
||||||
|
/**
|
||||||
|
* morny 的 bot 账户的用户名<br>
|
||||||
|
* <br>
|
||||||
|
* 这个字段将会在登陆成功后赋值为登录到的 bot 的 username。
|
||||||
|
* 它应该是和 {@link #account} 的 username 同步的<br>
|
||||||
|
* <br>
|
||||||
|
* 如果在登陆之前就定义了此字段,则登陆代码会验证登陆的 bot 的 username
|
||||||
|
* 是否与定义的 username 符合。如果不符合则会报错。
|
||||||
|
*/
|
||||||
|
public final String username;
|
||||||
|
/**
|
||||||
|
* morny 的 bot 账户的 telegram id<br>
|
||||||
|
* <br>
|
||||||
|
* 这个字段将会在登陆成功后赋值为登录到的 bot 的 id。
|
||||||
|
*/
|
||||||
|
public final long userid;
|
||||||
|
/**
|
||||||
|
* morny 的事件忽略前缀时间<br>
|
||||||
|
* <br>
|
||||||
|
* {@link cc.sukazyo.cono.morny.bot.event.OnUpdateTimestampOffsetLock}
|
||||||
|
* 会根据这里定义的时间戳取消掉比此时间更早的事件链
|
||||||
|
*/
|
||||||
|
public final long latestEventTimestamp;
|
||||||
|
/**
|
||||||
|
* morny 主程序启动时间<br>
|
||||||
|
* 用于统计数据
|
||||||
|
*/
|
||||||
|
public static final long coeurStartTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
public static final long DINNER_CHAT_ID = -1001707106392L;
|
||||||
|
|
||||||
|
private record LogInResult(TelegramBot account, String username, long userid) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 bot 初始化
|
||||||
|
*
|
||||||
|
* @param botKey bot 的 telegram bot api token
|
||||||
|
* @param botUsername bot 的 username 限定。如果为 null 则表示不限定,
|
||||||
|
* 如果指定,则登录时会检查所登陆的 bot 的用户名是否与此相等
|
||||||
|
* @param master morny 实例所信任的主人的 id。用于初始化 {@link #trusted}
|
||||||
|
* @param trustedChat morny 实例所信任的群组的 id。用于初始化 {@link #trusted}
|
||||||
|
* @param latestEventTimestamp 事件处理器会处理事件的最早时间戳 ——
|
||||||
|
* 只有限定的 message 事件会受此影响。
|
||||||
|
* 单位为毫秒
|
||||||
|
*/
|
||||||
|
private MornyCoeur (
|
||||||
|
@Nullable String botApi, @Nullable String botApi4File,
|
||||||
|
@Nonnull String botKey, @Nullable String botUsername,
|
||||||
|
long master, long trustedChat, Set<Long> trustedRDinner,
|
||||||
|
long latestEventTimestamp,
|
||||||
|
boolean isRemoveCommandListWhenExit
|
||||||
|
) {
|
||||||
|
|
||||||
|
this.latestEventTimestamp = latestEventTimestamp;
|
||||||
|
this.isRemoveCommandListWhenExit = isRemoveCommandListWhenExit;
|
||||||
|
configureSafeExit();
|
||||||
|
|
||||||
|
logger.info("args key:\n " + botKey);
|
||||||
|
if (botUsername != null) {
|
||||||
|
logger.info("login as:\n " + botUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final LogInResult loginResult = login(botApi, botApi4File, botKey, botUsername);
|
||||||
|
this.account = loginResult.account;
|
||||||
|
this.username = loginResult.username;
|
||||||
|
this.userid = loginResult.userid;
|
||||||
|
this.trusted = new MornyTrusted(master, trustedChat, trustedRDinner);
|
||||||
|
StringBuilder trustedReadersDinnerIds = new StringBuilder();
|
||||||
|
trusted.getTrustedReadersOfDinnerSet().forEach(id -> trustedReadersDinnerIds.append("\n ").append(id));
|
||||||
|
logger.info(String.format("""
|
||||||
|
trusted param set:
|
||||||
|
- master (id)
|
||||||
|
%d
|
||||||
|
- trusted chat (id)
|
||||||
|
%d
|
||||||
|
- trusted reader-of-dinner (id)%s""",
|
||||||
|
master, trustedChat, trustedReadersDinnerIds
|
||||||
|
));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
RuntimeException ex = new RuntimeException("Cannot login to bot/api. :\n " + e.getMessage());
|
||||||
|
logger.error(ex.getMessage());
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.extraActionInstance = ExtraAction.as(account);
|
||||||
|
|
||||||
|
logger.info("Bot login succeed.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向外界暴露的 morny 初始化入口.
|
||||||
|
* <p>
|
||||||
|
* 如果 morny 已经初始化,则不会进行初始化,抛出错误消息并直接退出方法。
|
||||||
|
*
|
||||||
|
* @see #MornyCoeur 程序初始化方法
|
||||||
|
*/
|
||||||
|
public static void main (
|
||||||
|
@Nullable String botApi, @Nullable String botApi4File,
|
||||||
|
@Nonnull String botKey, @Nullable String botUsername,
|
||||||
|
long master, long trustedChat, Set<Long> trustedRDinner, long latestEventTimestamp,
|
||||||
|
boolean isAutomaticResetCommandList, boolean isRemoveCommandListWhenExit
|
||||||
|
) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
logger.info("Coeur Starting");
|
||||||
|
INSTANCE = new MornyCoeur(
|
||||||
|
botApi, botApi4File,
|
||||||
|
botKey, botUsername,
|
||||||
|
master, trustedChat, trustedRDinner,
|
||||||
|
latestEventTimestamp,
|
||||||
|
isRemoveCommandListWhenExit
|
||||||
|
);
|
||||||
|
MornyDaemons.start();
|
||||||
|
logger.info("start telegram events listening");
|
||||||
|
EventListeners.registerAllListeners();
|
||||||
|
INSTANCE.account.setUpdatesListener(OnUpdate::onNormalUpdate);
|
||||||
|
if (isAutomaticResetCommandList) {
|
||||||
|
logger.info("resetting telegram command list");
|
||||||
|
commandManager().automaticUpdateList();
|
||||||
|
}
|
||||||
|
logger.info("Coeur start complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.error("Coeur already started!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向所有的数据管理器发起保存数据的指令
|
||||||
|
* @since 0.4.3.0
|
||||||
|
*/
|
||||||
|
public void saveDataAll () {
|
||||||
|
TrackerDataManager.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于退出时进行缓存的任务处理等进行安全退出
|
||||||
|
*/
|
||||||
|
private void exitCleanup () {
|
||||||
|
logger.info("clean:save tracker data.");
|
||||||
|
MornyDaemons.stop();
|
||||||
|
if (isRemoveCommandListWhenExit) {
|
||||||
|
commandManager.automaticRemoveList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为程序在虚拟机上添加退出钩子
|
||||||
|
*/
|
||||||
|
private void configureSafeExit () {
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(this::exitCleanup, "exit-cleaning"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录 bot<br>
|
||||||
|
* <br>
|
||||||
|
* 会反复尝试三次进行登录。如果登录失败,则会直接抛出 RuntimeException 结束处理。
|
||||||
|
* 会通过 GetMe 动作验证是否连接上了 telegram api 服务器,
|
||||||
|
* 同时也要求登录获得的 username 和 {@link #username} 声明值相等
|
||||||
|
*
|
||||||
|
* @param api bot client 将会连接到的 telegram bot api 位置
|
||||||
|
* @param api4File bot client 将会连接到的 telegram file api 位置,如果不指定则会跟随 {@code api} 选项的设定
|
||||||
|
* @param key bot 的 api-token
|
||||||
|
* @param requireName 要求登录到的需要的 username,如果登陆后的 username 与此不同则会报错退出
|
||||||
|
* @return 成功登录后的 {@link TelegramBot} 对象
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
private static LogInResult login (
|
||||||
|
@Nullable String api, @Nullable String api4File,
|
||||||
|
@Nonnull String key, @Nullable String requireName
|
||||||
|
) {
|
||||||
|
final TelegramBot.Builder accountConfig = new TelegramBot.Builder(key);
|
||||||
|
boolean isCustomApi = false;
|
||||||
|
String apiUrlSet = "https://api.telegram.org/bot";
|
||||||
|
String api4FileUrlSet = FileApi.FILE_API;
|
||||||
|
if (api != null) {
|
||||||
|
api = api.endsWith("/") ? api.substring(0, api.length() - 1) : api;
|
||||||
|
accountConfig.apiUrl(apiUrlSet = api.endsWith("/bot")? api : api + "/bot");
|
||||||
|
isCustomApi = true;
|
||||||
|
}
|
||||||
|
if (api4File != null) {
|
||||||
|
api4File = api4File.endsWith("/") ? api4File : api4File + "/";
|
||||||
|
accountConfig.fileApiUrl(api4FileUrlSet = api4File.endsWith("/file/bot")? api4File : api4File + "/file/bot");
|
||||||
|
isCustomApi = true;
|
||||||
|
} else if (api != null && !api.endsWith("/bot")) {
|
||||||
|
accountConfig.fileApiUrl(api4FileUrlSet = api + "/file/bot");
|
||||||
|
}
|
||||||
|
if (isCustomApi) {
|
||||||
|
logger.info(String.format("""
|
||||||
|
Telegram Bot API set to :
|
||||||
|
- %s
|
||||||
|
- %s""",
|
||||||
|
apiUrlSet, api4FileUrlSet
|
||||||
|
));
|
||||||
|
}
|
||||||
|
final TelegramBot account = accountConfig.build();
|
||||||
|
logger.info("Trying to login...");
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
if (i != 1) logger.info("retrying...");
|
||||||
|
try {
|
||||||
|
final User remote = account.execute(new GetMe()).user();
|
||||||
|
if (requireName != null && !requireName.equals(remote.username()))
|
||||||
|
throw new RuntimeException("Required the bot @" + requireName + " but @" + remote.username() + " logged in!");
|
||||||
|
logger.info("Succeed login to @" + remote.username());
|
||||||
|
return new LogInResult(account, remote.username(), remote.id());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
logger.error("login failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Login failed..");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #saveDataAll()
|
||||||
|
* @since 0.4.3.0
|
||||||
|
*/
|
||||||
|
public static void callSaveData () {
|
||||||
|
INSTANCE.saveDataAll();
|
||||||
|
logger.info("done all save action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录成功后的 telegram bot 对象
|
||||||
|
*
|
||||||
|
* @return {@link #account MornyCoeur.account}
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static TelegramBot getAccount () {
|
||||||
|
return INSTANCE.account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录 bot 的 username
|
||||||
|
*
|
||||||
|
* @return {@link #username MornyCoeur.username}
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static String getUsername () {
|
||||||
|
return INSTANCE.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 获取忽略时间点
|
||||||
|
*
|
||||||
|
* @return {@link #latestEventTimestamp MornyCoeur.latestEventTimestamp}
|
||||||
|
*/
|
||||||
|
public static long getLatestEventTimestamp () {
|
||||||
|
return INSTANCE.latestEventTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Morny 的{@link MornyTrusted 信任验证机}
|
||||||
|
*
|
||||||
|
* @return {@link #trusted MornyCoeur.trusted}
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static MornyTrusted trustedInstance () {
|
||||||
|
return INSTANCE.trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static MornyCommands commandManager () {
|
||||||
|
return INSTANCE.commandManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static MornyQueries queryManager () {
|
||||||
|
return INSTANCE.queryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static ExtraAction extra () {
|
||||||
|
return INSTANCE.extraActionInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getUserid () { return INSTANCE.userid; }
|
||||||
|
|
||||||
|
}
|
70
src/main/java/cc/sukazyo/cono/morny/MornyHello.java
Normal file
70
src/main/java/cc/sukazyo/cono/morny/MornyHello.java
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link #MORNY_PREVIEW_IMAGE_ASCII} 静态数据存放类
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public class MornyHello {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统的开屏欢迎语 ASCII 字符画字段
|
||||||
|
*/
|
||||||
|
public static final String MORNY_PREVIEW_IMAGE_ASCII = """
|
||||||
|
ttt///t/////fucj(\\tvnxtf{< .' .. .:i` . . ^!`l|-^i+,!_[:1/|{i?//\\//jf\\\\\\///\\\\\\\\//\\\\\\//////\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\//\\\\\\\\/\\\\\\\\/\\\\//\\\\\\///\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\fnncvvU0O00QCx!!". .. ` \s
|
||||||
|
tt//////////\\jzjrucnjt/?{j,,"' . .' .. .":. .;{: ' "`.,1(<."i?)\\(-}\\\\\\(((\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///\\//////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|\\\\\\\\\\\\///\\\\///\\\\\\\\\\\\\\\\|\\\\\\\\\\\\|\\\\\\\\\\\\\\\\tvXvuXcxn/[<!l~<` `I`. \s
|
||||||
|
tt//////t////\\//|rvx//\\(((-;,''" ",.,II..' `. . ^"' . .` .. .. .:: ```!],";";;^ "!?)/_ :li~)1[;<li<(\\1(1;+||\\||\\\\\\\\\\\\\\\\\\||\\\\\\||\\\\\\\\\\///\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\||\\\\\\\\\\\\\\\\//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\/\\\\||\\\\\\\\\\\\\\\\\\\\\\\\||||\\\\//t{}[!>Il)({_:.. ."` .,\s
|
||||||
|
//////////////////\\////|)/([}-_<+[]>.^^""[<'`^` .''""`'.`'`"i! ^!>l:' :<" !!.IiI`+l^^`i>_<`??)1;^{\\\\\\\\\\{|({({|/\\]I)\\\\()\\(]}|\\\\||\\|||\\/\\\\\\\\\\\\|||\\\\\\\\\\\\\\\\//\\\\\\\\/\\\\\\\\||\\\\\\\\\\\\\\\\//\\\\\\\\\\\\\\\\\\\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\//\\\\\\\\\\\\\\\\\\\\\\\\////\\\\\\\\\\\\\\\\\\//|{{?{|)[[-;
|
||||||
|
ttt/tt//////////////////{)(\\t(/tt/1~I}{-1\\_^])1_+[{|(?"<1~>>+!+[}11)}[(1}]};^1\\|~_1}{I:-1(I+)(|))|\\\\/////////\\\\////\\\\\\/////\\\\\\\\\\\\\\\\\\\\\\\\\\\\/\\//\\\\///\\//||\\////|)(//\\\\///){\\/\\(11|///({)//({[1\\\\\\\\\\\\\\\\\\\\|\\/\\\\\\/\\//////////\\\\\\\\\\\\\\\\\\//\\\\\\\\\\///////\\|\\\\\\\\//////\\\\///\\
|
||||||
|
tttt/////////////\\///////\\||///////t//|(|)|}|\\/(\\\\(//(l_{{<i!l}}})(()\\/\\///{{|||\\\\|\\\\((\\\\\\\\\\\\\\\\\\\\\\////\\\\\\\\\\//\\\\/\\////\\///\\\\\\\\\\\\\\\\\\//\\\\\\\\/\\|}[{|1?{|[i:,,;i<i,,,I~}}<<l^":I,`.':-<l!~l~i:!;,l)\\\\/\\//\\/\\\\\\/////\\\\\\//\\\\////\\\\\\\\\\\\\\\\//\\\\\\/\\\\///\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\//
|
||||||
|
//t//////////////\\///////////////////////\\//////////////////\\/\\/////\\\\\\\\\\\\\\//\\\\|||\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\/\\\\\\\\\\\\\\\\\\\\//\\\\/////////\\\\\\\\|))([+)}!<~"^,^.`' .^."~, :` '" `_>. ... ">+<^'I!: ^<(\\\\1}1//\\\\\\//////////\\\\///\\/\\///\\\\\\\\\\\\//\\\\//\\\\\\\\\\\\\\\\\\\\\\\\\\\\///\\(/\\{
|
||||||
|
t////////////////////////////////////////////////////////\\/\\\\///////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\/\\\\\\\\\\\\||\\|\\\\\\\\\\|\\\\\\\\/\\\\|\\\\\\\\\\\\////((|///}!:,":,^`. .;' ' '^..':. ^!;. .^^ '^^`. '' ...I[{!>:^;_i:'~\\ttt/////tt//\\\\////////////\\\\\\\\/\\\\\\\\\\\\/\\\\\\\\/\\\\\\\\\\)}-+[+I??i
|
||||||
|
ttt////////////////////////////////////////////////////////\\\\//\\//\\\\/\\\\\\\\\\\\\\\\\\\\//\\\\\\\\\\\\||\\\\\\/\\//\\\\\\\\\\\\\\\\\\/\\|\\\\\\////\\1;``^;<>+!">__+I `' .. "'. .;" ;;. .:^ ``,,;'` .;]I ,-_-|\\////t////t///////\\/\\\\\\\\//\\\\\\\\\\\\//\\\\\\///////-II<!l_; I
|
||||||
|
/////////////////////////////////////////////////\\\\/\\\\\\\\\\////\\\\//\\\\/////\\///////////tt//\\\\/\\////\\/////t/\\//\\\\///\\]{\\l,`'+< ,i ^i" . .`"l" . .`;?-' .` `>1ttt///tttt/////////\\/\\/////\\\\\\\\////t|+<}?!-]l<{[[1-+]
|
||||||
|
t//////////////////////////////////////\\/////////\\////////////////\\//////\\//tttttttttt//////////////////////////)_)t)|}1f/{<.^,^:~: . .. '''^:-|/> '-/}-_?\\/)-{?(//\\(\\tt////\\///\\\\\\\\\\\\\\///t1.;); .l~` '"
|
||||||
|
///////////////////////////////////////////tt/t(|tt//]+{t\\{][|////\\//////////ttttt///t//t/////////////\\//////|//{[|f}!l<!!I, `' ,!!i+- .`:l;IIll>>~++~<<<!:`. '' '^-l `.,l:.`{{[_:]/1l;>\\//]l~?])tt//\\\\\\\\\\/\\\\///\\\\|?<_}["^!;I^;]:. .
|
||||||
|
////////////////////////////////tttt/|{[1)]~!!+>!<_(/|[-<"i!l,]tt//ttt/t////ttt//t///ttttttt////////t//t//ttt){+. :?^ '. l_-!+l;;;|!!>~~il!lllllllllll!!lI:`'. .' :I;]_}>,?tf:.+fft)l+1//\\~`'I-(//\\/t/|/(-1[)/?>>II:' '.`';-'` \s
|
||||||
|
/////////////////////////t//()\\1_<>il^'''' ,!>;.,.'{tti `~tf(`'-(|fffftttttttttt/tttttttttttt///tttft//(t|]?-+!^ ."`. `. ;!I,. .?{il-\\_!~<>>!lII;IllIIIIIllllllllllI;;:,:,' '"^`(f{+{>' .<{t(I!}/||t> ^(//}>;:1\\]: "[:"` ^<: . II.'.. \s
|
||||||
|
///////////////ttt//tt((-!+}"'^. I, ,?<:' ,:;!>~',!_~{}-1]`^!}_+\\ttttttt/tttttttfff/tt\\(||]-?+;,:"l" '..'.. ?]l:" -(lI;,~?~!IIIlllI:IIlI;IIIIlllllllIIIIIllII!; . . '^^;~), "~!}\\/t//\\\\/_. '</||1?-)/\\\\)+_1>". '_i !i''' \s
|
||||||
|
tt//t///ttt///(]<>l>][l"'.`,. ^.^. ii ;; ~>>>. .i~I'^^<}), .;|tfftttttttttttf\\]}t-!,,I` .^ '. !: . .",I;. ^,I<)/-l:;llllllI;lIll;;IIIIlllIllIlIIlIllI;><. ' .;}". '.:+](ft\\}(t/t{;<\\{l^>}!^l\\/{>1/t(lI:I!+<<". ':" \s
|
||||||
|
t//tttt|?+!I!:' '` .`. ...... `^ "<^.;`^"'`,!".,^^^.,?)!. [f/+>(/tttft\\tff|+^,!' '^: >[,++:`' .I^ . _?!:^. ;~{/<II:IllIlllI;l;IlI;;IIIIIIlIIIIIIllIlllI+- "+;,...<\\;^_(/~}t/(+ ^(/?.:|)il)\\>?//)! __::. ':. '. \s
|
||||||
|
t/\\}[{]",il'`!-<-]:`'^` .. '' .^+:'. .^'"i:`^. ';`:<_|>'.?/t/!"<)ffftf)]]!'II.,l ^' ''. '";" .' `Il, ;]>]j_;lI;ll;!!llIII>~IIIIII;:<iIlIIlII;llllll;(> ;,~.',.<:`, 'I_|\\; .i|/]^ ?(}\\/////\\i' '' ....'^ \s
|
||||||
|
tf1<}i `^. `I` .I?"'. . . ^' .^' .'` .". >}_.I|t{_(tf({~,~(); ')t};.><,. .. . .. . . .]}^{j1IlllIlI!1IlllII?{IIIIIlI;[1!I;IIllIIlIIllI<x" '.!+~!,``...:. 'l]?l"i{/\\><]_;+/t\\(|\\/1,' "` ... \s
|
||||||
|
)+::((:^' ll .,` . . ..'. ' :+'`{tj{,l: ^;"..;!"^.I?' '~; .` .'. . `1+ [x?-:lIlll!]r-IllI~~{~I>lllll[i\\--+;;I~IIIII!l;x] "I"-<<_> >i.' l{}:itf/}[/\\)(\\}))|(:^^..'. `". \s
|
||||||
|
>.._f|i.:l,;^^''__. .^' `' "+<!~)?,,[>,`]1i`!1_. ^l: .". .` '1I +JIt!IIlll;]\\<vlIlI(;I\\i+<iII>) >1}c(_i(!IllI_l;(f. ,_";~~+^ .. .;-i '+([i+: !//1](||/\\(?:^..^^ \s
|
||||||
|
i'"}_,.` ''^... '. `. ^<`_> .. +x??_~]:[|!,.ll` . {+ ;Y[^|,>~IlIIf\\ {/;I!\\ [[-'<+l-{ _??]f\\n]lllI[!;1v` `+"]-}]~" ..'`l, ''i-` l+?\\\\\\/t{!)t[:' .^^ \s
|
||||||
|
;,:: :,^..;:. . i+..;^ `_<!,!~II,`. '' . "t..\\n^!]!i]<II!n> ]]<-?l``-]' I>]?+<l~<!._n_IllI1+i}J: ...l!,'`' `. . .~>. ^-|\\\\_I?]{t/?` .... \s
|
||||||
|
^(\\]I^~?;."!" . .. . ^<^ :( >t) _[il>|+:(U<1nYQ0Xx\\> . .~xcXXYzx(n?IllI}">xCI .:1]_-" . .^. `}>!}((1-^,+?" .. \s
|
||||||
|
1+,~I.<! .`' .. `x"(+1 >(l<?_1}Cj!f\\?l"' '"I~]|_;Ill{ vQ" ':,~_' .. .. .`^[(`,?\\||\\+ '," . \s
|
||||||
|
}<. `^]!.;lI>' . ... ~|r:;`.+I?\\};+t) "".-?;lI>(;]xn. '_>]!+. '^'`l:11l[|((+?: . \s
|
||||||
|
[-`.':;..""' lv|. .:_(;I!u> ^,",^. .;?I]?IlI}n[</( "+'.. .I]; ',;!\\/\\|~I}" '";'. \s
|
||||||
|
.,><I>^ ')(I' `tlII>x1" <}1{)l "~+ |[II;\\[:~zl .i;. .... `"i\\\\}]..!' ^.... \s
|
||||||
|
-" ` ' . ":` .|+<<;!\\U)>^ '^`' ^"I?)c-;<if]>j/ <1I~;` .!}\\(: .;"`' \s
|
||||||
|
' 'l, .' ~[><+;!f()nn|]!:' ..^:!+1fcjx}}v!_)})>|n` ~^ ^;"'` .<+I<)/||\\i'"<"'^ `. \s
|
||||||
|
II ^}_'+_!fI?_/-jJjUr\\\\ucJJz\\|J>}?-j{]^ni<z; : `' .-+~->" .;](),.;-<`' .^ .^' \s
|
||||||
|
+[" +]{.`i;I</-jmvxjj)<^ !0~l-?+` : '" '` .' l\\)-++{f\\" ',^^^ .^ ,!` '' \s
|
||||||
|
.>; ::."!??l.^Ywj}<, (n, ,~_:` .,, ` `` ' '... ^+-l,]}]}\\j/!. . ` 'I<~`'{tl..^` \s
|
||||||
|
'' .,<{[>" i/i" `-[; ,<_[>^i_l,:^_! ',+l.. ^,:,,. ;~>l;^ l> ',;I^???~,'l".. .. \s
|
||||||
|
;{?l. !+ .. .<1i '^' "}|{:-+-;?\\[)-] ^:l1-:. '' '`, . ';.`~^ '. ..^`. \s
|
||||||
|
'i+;]}!,. <))\\!<|ji >((_}}?t)}\\\\v|]?jI!), "lf!l. ... .^ . ". \s
|
||||||
|
.+{>` l/z\\!,>""I+~_){]<t~-(! .!nt_]]]])1)]]\\( ?_' :( . . `:. `; ." ... .. \s
|
||||||
|
Ir. ..>vQjut_~~>>>_-<]<-)f":l_v){\\/1}}}{t/\\0?z~. ^' `-l . . .. . \s
|
||||||
|
l\\ :_>>i:^+\\)_-]!:>-+<?\\Uzfjj-/mxt|[??/f)??}/mc1;. . . . ^' .
|
||||||
|
I1I`l?-}l>l'...`^;1!^ 'l>})l\\n\\Qt?]?]})1{][[(XC>^ ...
|
||||||
|
^-+^.i-((?!"`:>l<[<!tc_:;<+>~<]nQY+?????][{\\cmO||l . ''..
|
||||||
|
'|: '^[{~)\\_+++))1{uxnvt(t){{[[u0\\1|({1()){-?|xfc: .. \s
|
||||||
|
.<-, ]-]]]})11)){{{{}{{)|{}}{1{111{11{{}}]_!"x\\]Xf \s
|
||||||
|
,]<\\}][[[[[[[[[[[[[[[]][[[[[[[[][[[[[[]?-+!YC{z} .`
|
||||||
|
"_[}?]][[[[[[[[[[[[[[[[[[][[[[[[[[[[[[[[[]vn\\?. ^
|
||||||
|
^. ;{_(_??][[[[[[[[[[[[[[[[[|[[[[[[[[[[[[[[[[v_(]^ \s
|
||||||
|
.' '. :t>/?[[[[[[[[[[[[[[[[[[[]t\\][[[[[[[[[[][]?u;()_ .. .'. . .
|
||||||
|
`. .` :)!j_]][[[]]]]][[[[[[[[[[}j(/{[[[[[][[}1{~n!)ft . .. .. . .. .
|
||||||
|
". ..' .` . "' .^":;~ti{\\1]][]]]??]]]]][]]??[){[}[[[[?+}]<I"]({~c' .,",. .. ', .'`I' ^l. \s
|
||||||
|
. .. .'. ..'.... '.;,.'.":`,_: '` . '"^. '<:,'. 'l:`'. . ';"'. .i~I:;,,`.^!{[?[[[[[[[[[[[[[[]?]]]])t11])I;:. '^c^ .. '. . .' .^ .."?, '` \s
|
||||||
|
' '^. `. . I;. . `^ '... ...``'. ,">!^':``^``,`;I.!<>?>:??i:;-;,<_..^,Ii: 'l,i+```' ..'!; ''. ?~.'<+ .li!:,1?}[[[[[[[[[[[[[[[[[[[[)1}]t[:, "O" . .' . ^!' . ". i+' . \s
|
||||||
|
^. . .':`'`~/1,-<-~^'^'^^,`.."i_>^. `1t]!,^I]l^;`,I::_?]?[!:;`.`"'`l!l<1f{~>;]\\1(]I>~l!l[<,,;`lI,~},^>!>l<l[-' I-+:i_!ll::l"`>'...'": 'x' ]l>i .:I1[~]]]]}}}}}[[}}[}}}}}[[]]]??1}}[}~;:>vx. :;..:??,.' ^` I;>.";:"^' .. ^'.^"" ' .!},' .
|
||||||
|
~!;:!".":i"^_/|]^li(\\1;;it{' .[\\fft+<}(/{}/)|f||<tj\\/1//jj[<<~])11?''.`;\\)l!(ff~!\\j1-|\\t\\f\\//||>'.^{/[)?!:(?+-,I+fjtil"'"+fj{i:',!!;!!^:.`r. r; !l'"i1?!i>~+_?][[[?-???]][[]?-+-]??+~<_{[_l?> ''^l;l`-}<`^.i>+l ``;I":+?!~-l ,>>l.'.;. ':!!(/!":,I
|
||||||
|
/t<I^^!{{-;[[-~|1\\jj\\?(/)f{~~+}}//t(?(ft}{tf]j1>1ffft+{jff/ttff)];)?1(/tt\\/t/tfttttfftf/1\\|t\\|/?<_]_]{<_]/f({fffjttf/[i>1//|tft|" :<~:+}, ]>if" .:-~ >) ^`^l)f(_<??)\\}}(t|}>{/\\}-+1\\()t-{j/]!:^'l<]\\)+ ."_?I_{
|
||||||
|
ffft)|)(t[_-{tjjrjrj/{(||}(rjj\\1)I<\\((ffj/rjffttjffftrjfffrtfff/f[1jjffffftt//)}tttff/ttt[<{rj}tf1?<:~{/j)>)fttf|?)tfffftt1_;+tf1-1|~i1, >;:} '1_ ;( .. .. `:"_1{}tjtvj)vjr/|jfff/<(tf)+1/)1j)~~-[j[l|[(/\\j{:-]]([}\\t
|
||||||
|
""";
|
||||||
|
|
||||||
|
}
|
54
src/main/java/cc/sukazyo/cono/morny/MornySystem.java
Normal file
54
src/main/java/cc/sukazyo/cono/morny/MornySystem.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.FileUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morny Cono 的 Coeur 的程序属性存放类
|
||||||
|
*/
|
||||||
|
public class MornySystem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程序的语义化版本号<br>
|
||||||
|
* 会由 gradle 任务 {@code updateVersionCode} 更新
|
||||||
|
*/
|
||||||
|
public static final String VERSION = GradleProjectConfigures.VERSION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morny Coeur 当前的版本代号.<br>
|
||||||
|
* 一个单个单词,一般作为一个大版本的名称,只在重大更新改变<br>
|
||||||
|
* 格式保持为仅由小写字母和数字组成<br>
|
||||||
|
* 有时也可能是复合词或特殊的词句<br>
|
||||||
|
* <br>
|
||||||
|
* 会由 gradle 任务 {@code updateVersionCode} 更新
|
||||||
|
*/
|
||||||
|
public static final String CODENAME = GradleProjectConfigures.CODENAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取程序 jar 文件的 md5-hash 值<br>
|
||||||
|
* <br>
|
||||||
|
* 只支持 jar 文件方式启动的程序 ——
|
||||||
|
* 如果是通过 classpath 来启动,程序无法找到本体jar文件,则会返回 {@code <non-jar-runtime>} 文本
|
||||||
|
* <br>
|
||||||
|
* 值格式为 {@link java.lang.String}
|
||||||
|
*
|
||||||
|
* @return 程序jar文件的 md5-hash 值字符串,或 {@code <non-jar-runtime>} 如果出现错误
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static String getJarMd5() {
|
||||||
|
try {
|
||||||
|
return FileUtils.getMD5Three(MornyCoeur.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
|
||||||
|
} catch (IOException | URISyntaxException e) {
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
return "<non-jar-runtime>";
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
return "<calculation-error>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
src/main/java/cc/sukazyo/cono/morny/MornyTrusted.java
Normal file
58
src/main/java/cc/sukazyo/cono/morny/MornyTrusted.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.ChatMember.Status;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对用户进行身份权限验证的管理类
|
||||||
|
*/
|
||||||
|
public class MornyTrusted {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群聊id,其指向的群聊指示了哪个群的成员是受信任的
|
||||||
|
* @see #isTrusted(long) 受信检查
|
||||||
|
*/
|
||||||
|
public final Long TRUSTED_CHAT_ID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* morny 的主人<br>
|
||||||
|
* 这项值的对象总是会被认为是可信任的
|
||||||
|
*/
|
||||||
|
public final long MASTER;
|
||||||
|
|
||||||
|
private final Set<Long> TRUSTED_READERS_OF_DINNER;
|
||||||
|
|
||||||
|
public MornyTrusted (long master, long trustedChatId, Set<Long> trustedRDinner) {
|
||||||
|
this.TRUSTED_CHAT_ID = trustedChatId;
|
||||||
|
this.MASTER = master;
|
||||||
|
this.TRUSTED_READERS_OF_DINNER = new HashSet<>(){{
|
||||||
|
this.add(master);
|
||||||
|
this.addAll(trustedRDinner);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于检查一个 telegram-user 是否受信任<br>
|
||||||
|
* <br>
|
||||||
|
* 用户需要受信任才能执行一些对程序甚至是宿主环境而言危险的操作,例如关闭程序<br>
|
||||||
|
* <br>
|
||||||
|
* 它的逻辑(目前)是检查群聊 {@link #TRUSTED_CHAT_ID} 中这个用户是否为群组管理员
|
||||||
|
*
|
||||||
|
* @param userId 需要检查的用户的id
|
||||||
|
* @return 所传递的用户id对应的用户是否受信任
|
||||||
|
*/
|
||||||
|
public boolean isTrusted (long userId) {
|
||||||
|
if (userId == MASTER) return true;
|
||||||
|
return MornyCoeur.extra().isUserInGroup(userId, TRUSTED_CHAT_ID, Status.administrator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrustedForDinnerRead (long userId) {
|
||||||
|
return TRUSTED_READERS_OF_DINNER.contains(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Long> getTrustedReadersOfDinnerSet () {
|
||||||
|
return Set.copyOf(TRUSTED_READERS_OF_DINNER);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
249
src/main/java/cc/sukazyo/cono/morny/ServerMain.java
Normal file
249
src/main/java/cc/sukazyo/cono/morny/ServerMain.java
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package cc.sukazyo.cono.morny;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonFormat;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程序启动入口<br>
|
||||||
|
* <br>
|
||||||
|
* 会处理程序传入的参数和选项等数据,并执行对应的启动方式<br>
|
||||||
|
*
|
||||||
|
* @since 0.4.0.0
|
||||||
|
*/
|
||||||
|
public class ServerMain {
|
||||||
|
|
||||||
|
public static final String PROP_TOKEN_KEY = "TELEGRAM_BOT_API_TOKEN";
|
||||||
|
public static final String PROP_TOKEN_MORNY_KEY = "MORNY_TG_TOKEN";
|
||||||
|
|
||||||
|
private static final String THREAD_MORNY_INIT = "morny-init";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程序入口,也是参数处理器<br>
|
||||||
|
* <br>
|
||||||
|
* 以 {@code -} 开头的参数会被解析为选项<br>
|
||||||
|
* <br>
|
||||||
|
* 支持以下选项
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* {@code --version} 只输出版本信息,不运行主程序。此参数会导致其它所有参数失效(优先级最高)
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --only-hello} 只输出欢迎字符画({@link MornyHello}),不运行主程序。
|
||||||
|
* 不要同时使用 {@code --no-hello},原因见下。
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --token} <b>主程序模式的必选项</b><br>
|
||||||
|
* 用于 bot 启动的 telegram bot api token
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --username} {@link MornyCoeur#getUsername() bot 的 username} 预定义
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --api} 设定 {@link MornyCoeur#getAccount() bot client} 使用的 telegram bot api server。
|
||||||
|
* 需要注意的是如果带有后缀 {@code /bot} 则会单独设定 api server
|
||||||
|
* 而不会适应性的同时为 {@code --api-files} 设定值。
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --api-files} 单独设定 {@link MornyCoeur#getAccount() bot client} 使用的 telegram bot file api server
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --no-hello} 不在主程序启动时输出用于欢迎消息的字符画。
|
||||||
|
* 与 {@code --only-hello} 参数不兼容 —— 会导致程序完全没有任何输出
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --outdated-block} 会使得 {@link MornyCoeur#latestEventTimestamp}
|
||||||
|
* 赋值为程序启动的时间,从而造成阻挡程序启动之前的消息事件处理效果。
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --auto-cmd} (下面两个)选项 {@code --auto-cmd-list} 和 {@code --auto-cmd-remove} 的合并版本
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --auto-cmd-list} 使 morny 在启动时自动依据程序本体更新登录 bot 的命令列表
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@code --auto-cmd-remove} 使 morny 在关闭时自动依据程序本体删除 bot 的命令列表
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
* <s>除去选项之外,第一个参数会被赋值为 bot 的 telegram bot api token,</s>
|
||||||
|
* <s>第二个参数会被赋值为 bot 的 username 限定名。其余的参数会被认定为无法理解。</s><br>
|
||||||
|
* <b>自 {@code 0.4.2.3},token 和 username 的赋值已被选项组支持</b><br>
|
||||||
|
* <b>自 {@code 0.5.0.4},旧的直接通过参数为 bot token & username 赋值的方式已被删除</b>
|
||||||
|
* 使用参数所进行取值的 token 和 username 已被转移至 {@code --token} 和 {@code --username} 参数<br>
|
||||||
|
*
|
||||||
|
* @see MornyCoeur#main
|
||||||
|
* @since 0.4.0.0
|
||||||
|
* @param args 参数组
|
||||||
|
*/
|
||||||
|
public static void main (@Nonnull String[] args) {
|
||||||
|
|
||||||
|
//#
|
||||||
|
//# 启动参数设置区块
|
||||||
|
//#
|
||||||
|
|
||||||
|
boolean versionEchoMode = false;
|
||||||
|
boolean welcomeEchoMode = false;
|
||||||
|
boolean showWelcome = true;
|
||||||
|
String key = null;
|
||||||
|
String username = null;
|
||||||
|
boolean outdatedBlock = false;
|
||||||
|
long master = 793274677L;
|
||||||
|
Set<Long> trustedReadersOfDinner = new HashSet<>();
|
||||||
|
long trustedChat = -1001541451710L;
|
||||||
|
boolean autoCmdList = false;
|
||||||
|
boolean autoCmdRemove = false;
|
||||||
|
String api = null;
|
||||||
|
String api4File = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
|
||||||
|
if (args[i].startsWith("-")) {
|
||||||
|
|
||||||
|
switch (args[i]) {
|
||||||
|
case "--outdated-block", "-ob" -> {
|
||||||
|
outdatedBlock = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--no-hello", "-hf", "--quiet", "-q" -> {
|
||||||
|
showWelcome = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--only-hello", "-ho", "-o", "-hi" -> {
|
||||||
|
welcomeEchoMode = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--version", "-v" -> {
|
||||||
|
versionEchoMode = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--token", "-t" -> {
|
||||||
|
i++;
|
||||||
|
key = args[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--username", "-u" -> {
|
||||||
|
i++;
|
||||||
|
username = args[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--master", "-mm" -> {
|
||||||
|
i++;
|
||||||
|
master = Long.parseLong(args[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--trusted-chat", "-trs" -> {
|
||||||
|
i++;
|
||||||
|
trustedChat = Long.parseLong(args[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//noinspection SpellCheckingInspection
|
||||||
|
case "--trusted-reader-dinner", "-trsd" -> {
|
||||||
|
i++;
|
||||||
|
trustedReadersOfDinner.add(Long.parseLong(args[i]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--auto-cmd", "-cmd", "-c" -> {
|
||||||
|
autoCmdList = true;
|
||||||
|
autoCmdRemove = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--auto-cmd-list", "-ca" -> {
|
||||||
|
autoCmdList = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--auto-cmd-remove", "-cr" -> {
|
||||||
|
autoCmdRemove = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--api", "-a" -> {
|
||||||
|
i++;
|
||||||
|
api = args[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case "--api-files", "files-api", "-af" -> {
|
||||||
|
i++;
|
||||||
|
api4File = args[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("Can't understand arg to some meaning :\n " + args[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
String propToken = null;
|
||||||
|
String propTokenKey = null;
|
||||||
|
for (String iKey : new String[]{PROP_TOKEN_KEY, PROP_TOKEN_MORNY_KEY}) {
|
||||||
|
if (System.getenv(iKey) != null) {
|
||||||
|
propToken = System.getenv(iKey);
|
||||||
|
propTokenKey = iKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#
|
||||||
|
//# 启动相关参数的检查和处理
|
||||||
|
//#
|
||||||
|
|
||||||
|
if (versionEchoMode) {
|
||||||
|
|
||||||
|
logger.info(String.format("""
|
||||||
|
Morny Cono Version
|
||||||
|
- version :
|
||||||
|
%s %s
|
||||||
|
- md5hash :
|
||||||
|
%s
|
||||||
|
- co.time :
|
||||||
|
%d
|
||||||
|
%s [UTC]""",
|
||||||
|
MornySystem.VERSION, MornySystem.CODENAME.toUpperCase(),
|
||||||
|
MornySystem.getJarMd5(),
|
||||||
|
GradleProjectConfigures.COMPILE_TIMESTAMP,
|
||||||
|
CommonFormat.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showWelcome) logger.info(MornyHello.MORNY_PREVIEW_IMAGE_ASCII);
|
||||||
|
if (welcomeEchoMode) return;
|
||||||
|
|
||||||
|
logger.info(String.format("""
|
||||||
|
ServerMain.java Loaded >>>
|
||||||
|
- version %s (%s)(%d)
|
||||||
|
- Morny %s""",
|
||||||
|
MornySystem.VERSION,
|
||||||
|
MornySystem.getJarMd5(), GradleProjectConfigures.COMPILE_TIMESTAMP,
|
||||||
|
MornySystem.CODENAME.toUpperCase()
|
||||||
|
));
|
||||||
|
|
||||||
|
//#
|
||||||
|
//# Coeur 参数检查和正式启动主程序
|
||||||
|
//#
|
||||||
|
|
||||||
|
if (propToken != null) {
|
||||||
|
key = propToken;
|
||||||
|
logger.info("Parameter <token> set by EnvVar $"+propTokenKey);
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
logger.info("Parameter required has no value:\n --token.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Thread.currentThread().setName(THREAD_MORNY_INIT);
|
||||||
|
MornyCoeur.main(
|
||||||
|
api, api4File,
|
||||||
|
key, username,
|
||||||
|
master, trustedChat, trustedReadersOfDinner,
|
||||||
|
outdatedBlock?System.currentTimeMillis():0,
|
||||||
|
autoCmdList, autoCmdRemove
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.api;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class EventListener {
|
||||||
|
|
||||||
|
public boolean onMessage (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onEditedMessage (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onChannelPost (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onEditedChannelPost (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onInlineQuery (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onChosenInlineResult (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onCallbackQuery (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onShippingQuery (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onPreCheckoutQuery (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onPoll (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onPollAnswer (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onMyChatMemberUpdated (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onChatMemberUpdated (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onChatJoinRequest (@Nonnull Update update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.api;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class EventListenerManager {
|
||||||
|
|
||||||
|
private static final List<EventListener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private static class EventPublisher extends Thread {
|
||||||
|
|
||||||
|
private final Function<EventListener, Boolean> exec;
|
||||||
|
|
||||||
|
public EventPublisher(@Nonnull Update update, @Nonnull Function<EventListener, Boolean> exec) {
|
||||||
|
this.setName("EVT"+update.updateId());
|
||||||
|
this.exec = exec;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run () {
|
||||||
|
for (EventListener x : listeners) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (exec.apply(x)) return;
|
||||||
|
|
||||||
|
} catch (EventRuntimeException e) {
|
||||||
|
|
||||||
|
final StringBuilder errorMessage = new StringBuilder();
|
||||||
|
errorMessage.append("Event runtime breaks: " + e.getMessage()).append('\n');
|
||||||
|
errorMessage.append("at " + e.getStackTrace()[0].toString()).append('\n');
|
||||||
|
errorMessage.append("at " + e.getStackTrace()[1].toString()).append('\n');
|
||||||
|
errorMessage.append("at " + e.getStackTrace()[2].toString()).append('\n');
|
||||||
|
errorMessage.append("at " + e.getStackTrace()[3].toString()).append('\n');
|
||||||
|
if (e instanceof EventRuntimeException.ActionFailed) {
|
||||||
|
errorMessage.append((
|
||||||
|
"\"telegram request track\": " +
|
||||||
|
new GsonBuilder().setPrettyPrinting().create().toJson(((EventRuntimeException.ActionFailed)e).getResponse())
|
||||||
|
).indent(4)).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(errorMessage.toString());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
logger.error("Event Error!");
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addListener (@Nonnull EventListener... listeners) {
|
||||||
|
EventListenerManager.listeners.addAll(Arrays.asList(listeners));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishMessageEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onMessage(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishEditedMessageEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onEditedMessage(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishChannelPostEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onChannelPost(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishEditedChannelPostEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onEditedChannelPost(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishInlineQueryEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onInlineQuery(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishChosenInlineResultEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onChosenInlineResult(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishCallbackQueryEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onCallbackQuery(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishShippingQueryEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onShippingQuery(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishPreCheckoutQueryEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onPreCheckoutQuery(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishPollEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onPoll(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishPollAnswerEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onPollAnswer(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishMyChatMemberUpdatedEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onMyChatMemberUpdated(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishChatMemberUpdatedEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onChatMemberUpdated(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void publishChatJoinRequestEvent (@Nonnull Update update) {
|
||||||
|
new EventPublisher(update, x -> x.onChatJoinRequest(update)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.api;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.request.InlineQueryResult;
|
||||||
|
|
||||||
|
public class InlineQueryUnit<T extends InlineQueryResult<T>> {
|
||||||
|
|
||||||
|
public static final int DEFAULT_INLINE_CACHE_TIME = 300;
|
||||||
|
public static final boolean DEFAULT_INLINE_PERSONAL_RESP = false;
|
||||||
|
|
||||||
|
private int cacheTime = DEFAULT_INLINE_CACHE_TIME;
|
||||||
|
private boolean isPersonal = DEFAULT_INLINE_PERSONAL_RESP;
|
||||||
|
public final T result;
|
||||||
|
|
||||||
|
public InlineQueryUnit (T result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int cacheTime () {
|
||||||
|
return cacheTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InlineQueryUnit<T> cacheTime (int cacheTime) {
|
||||||
|
this.cacheTime = cacheTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPersonal () {
|
||||||
|
return isPersonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InlineQueryUnit<T> isPersonal (boolean isPersonal) {
|
||||||
|
this.isPersonal = isPersonal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
src/main/java/cc/sukazyo/cono/morny/bot/api/OnUpdate.java
Normal file
59
src/main/java/cc/sukazyo/cono/morny/bot/api/OnUpdate.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.api;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.UpdatesListener;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class OnUpdate {
|
||||||
|
|
||||||
|
public static int onNormalUpdate (@Nonnull List<Update> updates) {
|
||||||
|
for (Update update : updates) {
|
||||||
|
if (update.message() != null) {
|
||||||
|
EventListenerManager.publishMessageEvent(update);
|
||||||
|
}
|
||||||
|
if (update.editedMessage() != null) {
|
||||||
|
EventListenerManager.publishEditedMessageEvent(update);
|
||||||
|
}
|
||||||
|
if (update.channelPost() != null) {
|
||||||
|
EventListenerManager.publishChannelPostEvent(update);
|
||||||
|
}
|
||||||
|
if (update.editedChannelPost() != null) {
|
||||||
|
EventListenerManager.publishEditedChannelPostEvent(update);
|
||||||
|
}
|
||||||
|
if (update.inlineQuery() != null) {
|
||||||
|
EventListenerManager.publishInlineQueryEvent(update);
|
||||||
|
}
|
||||||
|
if (update.chosenInlineResult() != null) {
|
||||||
|
EventListenerManager.publishChosenInlineResultEvent(update);
|
||||||
|
}
|
||||||
|
if (update.callbackQuery() != null) {
|
||||||
|
EventListenerManager.publishCallbackQueryEvent(update);
|
||||||
|
}
|
||||||
|
if (update.shippingQuery() != null) {
|
||||||
|
EventListenerManager.publishShippingQueryEvent(update);
|
||||||
|
}
|
||||||
|
if (update.preCheckoutQuery() != null) {
|
||||||
|
EventListenerManager.publishPreCheckoutQueryEvent(update);
|
||||||
|
}
|
||||||
|
if (update.poll() != null) {
|
||||||
|
EventListenerManager.publishPollEvent(update);
|
||||||
|
}
|
||||||
|
if (update.pollAnswer() != null) {
|
||||||
|
EventListenerManager.publishPollAnswerEvent(update);
|
||||||
|
}
|
||||||
|
if (update.myChatMember() != null) {
|
||||||
|
EventListenerManager.publishMyChatMemberUpdatedEvent(update);
|
||||||
|
}
|
||||||
|
if (update.chatMember() != null) {
|
||||||
|
EventListenerManager.publishChatMemberUpdatedEvent(update);
|
||||||
|
}
|
||||||
|
if (update.chatJoinRequest() != null) {
|
||||||
|
EventListenerManager.publishChatJoinRequestEvent(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UpdatesListener.CONFIRMED_UPDATES_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.DeleteMessage;
|
||||||
|
import com.pengrad.telegrambot.request.GetChatMember;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class DirectMsgClear implements ISimpleCommand {
|
||||||
|
|
||||||
|
@Nonnull @Override public String getName () { return "r"; }
|
||||||
|
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
logger.debug("Executing command /r");
|
||||||
|
if (event.message().replyToMessage() == null) return;
|
||||||
|
logger.trace("Message is a reply");
|
||||||
|
if (event.message().replyToMessage().from().id() != MornyCoeur.getUserid()) return;
|
||||||
|
logger.trace("Message is from me");
|
||||||
|
if (System.currentTimeMillis()/1000 - event.message().replyToMessage().date() > 48*60*60) return;
|
||||||
|
logger.trace("Message is not older than 48 hours");
|
||||||
|
|
||||||
|
final boolean isTrusted = MornyCoeur.trustedInstance().isTrusted(event.message().from().id());
|
||||||
|
|
||||||
|
if (
|
||||||
|
isTrusted || (
|
||||||
|
event.message().replyToMessage().replyToMessage() != null &&
|
||||||
|
event.message().replyToMessage().replyToMessage().from().id().equals(event.message().from().id())
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new DeleteMessage(
|
||||||
|
event.message().chat().id(), event.message().replyToMessage().messageId()
|
||||||
|
));
|
||||||
|
if (event.message().chat().type() == Chat.Type.Private || (
|
||||||
|
MornyCoeur.extra().exec(
|
||||||
|
new GetChatMember(event.message().chat().id(), event.message().from().id())
|
||||||
|
).chatMember().canDeleteMessages()
|
||||||
|
)) {
|
||||||
|
MornyCoeur.extra().exec(new DeleteMessage(
|
||||||
|
event.message().chat().id(), event.message().messageId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else logger.trace("User is not trusted");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
205
src/main/java/cc/sukazyo/cono/morny/bot/command/Encryptor.java
Normal file
205
src/main/java/cc/sukazyo/cono/morny/bot/command/Encryptor.java
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonConvert;
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonEncrypt;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape;
|
||||||
|
import com.pengrad.telegrambot.model.PhotoSize;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.GetFile;
|
||||||
|
import com.pengrad.telegrambot.request.SendDocument;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class Encryptor implements ITelegramCommand {
|
||||||
|
|
||||||
|
@Nonnull @Override public String getName () { return "encrypt"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[algorithm|(l)] [(uppercase)]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "通过指定算法加密回复的内容 (目前只支持文本)"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
// show a simple help page
|
||||||
|
// the first paragraph lists available encrypt algorithms, and its aliases.
|
||||||
|
// with the separator "---",
|
||||||
|
// the second paragraphs shows the mods available and its aliases.
|
||||||
|
if (!command.hasArgs() || (command.getArgs()[0].equals("l") && command.getArgs().length==1)) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(), """
|
||||||
|
<b><u>base64</u></b>, b64
|
||||||
|
<b><u>base64url</u></b>, base64u, b64u
|
||||||
|
<b><u>base64decode</u></b>, base64d, b64d
|
||||||
|
<b><u>base64url-decode</u></b>, base64ud, b64ud
|
||||||
|
<b><u>sha1</u></b>
|
||||||
|
<b><u>sha256</u></b>
|
||||||
|
<b><u>sha512</u></b>
|
||||||
|
<b><u>md5</u></b>
|
||||||
|
---
|
||||||
|
<b><i>uppercase</i></b>, upper, u <i>(sha1/sha256/sha512/md5 only)</i>
|
||||||
|
"""
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// param1 is the encrypting algorithm, it MUST EXIST.
|
||||||
|
// so the mod will be set in param2.
|
||||||
|
// and for now only support UPPERCASE mod, so it exists in param2, or there should no any params.
|
||||||
|
boolean modUpperCase = false;
|
||||||
|
if (command.getArgs().length > 1) {
|
||||||
|
if (command.getArgs().length < 3 && (
|
||||||
|
command.getArgs()[1].equalsIgnoreCase("uppercase") ||
|
||||||
|
command.getArgs()[1].equalsIgnoreCase("u") ||
|
||||||
|
command.getArgs()[1].equalsIgnoreCase("upper")
|
||||||
|
)) {
|
||||||
|
modUpperCase = true;
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(), TelegramStickers.ID_404
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for now, only support reply to A TEXT MESSAGE or ONE UNIVERSAL FILE
|
||||||
|
// if the replied message contains a UNIVERSAL FILE, it will use the file and will not use the text with it
|
||||||
|
// do not support TELEGRAM INLINE IMAGE/VIDEO/AUDIO yet
|
||||||
|
// do not support MULTI_FILE yet
|
||||||
|
// if there's no text message in reply, it will report null as result.
|
||||||
|
boolean inputText;
|
||||||
|
byte[] data;
|
||||||
|
String dataName;
|
||||||
|
if (event.message().replyToMessage() != null && event.message().replyToMessage().document() != null) {
|
||||||
|
inputText = false;
|
||||||
|
try {
|
||||||
|
data = MornyCoeur.getAccount().getFileContent(MornyCoeur.extra().exec(new GetFile(
|
||||||
|
event.message().replyToMessage().document().fileId()
|
||||||
|
)).file());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("NetworkRequest error: TelegramFileAPI:\n\t" + e.getMessage());
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_NETWORK_ERR
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dataName = event.message().replyToMessage().document().fileName();
|
||||||
|
} else if (event.message().replyToMessage() != null && event.message().replyToMessage().photo() != null) {
|
||||||
|
inputText = false;
|
||||||
|
try {
|
||||||
|
PhotoSize originPhoto = null;
|
||||||
|
long photoSize = 0;
|
||||||
|
for (PhotoSize size : event.message().replyToMessage().photo()) if (photoSize < (long)size.width() *size.height()) {
|
||||||
|
originPhoto = size;
|
||||||
|
photoSize = (long)size.width() *size.height();
|
||||||
|
} // found max size (original) image in available sizes
|
||||||
|
if (originPhoto==null) throw new IOException("no photo object from api.");
|
||||||
|
data = MornyCoeur.getAccount().getFileContent(MornyCoeur.extra().exec(new GetFile(
|
||||||
|
originPhoto.fileId()
|
||||||
|
)).file());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("NetworkRequest error: TelegramFileAPI:\n\t" + e.getMessage());
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_NETWORK_ERR
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dataName = "photo"+CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(String.valueOf(System.currentTimeMillis()))).substring(32-12).toUpperCase()+".png";
|
||||||
|
} else if (event.message().replyToMessage() != null && event.message().replyToMessage().text() != null) {
|
||||||
|
inputText = true;
|
||||||
|
data = event.message().replyToMessage().text().getBytes(CommonEncrypt.ENCRYPT_STANDARD_CHARSET);
|
||||||
|
dataName = null;
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"<i><u>null</u></i>"
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean echoString = true;
|
||||||
|
String resultString = null;
|
||||||
|
byte[] result = null;
|
||||||
|
String resultName = null;
|
||||||
|
switch (command.getArgs()[0]) {
|
||||||
|
case "base64", "b64", "base64url", "base64u", "b64u" -> {
|
||||||
|
final Base64.Encoder b64tool = command.getArgs()[0].contains("u") ? Base64.getUrlEncoder() : Base64.getEncoder();
|
||||||
|
result = b64tool.encode(data);
|
||||||
|
if (!inputText) {
|
||||||
|
echoString = false;
|
||||||
|
resultName = dataName+".b64.txt";
|
||||||
|
} else {
|
||||||
|
resultString = new String(result, CommonEncrypt.ENCRYPT_STANDARD_CHARSET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "base64decode", "base64d", "b64d", "base64url-decode", "base64ud", "b64ud" -> {
|
||||||
|
final Base64.Decoder b64tool = command.getArgs()[0].contains("u") ? Base64.getUrlDecoder() : Base64.getDecoder();
|
||||||
|
try { result = b64tool.decode(data); }
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(), TelegramStickers.ID_404
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!inputText) {
|
||||||
|
echoString = false;
|
||||||
|
resultName = CommonEncrypt.base64FilenameLint(dataName);
|
||||||
|
} else {
|
||||||
|
resultString = new String(result, CommonEncrypt.ENCRYPT_STANDARD_CHARSET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "md5" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(data));
|
||||||
|
case "sha1" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashSha1(data));
|
||||||
|
case "sha256" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashSha256(data));
|
||||||
|
case "sha512" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashSha512(data));
|
||||||
|
default -> {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(), TelegramStickers.ID_404
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modUpperCase) {
|
||||||
|
// modUpperCase support only algorithm that showed as HEX value.
|
||||||
|
// it means md5, sha1, sha256, sha512 here.
|
||||||
|
// other will report wrong param.
|
||||||
|
switch (command.getArgs()[0]) {
|
||||||
|
case "md5", "sha1", "sha256", "sha512" -> {
|
||||||
|
assert resultString != null;
|
||||||
|
resultString = resultString.toUpperCase();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(), TelegramStickers.ID_404
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (echoString) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"<pre><code>" + MsgEscape.escapeHtml(resultString) + "</code></pre>"
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendDocument(
|
||||||
|
event.message().chat().id(),
|
||||||
|
result
|
||||||
|
).fileName(resultName).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.MornyTrusted;
|
||||||
|
import cc.sukazyo.cono.morny.bot.event.OnEventHackHandle;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link OnEventHackHandle} 的命令行前端
|
||||||
|
* @since 0.4.2.0
|
||||||
|
*/
|
||||||
|
public class EventHack implements ITelegramCommand {
|
||||||
|
|
||||||
|
@Nonnull @Override public String getName () { return "event_hack"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[(user|group|any)]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "输出 bot 下一个获取到的事件序列化数据"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link OnEventHackHandle} 的命令行前端<br>
|
||||||
|
* <br>
|
||||||
|
* 实现了通过命令行进行 EventHack 功能。<br>
|
||||||
|
* 支持三种模式,默认为 {@link OnEventHackHandle.HackType#USER USER},
|
||||||
|
* {@link OnEventHackHandle.HackType#ANY ANY} 时,将会通过 {@link MornyTrusted#isTrusted(long)} 检查触发用户的权限
|
||||||
|
*
|
||||||
|
* @param event 命令基础参数,触发的事件对象本身
|
||||||
|
* @param command 命令基础参数,解析出的命令对象
|
||||||
|
* @since 0.4.2.0
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
boolean isOk = false;
|
||||||
|
|
||||||
|
String x_mode = "";
|
||||||
|
if (command.hasArgs()) {
|
||||||
|
x_mode = command.getArgs()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (x_mode) {
|
||||||
|
case "any":
|
||||||
|
if (MornyCoeur.trustedInstance().isTrusted(event.message().from().id())) {
|
||||||
|
OnEventHackHandle.registerHack(
|
||||||
|
event.message().messageId(),
|
||||||
|
event.message().from().id(),
|
||||||
|
event.message().chat().id(),
|
||||||
|
OnEventHackHandle.HackType.ANY
|
||||||
|
);isOk = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
OnEventHackHandle.registerHack(
|
||||||
|
event.message().messageId(),
|
||||||
|
event.message().from().id(),
|
||||||
|
event.message().chat().id(),
|
||||||
|
OnEventHackHandle.HackType.GROUP
|
||||||
|
);isOk = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
OnEventHackHandle.registerHack(
|
||||||
|
event.message().messageId(),
|
||||||
|
event.message().from().id(),
|
||||||
|
event.message().chat().id(),
|
||||||
|
OnEventHackHandle.HackType.USER
|
||||||
|
);isOk = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOk) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_WAITING
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_403
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.GetChatMember;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.response.GetChatMemberResponse;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class GetUsernameAndId implements ITelegramCommand {
|
||||||
|
|
||||||
|
@Nonnull @Override public String getName () { return "user"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[userid]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "获取指定或回复的用户相关信息"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
final String[] args = command.getArgs();
|
||||||
|
|
||||||
|
if (args.length > 1) { MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Unavailable] Too much arguments."
|
||||||
|
).replyToMessageId(event.message().messageId())); return; }
|
||||||
|
|
||||||
|
long userId = event.message().from().id();
|
||||||
|
|
||||||
|
if (event.message().replyToMessage()!= null) {
|
||||||
|
userId = event.message().replyToMessage().from().id();
|
||||||
|
}
|
||||||
|
if (args.length > 0) {
|
||||||
|
try {
|
||||||
|
userId = Long.parseLong(args[0]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Unavailable] " + e.getMessage()
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final GetChatMemberResponse response = MornyCoeur.getAccount().execute(
|
||||||
|
new GetChatMember(event.message().chat().id(), userId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.chatMember() == null) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Unavailable] user not found."
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final User user = response.chatMember().user();
|
||||||
|
|
||||||
|
if (user.id() == 136817688) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"<code>$__channel_identify</code>"
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramUserInformation.informationOutputHTML(user)
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public interface ISimpleCommand {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String[] getAliases();
|
||||||
|
|
||||||
|
void execute (@Nonnull InputCommand command, @Nonnull Update event);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public interface ITelegramCommand extends ISimpleCommand {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
String getParamRule();
|
||||||
|
@Nonnull
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.data.ip186.IP186QueryResponse;
|
||||||
|
import cc.sukazyo.cono.morny.data.ip186.IP186QueryHandler;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@value IP186QueryHandler#SITE_URL} 查询的 telegram 命令前端
|
||||||
|
* @since 0.4.2.10
|
||||||
|
*/
|
||||||
|
public class Ip186Query {
|
||||||
|
|
||||||
|
public static final String CMD_IP = "ip";
|
||||||
|
public static final String CMD_WHOIS = "whois";
|
||||||
|
|
||||||
|
public static class Ip implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return CMD_IP; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[ip]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "通过 https://ip.186526.xyz 查询 ip 资料"; }
|
||||||
|
@Override public void execute (@NotNull InputCommand command, @NotNull Update event) { exec(event, command); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Whois implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return CMD_WHOIS; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[domain]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "通过 https://ip.186526.xyz 查询域名资料"; }
|
||||||
|
@Override public void execute (@NotNull InputCommand command, @NotNull Update event) { exec(event, command); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void exec (@Nonnull Update event, @Nonnull InputCommand command) {
|
||||||
|
|
||||||
|
String arg = null;
|
||||||
|
if (!command.hasArgs()) {
|
||||||
|
if (event.message().replyToMessage() != null) {
|
||||||
|
arg = event.message().replyToMessage().text();
|
||||||
|
}
|
||||||
|
} else if (command.getArgs().length > 1) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Unavailable] Too much arguments."
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
arg = command.getArgs()[0];
|
||||||
|
}
|
||||||
|
if (arg == null) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Unavailable] No ip defined."
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
IP186QueryResponse response = switch (command.getCommand()) {
|
||||||
|
case CMD_IP -> IP186QueryHandler.queryIp(arg);
|
||||||
|
case CMD_WHOIS -> IP186QueryHandler.queryWhoisPretty(arg);
|
||||||
|
default -> throw new IllegalArgumentException("Unknown 186-IP query method " + command.getCommand());
|
||||||
|
};
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
escapeHtml(response.url()) + "\n<code>" + escapeHtml(response.body()) + "</code>"
|
||||||
|
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Exception] in query:\n<code>" + escapeHtml(e.getMessage()) + "</code>"
|
||||||
|
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,373 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.GradleProjectConfigures;
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.MornySystem;
|
||||||
|
import cc.sukazyo.cono.morny.data.MornyJrrp;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString;
|
||||||
|
import com.pengrad.telegrambot.model.BotCommand;
|
||||||
|
import com.pengrad.telegrambot.model.DeleteMyCommands;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
import com.pengrad.telegrambot.request.SetMyCommands;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
import static cc.sukazyo.cono.morny.util.CommonFormat.formatDate;
|
||||||
|
import static cc.sukazyo.cono.morny.util.CommonFormat.formatDuration;
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
|
||||||
|
|
||||||
|
public class MornyCommands {
|
||||||
|
|
||||||
|
private final Map<String, ISimpleCommand> commands = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private void pushCommandTo (@Nonnull String name, @Nonnull ISimpleCommand instance) {
|
||||||
|
if (commands.containsKey(name)) {
|
||||||
|
logger.warn(String.format("""
|
||||||
|
Telegram command instance named "%s" already exists and will be override by another command instance
|
||||||
|
- current: %s
|
||||||
|
- new : %s""",
|
||||||
|
name,
|
||||||
|
commands.get(name).getClass().getName(),
|
||||||
|
instance.getClass().getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
commands.put(name, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register (@Nonnull ISimpleCommand... list) {
|
||||||
|
for (ISimpleCommand instance : list) {
|
||||||
|
final String[] aliases = instance.getAliases();
|
||||||
|
pushCommandTo(instance.getName(), instance);
|
||||||
|
if (aliases!=null) for (String alias : aliases) pushCommandTo(alias, instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NonAsciiCharacters")
|
||||||
|
public MornyCommands () {
|
||||||
|
|
||||||
|
register(
|
||||||
|
new ON(),
|
||||||
|
new Hello(), new HelloOnStart(),
|
||||||
|
new GetUsernameAndId(),
|
||||||
|
new EventHack(),
|
||||||
|
new Nbnhhsh(),
|
||||||
|
new Ip186Query.Ip(),
|
||||||
|
new Ip186Query.Whois(),
|
||||||
|
new Encryptor(),
|
||||||
|
new SaveData(),
|
||||||
|
new MornyInformations(),
|
||||||
|
new Version(),
|
||||||
|
new MornyRuntime(),
|
||||||
|
new Jrrp(),
|
||||||
|
new Exit(), new ExitAlias()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 特殊的命令
|
||||||
|
register(
|
||||||
|
new Testing(),
|
||||||
|
new DirectMsgClear()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 统一注册这些奇怪的东西&.&
|
||||||
|
register(
|
||||||
|
new 喵呜.抱抱(),
|
||||||
|
new 喵呜.揉揉(),
|
||||||
|
new 喵呜.蹭蹭(),
|
||||||
|
new 喵呜.贴贴(),
|
||||||
|
new 私わね(),
|
||||||
|
new 喵呜.Progynova()
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
if (commands.containsKey(command.getCommand())) {
|
||||||
|
commands.get(command.getCommand()).execute(command, event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return nonCommandExecutable(event, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void automaticUpdateList () {
|
||||||
|
BotCommand[] commandList = getCommandListTelegram();
|
||||||
|
automaticRemoveList();
|
||||||
|
MornyCoeur.extra().exec(new SetMyCommands(
|
||||||
|
commandList
|
||||||
|
));
|
||||||
|
logger.info("automatic updated telegram command list :\n" + commandListToString(commandList));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void automaticRemoveList () {
|
||||||
|
MornyCoeur.extra().exec(new DeleteMyCommands());
|
||||||
|
logger.info("cleaned up command list.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String commandListToString (@Nonnull BotCommand[] list) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (BotCommand signal : list) {
|
||||||
|
builder.append(signal.command()).append(" - ").append(signal.description()).append("\n");
|
||||||
|
}
|
||||||
|
return builder.substring(0, builder.length()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BotCommand[] getCommandListTelegram () {
|
||||||
|
final List<BotCommand> telegramFormatListing = new ArrayList<>();
|
||||||
|
commands.forEach((regKey, command) -> {
|
||||||
|
if (command instanceof ITelegramCommand && regKey.equals(command.getName())) {
|
||||||
|
telegramFormatListing.add(formatTelegramCommandListLine(
|
||||||
|
command.getName(),
|
||||||
|
((ITelegramCommand)command).getParamRule(),
|
||||||
|
((ITelegramCommand)command).getDescription()
|
||||||
|
));
|
||||||
|
if (command.getAliases() != null) for (String alias : command.getAliases()) {
|
||||||
|
telegramFormatListing.add(formatTelegramCommandListLine(alias, "", "↑"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return telegramFormatListing.toArray(BotCommand[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BotCommand formatTelegramCommandListLine (@Nonnull String commandName, @Nonnull String paramRule, @Nonnull String intro) {
|
||||||
|
return new BotCommand(commandName, "".equals(paramRule) ? (intro) : (paramRule+" - "+intro));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean nonCommandExecutable (Update event, InputCommand command) {
|
||||||
|
if (command.getTarget() == null) return false; // 无法解析的命令,转交事件链后代处理
|
||||||
|
else { // 无法解析的显式命令格式,报错找不到命令
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_404
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// /// /// /// /// /// /// /// ///
|
||||||
|
///
|
||||||
|
/// Old Simple Command Block
|
||||||
|
///
|
||||||
|
|
||||||
|
private static class ON implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "o"; }
|
||||||
|
@Nullable
|
||||||
|
@Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "检查是否在线"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandOnExec(event); }
|
||||||
|
}
|
||||||
|
private static void onCommandOnExec (@Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_ONLINE_STATUS_RETURN
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Hello implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "hello"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[]{"hi"}; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "打招呼"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandHelloExec(event); }
|
||||||
|
}
|
||||||
|
private static class HelloOnStart implements ISimpleCommand { @Nonnull @Override public String getName () { return "start"; }@Nullable @Override public String[] getAliases () { return new String[0]; }@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandHelloExec(event); }}
|
||||||
|
private static void onCommandHelloExec (@Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_HELLO
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Exit implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "exit"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "关闭 Bot (仅可信成员)"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandExitExec(event); }
|
||||||
|
}
|
||||||
|
private static class ExitAlias implements ISimpleCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "quit"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[]{"stop"}; }
|
||||||
|
@Override public void execute (@NotNull InputCommand command, @NotNull Update event) { onCommandExitExec(event); }
|
||||||
|
}
|
||||||
|
private static void onCommandExitExec (@Nonnull Update event) {
|
||||||
|
if (MornyCoeur.trustedInstance().isTrusted(event.message().from().id())) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_EXIT
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
logger.info("Morny exited by user " + TGToString.as(event.message().from()).toStringLogTag());
|
||||||
|
System.exit(0);
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_403
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
logger.info("403 exited tag from user " + TGToString.as(event.message().from()).toStringLogTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Version implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "version"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "检查 Bot 版本信息"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandVersionExec(event); }
|
||||||
|
}
|
||||||
|
private static void onCommandVersionExec (@Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
String.format(
|
||||||
|
"""
|
||||||
|
version:
|
||||||
|
- Morny <code>%s</code>
|
||||||
|
- <code>%s</code>
|
||||||
|
core md5_hash:
|
||||||
|
- <code>%s</code>
|
||||||
|
compile timestamp:
|
||||||
|
- <code>%d</code>
|
||||||
|
- <code>%s [UTC]</code>""",
|
||||||
|
escapeHtml(MornySystem.CODENAME.toUpperCase()),
|
||||||
|
escapeHtml(MornySystem.VERSION),
|
||||||
|
escapeHtml(MornySystem.getJarMd5()),
|
||||||
|
GradleProjectConfigures.COMPILE_TIMESTAMP,
|
||||||
|
escapeHtml(formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0))
|
||||||
|
)
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MornyRuntime implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "runtime"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "获取 Bot 运行时信息(包括版本号)"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandRuntimeExec(event); }
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @since 0.4.1.2
|
||||||
|
*/
|
||||||
|
private static void onCommandRuntimeExec (@Nonnull Update event) {
|
||||||
|
String hostname;
|
||||||
|
try {
|
||||||
|
hostname = InetAddress.getLocalHost().getHostName();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
hostname = "<unknown>";
|
||||||
|
}
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
String.format("""
|
||||||
|
system:
|
||||||
|
- <code>%s</code>
|
||||||
|
- <code>%s</code>
|
||||||
|
- <code>%s</code>
|
||||||
|
java runtime:
|
||||||
|
- <code>%s</code>
|
||||||
|
- <code>%s</code>
|
||||||
|
vm memory:
|
||||||
|
- <code>%d</code> / <code>%d</code> MB
|
||||||
|
- <code>%d</code> cores
|
||||||
|
coeur version:
|
||||||
|
- <code>%s</code> (<code>%s</code>)
|
||||||
|
- <code>%s</code>
|
||||||
|
- <code>%s [UTC]</code>
|
||||||
|
- [<code>%d</code>]
|
||||||
|
continuous:
|
||||||
|
- <code>%s</code>
|
||||||
|
- [<code>%d</code>]
|
||||||
|
- <code>%s [UTC]</code>
|
||||||
|
- [<code>%d</code>]""",
|
||||||
|
// system
|
||||||
|
escapeHtml(hostname),
|
||||||
|
escapeHtml(String.format("%s (%s)", System.getProperty("os.name"), System.getProperty("os.arch"))),
|
||||||
|
escapeHtml(System.getProperty("os.version")),
|
||||||
|
// java
|
||||||
|
escapeHtml(System.getProperty("java.vm.vendor")+"."+System.getProperty("java.vm.name")),
|
||||||
|
escapeHtml(System.getProperty("java.vm.version")),
|
||||||
|
// memory
|
||||||
|
Runtime.getRuntime().totalMemory() / 1024 / 1024,
|
||||||
|
Runtime.getRuntime().maxMemory() / 1024 / 1024,
|
||||||
|
Runtime.getRuntime().availableProcessors(),
|
||||||
|
// version
|
||||||
|
escapeHtml(MornySystem.VERSION),
|
||||||
|
escapeHtml(MornySystem.CODENAME),
|
||||||
|
escapeHtml(MornySystem.getJarMd5()),
|
||||||
|
escapeHtml(formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)),
|
||||||
|
GradleProjectConfigures.COMPILE_TIMESTAMP,
|
||||||
|
// continuous
|
||||||
|
escapeHtml(formatDuration(System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp)),
|
||||||
|
System.currentTimeMillis() - MornyCoeur.coeurStartTimestamp,
|
||||||
|
escapeHtml(formatDate(MornyCoeur.coeurStartTimestamp, 0)),
|
||||||
|
MornyCoeur.coeurStartTimestamp
|
||||||
|
)
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Jrrp implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "jrrp"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "获取 (假的) jrrp"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandJrrpExec(event); }
|
||||||
|
}
|
||||||
|
private static void onCommandJrrpExec (Update event) {
|
||||||
|
final double jrrp = MornyJrrp.getJrrpFromTelegramUser(event.message().from(), System.currentTimeMillis());
|
||||||
|
final String endChar = jrrp>70 ? "!" : jrrp>30 ? ";" : "...";
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
String.format(
|
||||||
|
"%s 在(utc的)今天的运气指数是———— <code>%.2f%%</code> %s",
|
||||||
|
TGToString.as(event.message().from()).fullnameRefHtml(),
|
||||||
|
jrrp, escapeHtml(endChar)
|
||||||
|
)
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SaveData implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "save"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "保存缓存数据到文件(仅可信成员)"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onSaveDataExec(event); }
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @since 0.4.3.0
|
||||||
|
*/
|
||||||
|
private static void onSaveDataExec (Update event) {
|
||||||
|
if (MornyCoeur.trustedInstance().isTrusted(event.message().from().id())) {
|
||||||
|
logger.info("called save from command by " + TGToString.as(event.message().from()).toStringLogTag());
|
||||||
|
MornyCoeur.callSaveData();
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_SAVED
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_403
|
||||||
|
).replyToMessageId(event.message().messageId())
|
||||||
|
);
|
||||||
|
logger.info("403 call save tag from user " + TGToString.as(event.message().from()).toStringLogTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class MornyInformations implements ITelegramCommand {
|
||||||
|
|
||||||
|
private static final String ACT_STICKER = "stickers";
|
||||||
|
|
||||||
|
@Nonnull @Override public String getName () { return "info"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[(stickers)|(stickers.)sticker_id]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "输出 Morny 当前版本的一些预定义信息"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
if (!command.hasArgs() || command.getArgs().length > 1) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(event.message().chat().id(), TelegramStickers.ID_404).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String action = command.getArgs()[0];
|
||||||
|
|
||||||
|
if (action.startsWith("stickers")) {
|
||||||
|
if (action.equals("stickers"))
|
||||||
|
TelegramStickers.echoAllStickers(MornyCoeur.extra(), event.message().chat().id(), event.message().messageId());
|
||||||
|
else {
|
||||||
|
TelegramStickers.echoStickerByID(
|
||||||
|
action.substring((ACT_STICKER+".").length()),
|
||||||
|
MornyCoeur.extra(), event.message().chat().id(), event.message().messageId()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(event.message().chat().id(), TelegramStickers.ID_404).replyToMessageId(event.message().messageId()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
src/main/java/cc/sukazyo/cono/morny/bot/command/Nbnhhsh.java
Normal file
70
src/main/java/cc/sukazyo/cono/morny/bot/command/Nbnhhsh.java
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.data.NbnhhshQuery;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.CommonConvert.stringsConnecting;
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
|
||||||
|
|
||||||
|
public class Nbnhhsh implements ITelegramCommand {
|
||||||
|
|
||||||
|
@Nonnull @Override public String getName () { return "nbnhhsh"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return null; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return "[text]"; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "检索文本内 nbnhhsh 词条"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
String queryTarget = "";
|
||||||
|
if (event.message().replyToMessage() != null && event.message().replyToMessage().text() != null)
|
||||||
|
queryTarget = event.message().replyToMessage().text();
|
||||||
|
if (command.hasArgs())
|
||||||
|
queryTarget = stringsConnecting(command.getArgs(), " ", 0, command.getArgs().length-1);
|
||||||
|
|
||||||
|
NbnhhshQuery.GuessResult response = NbnhhshQuery.sendGuess(queryTarget);
|
||||||
|
|
||||||
|
StringBuilder message = new StringBuilder("<a href=\"https://lab.magiconch.com/nbnhhsh/\">## Result of nbnhhsh query :</a>");
|
||||||
|
|
||||||
|
for (NbnhhshQuery.Word word : response.words) {
|
||||||
|
if (word.trans != null && word.trans.length == 0) word.trans = null;
|
||||||
|
if (word.inputting != null && word.inputting.length == 0) word.inputting = null;
|
||||||
|
if (word.trans == null && word.inputting == null) continue;
|
||||||
|
message.append("\n\n<b>[[ ").append(escapeHtml(word.name)).append(" ]]</b>");
|
||||||
|
if (word.trans != null) for (String trans : word.trans) {
|
||||||
|
message.append("\n* <i>").append(escapeHtml(trans)).append("</i>");
|
||||||
|
}
|
||||||
|
if (word.inputting != null) {
|
||||||
|
if (word.trans != null) message.append("\n");
|
||||||
|
message.append(" maybe:");
|
||||||
|
for (String trans : word.inputting) {
|
||||||
|
message.append("\n` <i>").append(escapeHtml(trans)).append("</i>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
message.toString()
|
||||||
|
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"[Exception] in query:\n<code>" + escapeHtml(e.getMessage()) + "</code>"
|
||||||
|
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
public class Roll {
|
||||||
|
}
|
36
src/main/java/cc/sukazyo/cono/morny/bot/command/Testing.java
Normal file
36
src/main/java/cc/sukazyo/cono/morny/bot/command/Testing.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class Testing implements ISimpleCommand {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public String getName () {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String[] getAliases () {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"<b>Just<b/> a TEST command."
|
||||||
|
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
74
src/main/java/cc/sukazyo/cono/morny/bot/command/喵呜.java
Normal file
74
src/main/java/cc/sukazyo/cono/morny/bot/command/喵呜.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@SuppressWarnings("NonAsciiCharacters")
|
||||||
|
public class 喵呜 {
|
||||||
|
|
||||||
|
public static class 抱抱 implements ISimpleCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "抱抱"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"抱抱——"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class 揉揉 implements ISimpleCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "揉揉"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"蹭蹭w"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class 蹭蹭 implements ISimpleCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "蹭蹭"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"喵呜~-"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class 贴贴 implements ISimpleCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "贴贴"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
"<tg-spoiler>(贴贴喵呜&.&)</tg-spoiler>"
|
||||||
|
).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Progynova implements ITelegramCommand {
|
||||||
|
@Nonnull @Override public String getName () { return "install"; }
|
||||||
|
@Nullable @Override public String[] getAliases () { return new String[0]; }
|
||||||
|
@Nonnull @Override public String getParamRule () { return ""; }
|
||||||
|
@Nonnull @Override public String getDescription () { return "抽取一个神秘盒子"; }
|
||||||
|
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().chat().id(),
|
||||||
|
TelegramStickers.ID_PROGYNOVA
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
src/main/java/cc/sukazyo/cono/morny/bot/command/私わね.java
Normal file
38
src/main/java/cc/sukazyo/cono/morny/bot/command/私わね.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.command;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
@SuppressWarnings("NonAsciiCharacters")
|
||||||
|
public class 私わね implements ISimpleCommand {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override public String getName () { return "me"; }
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override public String[] getAliases () { return null; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
|
||||||
|
if (ThreadLocalRandom.current().nextInt(521) == 0) {
|
||||||
|
// 可以接入未来的心情系统(如果有的话)
|
||||||
|
final String text = switch (ThreadLocalRandom.current().nextInt(11)) {
|
||||||
|
case 0,7,8,9,10 -> "才不是";
|
||||||
|
case 1,2,3,6 -> "才不是!";
|
||||||
|
case 4,5 -> "才不是..";
|
||||||
|
default -> throw new IllegalStateException("Unexpected random value in 私わね command.");
|
||||||
|
};
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
text
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListenerManager;
|
||||||
|
|
||||||
|
public class EventListeners {
|
||||||
|
|
||||||
|
public static final OnTelegramCommand COMMANDS_LISTENER = new OnTelegramCommand();
|
||||||
|
@SuppressWarnings("unused") public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord();
|
||||||
|
public static final OnUserSlashAction USER_SLASH_ACTION = new OnUserSlashAction();
|
||||||
|
public static final OnUpdateTimestampOffsetLock UPDATE_TIMESTAMP_OFFSET_LOCK = new OnUpdateTimestampOffsetLock();
|
||||||
|
public static final OnInlineQueries INLINE_QUERY = new OnInlineQueries();
|
||||||
|
public static final OnCallMe CALL_ME = new OnCallMe();
|
||||||
|
public static final OnEventHackHandle EVENT_HACK_HANDLE = new OnEventHackHandle();
|
||||||
|
@SuppressWarnings("unused") static final OnKuohuanhuanNeedSleep KUOHUANHUAN_NEED_SLEEP = new OnKuohuanhuanNeedSleep();
|
||||||
|
public static final OnUserRandoms USER_RANDOMS = new OnUserRandoms();
|
||||||
|
public static final OnCallMsgSend CALL_MSG_SEND = new OnCallMsgSend();
|
||||||
|
public static final OnMedicationNotifyApply MEDICATION_NOTIFY_APPLY = new OnMedicationNotifyApply();
|
||||||
|
public static final OnRandomlyTriggered RANDOMLY_TRIGGERED = new OnRandomlyTriggered();
|
||||||
|
|
||||||
|
public static void registerAllListeners () {
|
||||||
|
EventListenerManager.addListener(
|
||||||
|
// ACTIVITY_RECORDER,
|
||||||
|
UPDATE_TIMESTAMP_OFFSET_LOCK,
|
||||||
|
/* write functional event behind here */
|
||||||
|
// KUOHUANHUAN_NEED_SLEEP,
|
||||||
|
COMMANDS_LISTENER,
|
||||||
|
RANDOMLY_TRIGGERED,
|
||||||
|
USER_RANDOMS,
|
||||||
|
USER_SLASH_ACTION,
|
||||||
|
INLINE_QUERY,
|
||||||
|
CALL_ME,
|
||||||
|
CALL_MSG_SEND,
|
||||||
|
MEDICATION_NOTIFY_APPLY,
|
||||||
|
EVENT_HACK_HANDLE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.daemon.TrackerDataManager;
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class OnActivityRecord extends EventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update update) {
|
||||||
|
if (
|
||||||
|
update.message().chat().type() == Chat.Type.supergroup ||
|
||||||
|
update.message().chat().type() == Chat.Type.group
|
||||||
|
) {
|
||||||
|
TrackerDataManager.record(
|
||||||
|
update.message().chat().id(),
|
||||||
|
update.message().from().id(),
|
||||||
|
(long)update.message().date() * 1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return super.onMessage(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
182
src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMe.java
Normal file
182
src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMe.java
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.MornyTrusted;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonFormat;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString;
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.ForwardMessage;
|
||||||
|
import com.pengrad.telegrambot.request.GetChat;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
import com.pengrad.telegrambot.response.SendResponse;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 bot 呼叫主人的事件监听管理类
|
||||||
|
* @since 0.4.2.1
|
||||||
|
*/
|
||||||
|
public class OnCallMe extends EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主人的 telegram user id,同时被用于 chat id<br>
|
||||||
|
* 跟随 {@link MornyTrusted#MASTER} 的值
|
||||||
|
* @since 0.4.2.1
|
||||||
|
*/
|
||||||
|
private static final long ME = MornyCoeur.trustedInstance().MASTER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听私聊 bot 的消息进行呼叫关键字匹配。
|
||||||
|
* 如果成功,将会执行呼叫函数,并向呼叫者回显{@link TelegramStickers#ID_WAITING "已呼叫"贴纸}
|
||||||
|
*
|
||||||
|
* @param update 事件基础参数,消息事件所属的 tgapi:update 对象
|
||||||
|
* @return 事件基础返回值,是否已完成处理事件:<br>
|
||||||
|
* 如果匹配到呼叫,则返回{@code true},反之返回{@code false}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update update) {
|
||||||
|
if (update.message().text() == null)
|
||||||
|
return false;
|
||||||
|
if (update.message().chat().type() != Chat.Type.Private)
|
||||||
|
return false;
|
||||||
|
switch (update.message().text().toLowerCase()) {
|
||||||
|
case "steam", "sbeam", "sdeam" ->
|
||||||
|
requestSteamJoin(update);
|
||||||
|
case "hana paresu", "花宫", "内群" ->
|
||||||
|
requestHanaParesuJoin(update);
|
||||||
|
case "dinner", "lunch", "breakfast", "meal", "eating", "安妮今天吃什么" ->
|
||||||
|
requestLastDinner(update);
|
||||||
|
default -> {
|
||||||
|
if (update.message().text().startsWith("cc::")) {
|
||||||
|
requestCustomCall(update);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
update.message().chat().id(),
|
||||||
|
TelegramStickers.ID_SENT
|
||||||
|
).replyToMessageId(update.message().messageId())
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 steam library 呼叫<br>
|
||||||
|
* 将会向 {@link #ME} 发送
|
||||||
|
*
|
||||||
|
* @param event 执行呼叫的tg事件
|
||||||
|
*/
|
||||||
|
private static void requestSteamJoin (Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
ME, String.format(
|
||||||
|
"""
|
||||||
|
request <b>STEAM LIBRARY</b>
|
||||||
|
from %s""",
|
||||||
|
TGToString.as(event.message().from()).fullnameRefHtml()
|
||||||
|
)
|
||||||
|
).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行花宫呼叫<br>
|
||||||
|
* 将会向 {@link #ME} 发送
|
||||||
|
*
|
||||||
|
* @param event 执行呼叫的tg事件
|
||||||
|
*/
|
||||||
|
private static void requestHanaParesuJoin (Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
ME, String.format(
|
||||||
|
"""
|
||||||
|
request <b>Hana Paresu</b>
|
||||||
|
from %s""",
|
||||||
|
TGToString.as(event.message().from()).fullnameRefHtml()
|
||||||
|
)
|
||||||
|
).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对访问最近一次的饭局的请求进行回复<br>
|
||||||
|
*
|
||||||
|
* @param event 执行呼叫的tg事件
|
||||||
|
*/
|
||||||
|
private static void requestLastDinner (Update event) {
|
||||||
|
boolean isAllowed = false;
|
||||||
|
Message lastDinnerData = null;
|
||||||
|
if (MornyCoeur.trustedInstance().isTrustedForDinnerRead(event.message().from().id())) {
|
||||||
|
lastDinnerData = MornyCoeur.extra().exec(new GetChat(MornyCoeur.DINNER_CHAT_ID)).chat().pinnedMessage();
|
||||||
|
SendResponse sendResp = MornyCoeur.extra().exec(new ForwardMessage(
|
||||||
|
event.message().from().id(),
|
||||||
|
lastDinnerData.forwardFromChat().id(),
|
||||||
|
lastDinnerData.forwardFromMessageId()
|
||||||
|
));
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().from().id(),
|
||||||
|
String.format("<i>on</i> <code>%s [UTC+8]</code>\n- <code>%s</code> <i>before</i>",
|
||||||
|
MsgEscape.escapeHtml(
|
||||||
|
CommonFormat.formatDate((long)lastDinnerData.forwardDate()*1000, 8)
|
||||||
|
), MsgEscape.escapeHtml(
|
||||||
|
CommonFormat.formatDuration(System.currentTimeMillis()-(long)lastDinnerData.forwardDate()*1000)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).replyToMessageId(sendResp.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
isAllowed = true;
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
event.message().from().id(),
|
||||||
|
TelegramStickers.ID_403
|
||||||
|
).replyToMessageId(event.message().messageId()));
|
||||||
|
}
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
ME, String.format(
|
||||||
|
"""
|
||||||
|
request <b>Last Annie Dinner</b>
|
||||||
|
from %s
|
||||||
|
%s""",
|
||||||
|
TGToString.as(event.message().from()).fullnameRefHtml(),
|
||||||
|
isAllowed ? "Allowed and returned " + String.format(
|
||||||
|
"https://t.me/c/%d/%d", Math.abs(lastDinnerData.forwardFromChat().id()+1000000000000L), lastDinnerData.forwardFromMessageId()
|
||||||
|
) : "Forbidden by perm check."
|
||||||
|
)
|
||||||
|
).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行自定义呼叫<br>
|
||||||
|
* 将会向 {@link #ME} 发送一个 request 数据消息和转发的原始请求消息<br>
|
||||||
|
* <br>
|
||||||
|
* <u>known issue</u><ul>
|
||||||
|
* <li>无法处理与转发带有媒体的消息</li>
|
||||||
|
* </ul>
|
||||||
|
* <br>
|
||||||
|
* 现在你可以通过这个 bot 来呼叫主人(sukazyo)任何事情了 ——
|
||||||
|
* <s>但是直接私聊sukazyo不好吗</s>
|
||||||
|
*
|
||||||
|
* @param event 执行呼叫的tg事件
|
||||||
|
* @since 0.4.2.2
|
||||||
|
*/
|
||||||
|
private static void requestCustomCall (Update event) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
ME, String.format(
|
||||||
|
"""
|
||||||
|
request <u>[???]</u>
|
||||||
|
from %s""",
|
||||||
|
TGToString.as(event.message().from()).fullnameRefHtml()
|
||||||
|
)
|
||||||
|
).parseMode(ParseMode.HTML));
|
||||||
|
MornyCoeur.extra().exec(new ForwardMessage(
|
||||||
|
ME,
|
||||||
|
event.message().chat().id(),
|
||||||
|
event.message().messageId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
221
src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.java
Normal file
221
src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.java
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.MessageEntity;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.GetChat;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramStickers;
|
||||||
|
import com.pengrad.telegrambot.response.GetChatResponse;
|
||||||
|
import com.pengrad.telegrambot.response.SendResponse;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
|
||||||
|
|
||||||
|
|
||||||
|
public class OnCallMsgSend extends EventListener {
|
||||||
|
|
||||||
|
private static final Pattern REGEX_MSG_SENDREQ_DATA_HEAD = Pattern.compile("^\\*msg([\\d-]+)(\\*\\S+)?\\n([\\s\\S]+)$");
|
||||||
|
|
||||||
|
private record MessageToSend (
|
||||||
|
String message,
|
||||||
|
MessageEntity[] entities,
|
||||||
|
ParseMode parseMode,
|
||||||
|
long targetId
|
||||||
|
) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage(Update update) {
|
||||||
|
|
||||||
|
// 执行体检查
|
||||||
|
if (update.message().chat().type() != Chat.Type.Private) return false;
|
||||||
|
if (update.message().text() == null) return false;
|
||||||
|
if (!update.message().text().startsWith("*msg")) return false;
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
if (!MornyCoeur.trustedInstance().isTrusted(update.message().from().id())) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
update.message().chat().id(),
|
||||||
|
TelegramStickers.ID_403
|
||||||
|
).replyToMessageId(update.message().messageId()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message msgsendReqRaw; // 用户书写的发送请求原文
|
||||||
|
MessageToSend msgsendReqBody; // 解析后的发送请求实例
|
||||||
|
|
||||||
|
// *msgsend 发送标识
|
||||||
|
// 处理发送要求
|
||||||
|
if (update.message().text().equals("*msgsend")) {
|
||||||
|
// 发送体处理
|
||||||
|
if (update.message().replyToMessage() == null) return answer404(update);
|
||||||
|
msgsendReqBody = parseRequest(update.message().replyToMessage());
|
||||||
|
if (msgsendReqBody == null) return answer404(update);
|
||||||
|
// 执行发送任务
|
||||||
|
SendResponse sendResponse = MornyCoeur.getAccount().execute(parseMessageToSend(msgsendReqBody));
|
||||||
|
if (!sendResponse.isOk()) { // 发送失败
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
update.message().chat().id(),
|
||||||
|
String.format("""
|
||||||
|
<b><u>%d</u> FAILED</b>
|
||||||
|
<code>%s</code>""",
|
||||||
|
sendResponse.errorCode(),
|
||||||
|
sendResponse.description()
|
||||||
|
)
|
||||||
|
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
} else { // 发送成功信号
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
update.message().chat().id(),
|
||||||
|
TelegramStickers.ID_SENT
|
||||||
|
).replyToMessageId(update.message().messageId()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
// 发送完成/失败 - 事件结束
|
||||||
|
}
|
||||||
|
|
||||||
|
// *msg 检查标识
|
||||||
|
if (update.message().text().equals("*msg")) { // 处理对曾经的原文的检查
|
||||||
|
if (update.message().replyToMessage() == null) {
|
||||||
|
return answer404(update);
|
||||||
|
}
|
||||||
|
msgsendReqRaw = update.message().replyToMessage();
|
||||||
|
} else if (update.message().text().startsWith("*msg")) { // 对接受到的原文进行检查
|
||||||
|
msgsendReqRaw = update.message();
|
||||||
|
} else {
|
||||||
|
return answer404(update); // 未定义的动作
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对发送请求的用户原文进行解析
|
||||||
|
msgsendReqBody = parseRequest(msgsendReqRaw);
|
||||||
|
if (msgsendReqBody == null) {
|
||||||
|
return answer404(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出发送目标信息
|
||||||
|
GetChatResponse targetChatReq = MornyCoeur.getAccount().execute(new GetChat(msgsendReqBody.targetId()));
|
||||||
|
if (!targetChatReq.isOk()) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
update.message().chat().id(),
|
||||||
|
String.format("""
|
||||||
|
<b><u>%d</u> FAILED</b>
|
||||||
|
<code>%s</code>""",
|
||||||
|
targetChatReq.errorCode(),
|
||||||
|
targetChatReq.description()
|
||||||
|
)
|
||||||
|
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
} else {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
update.message().chat().id(),
|
||||||
|
targetChatReq.chat().type() == Chat.Type.Private ? (
|
||||||
|
String.format("""
|
||||||
|
<i><u>%d</u>@%s</i>
|
||||||
|
🔒 <b>%s</b> %s""",
|
||||||
|
msgsendReqBody.targetId(),
|
||||||
|
escapeHtml(targetChatReq.chat().type().name()),
|
||||||
|
escapeHtml(targetChatReq.chat().firstName()+(targetChatReq.chat().lastName()==null?"":" "+targetChatReq.chat().lastName())),
|
||||||
|
targetChatReq.chat().username()==null?
|
||||||
|
String.format("<a href='tg://user?id=%d'>@@</a>", targetChatReq.chat().id()):
|
||||||
|
(escapeHtml("@"+targetChatReq.chat().username()))
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
String.format("""
|
||||||
|
<i><u>%d</u>@%s</i>:::
|
||||||
|
%s <b>%s</b>%s""",
|
||||||
|
msgsendReqBody.targetId(),
|
||||||
|
escapeHtml(targetChatReq.chat().type().name()),
|
||||||
|
switch (targetChatReq.chat().type()) {
|
||||||
|
case group -> "💭";
|
||||||
|
case channel -> "📢";
|
||||||
|
case supergroup -> "💬";
|
||||||
|
default -> "⭕️";
|
||||||
|
},
|
||||||
|
escapeHtml(targetChatReq.chat().title()),
|
||||||
|
targetChatReq.chat().username() != null?String.format(
|
||||||
|
" @%s", escapeHtml(targetChatReq.chat().username())
|
||||||
|
):""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
// 发送文本测试
|
||||||
|
SendResponse testSendResp = MornyCoeur.getAccount().execute(
|
||||||
|
parseMessageToSend(msgsendReqBody, update.message().chat().id()).replyToMessageId(update.message().messageId())
|
||||||
|
);
|
||||||
|
if (!testSendResp.isOk()) {
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
update.message().chat().id(),
|
||||||
|
String.format("""
|
||||||
|
<b><u>%d</u> FAILED</b>
|
||||||
|
<code>%s</code>""",
|
||||||
|
testSendResp.errorCode(),
|
||||||
|
testSendResp.description()
|
||||||
|
)
|
||||||
|
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static MessageToSend parseRequest (@Nonnull Message requestBody) {
|
||||||
|
|
||||||
|
final Matcher matcher = REGEX_MSG_SENDREQ_DATA_HEAD.matcher(requestBody.text());
|
||||||
|
if (matcher.matches()) {
|
||||||
|
long targetId = Long.parseLong(matcher.group(1));
|
||||||
|
ParseMode parseMode = matcher.group(2) == null ? null : switch (matcher.group(2)) {
|
||||||
|
case "*markdown", "*md", "*m↓" -> ParseMode.MarkdownV2;
|
||||||
|
case "*md1" -> ParseMode.Markdown;
|
||||||
|
case "*html" -> ParseMode.HTML;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
final int offset = "*msg".length()+matcher.group(1).length()+(matcher.group(2)==null?0:matcher.group(2).length())+1;
|
||||||
|
final ArrayList<MessageEntity> entities = new ArrayList<>();
|
||||||
|
if (requestBody.entities() != null) for (MessageEntity entity : requestBody.entities()) {
|
||||||
|
final MessageEntity parsed = new MessageEntity(entity.type(), entity.offset() - offset, entity.length());
|
||||||
|
if (entity.url() != null) parsed.url(entity.url());
|
||||||
|
if (entity.user() != null) parsed.user(entity.user());
|
||||||
|
if (entity.language() != null) parsed.language(entity.language());
|
||||||
|
entities.add(parsed);
|
||||||
|
}
|
||||||
|
return new MessageToSend(matcher.group(3), entities.toArray(MessageEntity[]::new), parseMode, targetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static SendMessage parseMessageToSend (@Nonnull MessageToSend body) {
|
||||||
|
return parseMessageToSend(body, body.targetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static SendMessage parseMessageToSend (@Nonnull MessageToSend body, long targetId) {
|
||||||
|
SendMessage sendingBody = new SendMessage(targetId, body.message);
|
||||||
|
if (body.entities != null) sendingBody.entities(body.entities);
|
||||||
|
if (body.parseMode != null) sendingBody.parseMode(body.parseMode);
|
||||||
|
return sendingBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean answer404 (@Nonnull Update update) {
|
||||||
|
MornyCoeur.extra().exec(new SendSticker(
|
||||||
|
update.message().chat().id(),
|
||||||
|
TelegramStickers.ID_404
|
||||||
|
).replyToMessageId(update.message().messageId()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件劫持与序列化工具.
|
||||||
|
* @since 0.4.2.0
|
||||||
|
*/
|
||||||
|
public class OnEventHackHandle extends EventListener {
|
||||||
|
|
||||||
|
/** 事件劫持请求列表 */
|
||||||
|
private static final Map<String, Hacker> hackers = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发事件劫持的限定条件.
|
||||||
|
* @since 0.4.2.0
|
||||||
|
*/
|
||||||
|
public enum HackType {
|
||||||
|
/** 只有相同用户发起的事件才会被触发 */
|
||||||
|
USER,
|
||||||
|
/** 只有相同群组内发生的事件才会触发 */
|
||||||
|
GROUP,
|
||||||
|
/** 任何事件都可以触发 */
|
||||||
|
ANY
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Hacker(long fromChatId, long fromMessageId) {
|
||||||
|
@Override public String toString() {
|
||||||
|
return fromChatId + "/" + fromMessageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.4.2.0
|
||||||
|
*/
|
||||||
|
public static void registerHack(long fromMessageId, long fromUserId, long fromChatId, @Nonnull HackType type) {
|
||||||
|
String rec = null;
|
||||||
|
switch (type) {
|
||||||
|
case USER -> rec = String.format("((%d))", fromUserId);
|
||||||
|
case GROUP -> rec = String.format("{{%d}}", fromChatId);
|
||||||
|
case ANY -> rec = "[[]]";
|
||||||
|
}
|
||||||
|
hackers.put(rec, new Hacker(fromChatId, fromMessageId));
|
||||||
|
logger.debug("add hacker track " + rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onEventHacked (Update update, long chatId, long fromUser) {
|
||||||
|
logger.debug(String.format("got event signed {{%d}}((%d))", chatId, fromUser));
|
||||||
|
Hacker x;
|
||||||
|
x = hackers.remove(String.format("((%d))", fromUser));
|
||||||
|
if (x == null) x = hackers.remove(String.format("{{%d}}", chatId));
|
||||||
|
if (x == null) x = hackers.remove("[[]]");
|
||||||
|
if (x == null) return false;
|
||||||
|
logger.debug("hacked event by " + x);
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(x.fromChatId, String.format(
|
||||||
|
"<code>%s</code>",
|
||||||
|
MsgEscape.escapeHtml(new GsonBuilder().setPrettyPrinting().create().toJson(update))
|
||||||
|
)).parseMode(ParseMode.HTML).replyToMessageId((int)x.fromMessageId));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.message().chat().id(), update.message().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEditedMessage (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.editedMessage().chat().id(), update.editedMessage().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onChannelPost (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.channelPost().chat().id(), update.channelPost().chat().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEditedChannelPost (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.editedChannelPost().chat().id(), update.editedChannelPost().chat().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInlineQuery (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, update.inlineQuery().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onChosenInlineResult (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, update.chosenInlineResult().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCallbackQuery (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, update.callbackQuery().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShippingQuery (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, update.shippingQuery().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreCheckoutQuery (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, update.preCheckoutQuery().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPoll (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPollAnswer (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, 0, update.pollAnswer().user().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMyChatMemberUpdated (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.myChatMember().chat().id(), update.myChatMember().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onChatMemberUpdated (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.chatMember().chat().id(), update.chatMember().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onChatJoinRequest (@Nonnull Update update) {
|
||||||
|
return onEventHacked(update, update.chatJoinRequest().chat().id(), update.chatJoinRequest().from().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.InlineQueryResult;
|
||||||
|
import com.pengrad.telegrambot.request.AnswerInlineQuery;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* telegram inlineQuery 功能的处理类,
|
||||||
|
* 也是一个 InlineQueryManager(还没做)
|
||||||
|
*
|
||||||
|
* @since 0.4.1.3
|
||||||
|
*/
|
||||||
|
public class OnInlineQueries extends EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.4.1.3
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onInlineQuery (@Nonnull Update update) {
|
||||||
|
|
||||||
|
List<InlineQueryUnit<?>> results = MornyCoeur.queryManager().query(update);
|
||||||
|
|
||||||
|
int cacheTime = Integer.MAX_VALUE;
|
||||||
|
boolean isPersonal = InlineQueryUnit.DEFAULT_INLINE_PERSONAL_RESP;
|
||||||
|
InlineQueryResult<?>[] inlineQueryResults = new InlineQueryResult<?>[results.size()];
|
||||||
|
for (int i = 0; i < results.size(); i++) {
|
||||||
|
inlineQueryResults[i] = results.get(i).result;
|
||||||
|
if (cacheTime > results.get(i).cacheTime()) cacheTime = results.get(i).cacheTime();
|
||||||
|
if (results.get(i).isPersonal()) isPersonal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.size() == 0) return false;
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new AnswerInlineQuery(
|
||||||
|
update.inlineQuery().id(), inlineQueryResults
|
||||||
|
).cacheTime(cacheTime).isPersonal(isPersonal));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.DeleteMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class OnKuohuanhuanNeedSleep extends EventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update update) {
|
||||||
|
final GregorianCalendar time = new GregorianCalendar(Locale.TAIWAN);
|
||||||
|
time.setTimeInMillis(System.currentTimeMillis());
|
||||||
|
if (
|
||||||
|
( update.message().from().id() == 786563752L && (
|
||||||
|
time.get(Calendar.HOUR_OF_DAY) >= 23 ||
|
||||||
|
time.get(Calendar.HOUR_OF_DAY) < 5
|
||||||
|
)) || ( update.message().from().id() == 1075871712L && (
|
||||||
|
(time.get(Calendar.HOUR_OF_DAY) >= 22 && time.get(Calendar.MINUTE) >= 30) ||
|
||||||
|
time.get(Calendar.HOUR_OF_DAY) >= 23 ||
|
||||||
|
time.get(Calendar.HOUR_OF_DAY) < 5
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
MornyCoeur.extra().exec(
|
||||||
|
new DeleteMessage(update.message().chat().id(),
|
||||||
|
update.message().messageId())
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.daemon.MedicationTimer;
|
||||||
|
import cc.sukazyo.cono.morny.daemon.MornyDaemons;
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class OnMedicationNotifyApply extends EventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEditedChannelPost (@NotNull Update update) {
|
||||||
|
return editedMessageProcess(update.editedChannelPost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEditedMessage (@NotNull Update update) {
|
||||||
|
return editedMessageProcess(update.editedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean editedMessageProcess (Message edited) {
|
||||||
|
if (edited.chat().id() != MedicationTimer.NOTIFY_CHAT) return false;
|
||||||
|
MornyDaemons.medicationTimerInstance.refreshNotificationWrite(edited);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
|
||||||
|
public class OnRandomlyTriggered extends EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* function CODE_IK0XA1
|
||||||
|
*/
|
||||||
|
// @Override
|
||||||
|
// public boolean onMessage (@Nonnull Update update) {
|
||||||
|
//
|
||||||
|
// if (update.message().text() == null) return false;
|
||||||
|
//
|
||||||
|
// if (update.message().text().contains("急") && Math.random()<(1d/20)) {
|
||||||
|
// MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
// update.message().chat().id(),
|
||||||
|
// "急也没用"
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return false;
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class OnTelegramCommand extends EventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update event) {
|
||||||
|
if (event.message().text() == null || !event.message().text().startsWith("/") || event.message().text().startsWith("/ ")) {
|
||||||
|
logger.debug("not command");
|
||||||
|
return false; // 检测到非(命令格式)文本,忽略掉命令处理
|
||||||
|
}
|
||||||
|
final InputCommand command = new InputCommand(event.message().text().substring(1));
|
||||||
|
if (!command.getCommand().matches("^\\w+$")) { logger.debug("not command");return false; }
|
||||||
|
logger.debug("is command");
|
||||||
|
if (command.getTarget() != null && !MornyCoeur.getUsername().equals(command.getTarget())) {
|
||||||
|
return true; // 检测到命令并非针对 morny,退出整个事件处理链
|
||||||
|
}
|
||||||
|
return MornyCoeur.commandManager().execute(command, event); // 转交命令管理器执行命令
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阻止 {@link MornyCoeur#latestEventTimestamp 指定时间} 之前的事件处理.
|
||||||
|
* <p>
|
||||||
|
* 只支持以下事件
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link EventListener#onMessage(Update) 收到消息}</li>
|
||||||
|
* <li>{@link EventListener#onEditedMessage(Update) 消息被更新}</li>
|
||||||
|
* <li>{@link EventListener#onChannelPost(Update) 收到频道消息}</li>
|
||||||
|
* <li>{@link EventListener#onEditedChannelPost(Update) 频道消息被更新}</li>
|
||||||
|
* </ul>
|
||||||
|
* @see #isOutdated 时间判断
|
||||||
|
*/
|
||||||
|
public class OnUpdateTimestampOffsetLock extends EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查传入时间是否在要求时间之前(即"过期").
|
||||||
|
* @param timestamp 传入时间,秒级
|
||||||
|
* @return 如果传入时间在要求时间<u>之前</u>,返回true,反之false
|
||||||
|
* @since 0.4.2.7
|
||||||
|
*/
|
||||||
|
public boolean isOutdated(long timestamp) {
|
||||||
|
return timestamp < MornyCoeur.getLatestEventTimestamp()/1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update update) {
|
||||||
|
return isOutdated(update.message().date());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.4.2.6 */
|
||||||
|
@Override
|
||||||
|
public boolean onEditedMessage (@Nonnull Update update) {
|
||||||
|
return isOutdated(update.editedMessage().editDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.4.2.6 */
|
||||||
|
@Override
|
||||||
|
public boolean onChannelPost (@Nonnull Update update) {
|
||||||
|
return isOutdated(update.channelPost().date());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @since 0.4.2.6 */
|
||||||
|
@Override
|
||||||
|
public boolean onEditedChannelPost (@Nonnull Update update) {
|
||||||
|
return isOutdated(update.editedChannelPost().editDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.util.UniversalCommand;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class OnUserRandoms extends EventListener {
|
||||||
|
|
||||||
|
private static final Pattern USER_OR_CN_QUERY = Pattern.compile("(.+)还是(.+)");
|
||||||
|
private static final Pattern USER_OR_EN_QUERY = Pattern.compile("(.+)or(.+)");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@NotNull Update update) {
|
||||||
|
|
||||||
|
if (update.message().text() == null) return false;
|
||||||
|
if (!update.message().text().startsWith("/")) return false;
|
||||||
|
|
||||||
|
final String[] preProcess = UniversalCommand.format(update.message().text());
|
||||||
|
if (preProcess.length > 1) return false;
|
||||||
|
final String query = preProcess[0];
|
||||||
|
|
||||||
|
// ----- START CODE BLOCK COMMENT -----
|
||||||
|
// 这里实现思路和代码优化有至少一半是 copilot 和 IDEA 提供的
|
||||||
|
// 实现思路都可以从人类手里抢一半贡献太恐怖了aba
|
||||||
|
String result = null;
|
||||||
|
final Matcher matcher;
|
||||||
|
if (query.contains("还是")) {
|
||||||
|
matcher = USER_OR_CN_QUERY.matcher(query);
|
||||||
|
} else {
|
||||||
|
matcher = USER_OR_EN_QUERY.matcher(query);
|
||||||
|
}
|
||||||
|
if (matcher.find()) {
|
||||||
|
result = ThreadLocalRandom.current().nextBoolean() ? matcher.group(1) : matcher.group(2);
|
||||||
|
}
|
||||||
|
// ----- STOP CODE BLOCK COMMENT -----
|
||||||
|
|
||||||
|
if (result == null) return false;
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
update.message().chat().id(), result
|
||||||
|
).replyToMessageId(update.message().messageId()));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.event;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.EventListener;
|
||||||
|
import cc.sukazyo.cono.morny.util.UniversalCommand;
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.CommonConvert.stringsConnecting;
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
|
||||||
|
|
||||||
|
public class OnUserSlashAction extends EventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMessage (@Nonnull Update event) {
|
||||||
|
final String text = event.message().text();
|
||||||
|
if (text == null) return false;
|
||||||
|
|
||||||
|
if (text.startsWith("/"))
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Due to @Lapis_Apple, we stopped slash action function at .DP7 groups.
|
||||||
|
/// It may be enabled after some updates when the function will not be conflicted to other bots.
|
||||||
|
// if (event.message().chat().id() == ) return false;
|
||||||
|
//{ if (event.message().chat().title() != null && event.message().chat().title().contains(".DP7")) {
|
||||||
|
// logger.info(String.format("""
|
||||||
|
// Chat slash action ignored due to the following keyword.
|
||||||
|
// - %s
|
||||||
|
// - ".DP7\"""",
|
||||||
|
// TGToString.as(event.message().chat()).toStringFullNameId()
|
||||||
|
// ));
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
final String[] action = UniversalCommand.format(text);
|
||||||
|
action[0] = action[0].substring(1);
|
||||||
|
|
||||||
|
if (action[0].matches("^\\w+(@\\w+)?$")) {
|
||||||
|
return false; // 忽略掉 Telegram 命令格式的输入
|
||||||
|
} else if (action[0].contains("/")) {
|
||||||
|
return false; // 忽略掉疑似目录格式的输入
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isHardParse = "".equals(action[0]);
|
||||||
|
/* 忽略空数据 */ if (isHardParse && action.length < 2) { return false; }
|
||||||
|
final String verb = isHardParse ? action[1] : action[0];
|
||||||
|
final boolean hasObject = action.length != (isHardParse?2:1);
|
||||||
|
final String object =
|
||||||
|
hasObject ?
|
||||||
|
stringsConnecting(action, " ", isHardParse?2:1, action.length-1) :
|
||||||
|
"";
|
||||||
|
final Message origin = event.message();
|
||||||
|
final Message target = (event.message().replyToMessage() == null ? (
|
||||||
|
origin
|
||||||
|
): (
|
||||||
|
event.message().replyToMessage()
|
||||||
|
));
|
||||||
|
|
||||||
|
MornyCoeur.extra().exec(new SendMessage(
|
||||||
|
event.message().chat().id(),
|
||||||
|
String.format(
|
||||||
|
"%s %s%s %s %s!",
|
||||||
|
TGToString.as(origin).getSenderFirstNameRefHtml(),
|
||||||
|
escapeHtml(verb), escapeHtml((hasObject?"":"了")),
|
||||||
|
origin==target ?
|
||||||
|
"<a href='tg://user?id="+TGToString.as(target).getSenderId()+"'>自己</a>" :
|
||||||
|
TGToString.as(target).getSenderFirstNameRefHtml(),
|
||||||
|
escapeHtml(hasObject ? object+" " : "")
|
||||||
|
)
|
||||||
|
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.query;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ITelegramQuery {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
List<InlineQueryUnit<?>> query (Update event);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.query;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MornyQueries {
|
||||||
|
|
||||||
|
private final List<ITelegramQuery> queryInstances = new ArrayList<>();
|
||||||
|
|
||||||
|
public MornyQueries () {
|
||||||
|
queryInstances.add(new RawText());
|
||||||
|
queryInstances.add(new MyInformation());
|
||||||
|
queryInstances.add(new ShareToolTwitter());
|
||||||
|
queryInstances.add(new ShareToolBilibili());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public List<InlineQueryUnit<?>> query (@Nonnull Update event) {
|
||||||
|
final List<InlineQueryUnit<?>> results = new ArrayList<>();
|
||||||
|
for (ITelegramQuery instance : queryInstances) {
|
||||||
|
final List<InlineQueryUnit<?>> r = instance.query(event);
|
||||||
|
if (r!=null) results.addAll(r);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.query;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
|
||||||
|
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
|
||||||
|
|
||||||
|
public class MyInformation implements ITelegramQuery {
|
||||||
|
|
||||||
|
public static final String ID_PREFIX = "[morny/info/me]";
|
||||||
|
public static final String TITLE = "My Account Information";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public List<InlineQueryUnit<?>> query(Update event) {
|
||||||
|
if (!(event.inlineQuery().query() == null || "".equals(event.inlineQuery().query()))) return null;
|
||||||
|
return Collections.singletonList(new InlineQueryUnit<>(new InlineQueryResultArticle(
|
||||||
|
inlineIds(ID_PREFIX), TITLE,
|
||||||
|
new InputTextMessageContent(
|
||||||
|
TelegramUserInformation.informationOutputHTML(event.inlineQuery().from())
|
||||||
|
).parseMode(ParseMode.HTML)
|
||||||
|
)).isPersonal(true).cacheTime(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/main/java/cc/sukazyo/cono/morny/bot/query/RawText.java
Normal file
31
src/main/java/cc/sukazyo/cono/morny/bot/query/RawText.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.query;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
|
||||||
|
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
|
||||||
|
|
||||||
|
public class RawText implements ITelegramQuery {
|
||||||
|
|
||||||
|
public static final String ID_PREFIX = "[morny/r/text]";
|
||||||
|
public static final String TITLE = "Raw Text";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public List<InlineQueryUnit<?>> query (Update event) {
|
||||||
|
if (event.inlineQuery().query() == null || "".equals(event.inlineQuery().query())) return null;
|
||||||
|
return Collections.singletonList(new InlineQueryUnit<>(new InlineQueryResultArticle(
|
||||||
|
inlineIds(ID_PREFIX, event.inlineQuery().query()), TITLE,
|
||||||
|
new InputTextMessageContent(event.inlineQuery().query())
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.query;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
import cc.sukazyo.cono.morny.util.BiliTool;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
|
||||||
|
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
//import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
|
||||||
|
|
||||||
|
public class ShareToolBilibili implements ITelegramQuery {
|
||||||
|
|
||||||
|
public static final String TITLE_BILI_AV = "[bilibili] Share video / av";
|
||||||
|
public static final String TITLE_BILI_BV = "[bilibili] Share video / BV";
|
||||||
|
public static final String ID_PREFIX_BILI_AV = "[morny/share/bili/av]";
|
||||||
|
public static final String ID_PREFIX_BILI_BV = "[morny/share/bili/bv]";
|
||||||
|
public static final Pattern REGEX_BILI_VIDEO = Pattern.compile("^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d+)|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]+))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d+)|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]+))$");
|
||||||
|
|
||||||
|
private static final String SHARE_FORMAT_HTML = "<a href='%s'>%s</a>";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public List<InlineQueryUnit<?>> query (Update event) {
|
||||||
|
if (event.inlineQuery().query() == null) return null;
|
||||||
|
final Matcher regex = REGEX_BILI_VIDEO.matcher(event.inlineQuery().query());
|
||||||
|
if (regex.matches()) {
|
||||||
|
|
||||||
|
// logger.debug(String.format(
|
||||||
|
// "====== ok\n1: %s\n2: %s\n3: %s\n4: %s\n5: %s\n6: %s\n7: %s",
|
||||||
|
// regex.group(1), regex.group(2), regex.group(3), regex.group(4),
|
||||||
|
// regex.group(5), regex.group(6), regex.group(7)
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// get video id from input, also get video part id
|
||||||
|
String av = regex.group(2)==null ? regex.group(6)==null ? null : regex.group(6) : regex.group(2);
|
||||||
|
String bv = regex.group(3)==null ? regex.group(7)==null ? null : regex.group(7) : regex.group(3);
|
||||||
|
// logger.trace(String.format("catch id av[%s] bv[%s]", av, bv));
|
||||||
|
final int part = regex.group(5)==null ? -1 : Integer.parseInt(regex.group(5));
|
||||||
|
// logger.trace(String.format("catch part [%s]", part));
|
||||||
|
if (av == null) {
|
||||||
|
assert bv != null;
|
||||||
|
av = String.valueOf(BiliTool.toAv(bv));
|
||||||
|
// logger.trace(String.format("converted bv[%s] to av[%s]", bv, av));
|
||||||
|
} else {
|
||||||
|
bv = BiliTool.toBv(Long.parseLong(av));
|
||||||
|
// logger.trace(String.format("converted av[%s] to bv[%s]", av, bv));
|
||||||
|
}
|
||||||
|
// build standard share links
|
||||||
|
final String linkPartParam = part==-1 ? "" : "?p="+part;
|
||||||
|
final String linkAv = "https://www.bilibili.com/video/av"+av + linkPartParam;
|
||||||
|
final String linkBv = "https://www.bilibili.com/video/BV"+bv + linkPartParam;
|
||||||
|
final String idAv = "av"+av;
|
||||||
|
final String idBv = "BV"+bv;
|
||||||
|
// logger.trace("built all data.");
|
||||||
|
|
||||||
|
// build share message element
|
||||||
|
List<InlineQueryUnit<?>> result = new ArrayList<>();
|
||||||
|
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
|
||||||
|
inlineIds(ID_PREFIX_BILI_AV+av), TITLE_BILI_AV+av,
|
||||||
|
new InputTextMessageContent(String.format(SHARE_FORMAT_HTML, linkAv, idAv)).parseMode(ParseMode.HTML)
|
||||||
|
)));
|
||||||
|
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
|
||||||
|
inlineIds(ID_PREFIX_BILI_BV+bv), TITLE_BILI_BV+bv,
|
||||||
|
new InputTextMessageContent(String.format(SHARE_FORMAT_HTML, linkBv, idBv)).parseMode(ParseMode.HTML)
|
||||||
|
)));
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package cc.sukazyo.cono.morny.bot.query;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
|
||||||
|
import com.pengrad.telegrambot.model.Update;
|
||||||
|
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
|
||||||
|
|
||||||
|
public class ShareToolTwitter implements ITelegramQuery {
|
||||||
|
|
||||||
|
public static final String TITLE_VX = "[tweet] Share as VxTwitter";
|
||||||
|
public static final String TITLE_VX_COMBINED = "[tweet] Share as VxTwitter(combination)";
|
||||||
|
public static final String ID_PREFIX_VX = "[morny/share/twitter/vxtwi]";
|
||||||
|
public static final String ID_PREFIX_VX_COMBINED = "[morny/share/twitter/vxtwi_combine]";
|
||||||
|
|
||||||
|
public static final Pattern REGEX_TWEET_LINK = Pattern.compile(
|
||||||
|
"^(?:https?://)?((?:(?:c\\.)?vx|fx|www\\.)?twitter\\.com)/((\\w+)/status/(\\d+)(?:/photo/(\\d+))?)/?(\\?[\\w&=-]+)?$");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public List<InlineQueryUnit<?>> query (@Nonnull Update event) {
|
||||||
|
if (event.inlineQuery().query() == null) return null;
|
||||||
|
final Matcher regex = REGEX_TWEET_LINK.matcher(event.inlineQuery().query());
|
||||||
|
if (regex.matches()) {
|
||||||
|
|
||||||
|
List<InlineQueryUnit<?>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
|
||||||
|
inlineIds(ID_PREFIX_VX+event.inlineQuery().query()), TITLE_VX,
|
||||||
|
String.format("https://vxtwitter.com/%s", regex.group(2))
|
||||||
|
)));
|
||||||
|
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
|
||||||
|
inlineIds(ID_PREFIX_VX_COMBINED+event.inlineQuery().query()), TITLE_VX_COMBINED,
|
||||||
|
String.format("https://c.vxtwitter.com/%s", regex.group(2))
|
||||||
|
)));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package cc.sukazyo.cono.morny.daemon;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.MornyCoeur;
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonFormat;
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.MessageEntity;
|
||||||
|
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||||
|
import com.pengrad.telegrambot.request.EditMessageText;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.response.SendResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class MedicationTimer extends Thread {
|
||||||
|
|
||||||
|
public static final long NOTIFY_CHAT = -1001729016815L;
|
||||||
|
public static final String NOTIFY_MESSAGE = "\uD83C\uDF65⏲";
|
||||||
|
private static final String DAEMON_THREAD_NAME = "TIMER_Medication";
|
||||||
|
|
||||||
|
private static final long LAST_NOTIFY_ID_NULL = -1L;
|
||||||
|
private long lastNotify = LAST_NOTIFY_ID_NULL;
|
||||||
|
|
||||||
|
|
||||||
|
MedicationTimer () {
|
||||||
|
super(DAEMON_THREAD_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run () {
|
||||||
|
logger.info("MedicationTimer started");
|
||||||
|
while (!interrupted()) {
|
||||||
|
try {
|
||||||
|
waitToNextRoutine();
|
||||||
|
sendNotification();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
interrupt();
|
||||||
|
logger.info("MedicationTimer was interrupted, will be exit now");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Unexpected error occurred");
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("MedicationTimer stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNotification () {
|
||||||
|
final SendResponse resp = MornyCoeur.extra().exec(new SendMessage(NOTIFY_CHAT, NOTIFY_MESSAGE));
|
||||||
|
if (resp.isOk()) lastNotify = resp.message().messageId();
|
||||||
|
else lastNotify = LAST_NOTIFY_ID_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshNotificationWrite (Message edited) {
|
||||||
|
if (edited.messageId() != lastNotify) return;
|
||||||
|
final String editTime = CommonFormat.formatDate(edited.editDate()*1000, 8);
|
||||||
|
ArrayList<MessageEntity> entities = new ArrayList<>();
|
||||||
|
if (edited.entities() != null) entities.addAll(List.of(edited.entities()));
|
||||||
|
entities.add(new MessageEntity(MessageEntity.Type.italic, edited.text().length() + "\n-- ".length(), editTime.length()));
|
||||||
|
EditMessageText sending = new EditMessageText(
|
||||||
|
NOTIFY_CHAT,
|
||||||
|
edited.messageId(),
|
||||||
|
edited.text() + "\n-- " + editTime + " --"
|
||||||
|
).parseMode(ParseMode.HTML).entities(entities.toArray(MessageEntity[]::new));
|
||||||
|
MornyCoeur.extra().exec(sending);
|
||||||
|
lastNotify = LAST_NOTIFY_ID_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long calcNextRoutineTimestamp () {
|
||||||
|
return ((System.currentTimeMillis()+8*60*60*1000) / (12*60*60*1000) + 1) * 12*60*60*1000 - 8*60*60*1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitToNextRoutine () throws InterruptedException {
|
||||||
|
sleep(calcNextRoutineTimestamp() - System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
src/main/java/cc/sukazyo/cono/morny/daemon/MornyDaemons.java
Normal file
30
src/main/java/cc/sukazyo/cono/morny/daemon/MornyDaemons.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package cc.sukazyo.cono.morny.daemon;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class MornyDaemons {
|
||||||
|
|
||||||
|
public static final MedicationTimer medicationTimerInstance = new MedicationTimer();
|
||||||
|
|
||||||
|
public static void start () {
|
||||||
|
logger.info("ALL Morny Daemons starting...");
|
||||||
|
// TrackerDataManager.init();
|
||||||
|
medicationTimerInstance.start();
|
||||||
|
logger.info("Morny Daemons started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop () {
|
||||||
|
|
||||||
|
logger.info("ALL Morny Daemons stopping...");
|
||||||
|
|
||||||
|
// TrackerDataManager.DAEMON.interrupt();
|
||||||
|
medicationTimerInstance.interrupt();
|
||||||
|
|
||||||
|
// TrackerDataManager.trackingLock.lock();
|
||||||
|
try { medicationTimerInstance.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); }
|
||||||
|
|
||||||
|
logger.info("ALL Morny Daemons STOPPED.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package cc.sukazyo.cono.morny.daemon;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.Log.logger;
|
||||||
|
|
||||||
|
public class TrackerDataManager {
|
||||||
|
|
||||||
|
public static final ReentrantLock trackingLock = new ReentrantLock();
|
||||||
|
|
||||||
|
private static final ReentrantLock recordLock = new ReentrantLock();
|
||||||
|
private static HashMap<Long, HashMap<Long, TreeSet<Long>>> record = new HashMap<>();
|
||||||
|
|
||||||
|
public static final TrackerDaemon DAEMON = new TrackerDaemon();
|
||||||
|
public static class TrackerDaemon extends Thread {
|
||||||
|
|
||||||
|
public TrackerDaemon () { this.setName("TRACKER"); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run () {
|
||||||
|
trackingLock.lock();
|
||||||
|
logger.info("Tracker started.");
|
||||||
|
long lastWaitTimestamp = System.currentTimeMillis();
|
||||||
|
boolean postProcess = false;
|
||||||
|
do {
|
||||||
|
lastWaitTimestamp += 10 * 60 * 1000;
|
||||||
|
long sleeping = lastWaitTimestamp - System.currentTimeMillis();
|
||||||
|
if (sleeping > 0) {
|
||||||
|
try { Thread.sleep(sleeping); } catch (InterruptedException e) { interrupt(); }
|
||||||
|
} else {
|
||||||
|
logger.warn("Tracker may be too busy to process data!!");
|
||||||
|
lastWaitTimestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
if (interrupted()) {
|
||||||
|
postProcess = true;
|
||||||
|
logger.info("CALLED TO EXIT! writing cache.");
|
||||||
|
}
|
||||||
|
if (record.size() != 0) {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
else logger.info("nothing to do yet");
|
||||||
|
} while (!postProcess);
|
||||||
|
trackingLock.unlock();
|
||||||
|
logger.info("Tracker exited.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void record (long chat, long user, long timestamp) {
|
||||||
|
recordLock.lock();
|
||||||
|
if (!record.containsKey(chat)) record.put(chat, new HashMap<>());
|
||||||
|
HashMap<Long, TreeSet<Long>> chatUsers = record.get(chat);
|
||||||
|
if (!chatUsers.containsKey(user)) chatUsers.put(user, new TreeSet<>());
|
||||||
|
TreeSet<Long> userRecords = chatUsers.get(user);
|
||||||
|
userRecords.add(timestamp);
|
||||||
|
recordLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init () {
|
||||||
|
DAEMON.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save () {
|
||||||
|
logger.info("start writing tracker data.");
|
||||||
|
save(reset());
|
||||||
|
logger.info("done writing tracker data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashMap<Long, HashMap<Long, TreeSet<Long>>> reset () {
|
||||||
|
recordLock.lock();
|
||||||
|
HashMap<Long, HashMap<Long, TreeSet<Long>>> recordOld = record;
|
||||||
|
record = new HashMap<>();
|
||||||
|
recordLock.unlock();
|
||||||
|
return recordOld;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save (HashMap<Long, HashMap<Long, TreeSet<Long>>> record) {
|
||||||
|
|
||||||
|
{
|
||||||
|
if (!record.containsKey(0L)) record.put(0L, new HashMap<>());
|
||||||
|
HashMap<Long, TreeSet<Long>> chatUsers = record.get(0L);
|
||||||
|
if (!chatUsers.containsKey(0L)) chatUsers.put(0L, new TreeSet<>());
|
||||||
|
TreeSet<Long> userRecords = chatUsers.get(0L);
|
||||||
|
userRecords.add(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
record.forEach((chat, chatUsers) -> chatUsers.forEach((user, userRecords) -> {
|
||||||
|
|
||||||
|
long dayCurrent = -1;
|
||||||
|
FileChannel channelCurrent = null;
|
||||||
|
|
||||||
|
for (long timestamp : userRecords) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
long day = timestamp / (24 * 60 * 60 * 1000);
|
||||||
|
if (dayCurrent != day) {
|
||||||
|
if (channelCurrent != null) channelCurrent.close();
|
||||||
|
channelCurrent = openFile(chat, user, day);
|
||||||
|
dayCurrent = day;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert channelCurrent != null;
|
||||||
|
channelCurrent.write(ByteBuffer.wrap(
|
||||||
|
String.format("%d\n", timestamp).getBytes(StandardCharsets.UTF_8)
|
||||||
|
));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(String.format("exception in write tracker data: %d/%d/%d", chat, user, timestamp));
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileChannel openFile (long chat, long user, long day) throws IOException {
|
||||||
|
File data = new File(String.format("./data/tracker/%d/%d", chat, user));
|
||||||
|
if (!data.isDirectory()) if (!data.mkdirs()) throw new IOException("Cannot create file directory " + data.getPath());
|
||||||
|
File file = new File(data, String.valueOf(day));
|
||||||
|
if (!file.isFile()) if (!file.createNewFile()) throw new IOException("Cannot create file " + file.getPath());
|
||||||
|
return new FileOutputStream(file, true).getChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
src/main/java/cc/sukazyo/cono/morny/data/MornyJrrp.java
Normal file
46
src/main/java/cc/sukazyo/cono/morny/data/MornyJrrp.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonConvert;
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonEncrypt;
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morny 的 jrrp 运算类.
|
||||||
|
*
|
||||||
|
* @see #getJrrpFromTelegramUser(User,long)
|
||||||
|
* @see #calcJrrpXmomi(long,long)
|
||||||
|
* @since 0.4.2.9
|
||||||
|
*/
|
||||||
|
public class MornyJrrp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 telegram 用户和时间戳作为参数获取 jrrp.
|
||||||
|
*
|
||||||
|
* @see #calcJrrpXmomi 当前版本的实现算法 {@code Xmomi}
|
||||||
|
* @since 0.4.2.9
|
||||||
|
* @param user telegram 用户
|
||||||
|
* @param timestamp 时间戳
|
||||||
|
* @return 通过当前版本的算法计算出的用户 jrrp 值,取值为 {@code [0.00, 100.00]}
|
||||||
|
*/
|
||||||
|
public static double getJrrpFromTelegramUser (User user, long timestamp) {
|
||||||
|
return calcJrrpXmomi(user.id(), timestamp / (1000 * 60 * 60 * 24)) * 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code Xmomi} 版本的 jrrp 算法.
|
||||||
|
* <p>
|
||||||
|
* 算法规则为,将用户id与日期戳链接为 <u><code>uid@daystamp</code></u> 这样的字符串,
|
||||||
|
* 然后通过 MD5 计算出字符串的哈希值,取哈希值前4个字节,将其作为16进制数值表示法转换为取值为 {@code [0x0000, 0xffff]} 的数值,
|
||||||
|
* 得到的数值除以区间最大值 {@code 0xffff} 即可得到一个分布在 {@code [0.0, 1.0]} 之间的分布值,
|
||||||
|
* 这个分布值乘以 {@code 100.0},即为计算得到的 jrrp 数值。
|
||||||
|
*
|
||||||
|
* @since 0.4.2.9
|
||||||
|
* @param userId telegram 用户 uid
|
||||||
|
* @param dayStamp unix 时间戳转换为日期单位后的数值. 数值应该在转换前转换时区
|
||||||
|
* @return 算法得到的 jrrp 值,取值为 {@code [0.00. 100.00]}
|
||||||
|
*/
|
||||||
|
public static double calcJrrpXmomi (long userId, long dayStamp) {
|
||||||
|
return (double)Long.parseLong(CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(userId + "@" + dayStamp)).substring(0, 4), 16) / (double)0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
src/main/java/cc/sukazyo/cono/morny/data/NbnhhshQuery.java
Normal file
50
src/main/java/cc/sukazyo/cono/morny/data/NbnhhshQuery.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
public class NbnhhshQuery {
|
||||||
|
|
||||||
|
public static class Word {
|
||||||
|
public String name;
|
||||||
|
public String[] trans;
|
||||||
|
public String[] inputting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GuessResult {
|
||||||
|
public Word[] words;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record GuessReq (String text) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String API_URL = "https://lab.magiconch.com/api/nbnhhsh/";
|
||||||
|
public static final String API_GUESS_METHOD = "guess/";
|
||||||
|
public static final String API_GUESS_DATA_TEMPLATE = "{ \"text\": \"%s\" }";
|
||||||
|
|
||||||
|
private static final OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
public static GuessResult sendGuess (String text) throws IOException {
|
||||||
|
final String reqJsonText = new Gson().toJson(new GuessReq(text));
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(API_URL + API_GUESS_METHOD)
|
||||||
|
.post(RequestBody.create(JSON, reqJsonText))
|
||||||
|
.build();
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
final ResponseBody body = response.body();
|
||||||
|
if (body == null) throw new IOException("Null body.");
|
||||||
|
final String x = "{ \"words\": " + body.string() + " }";
|
||||||
|
return new Gson().fromJson(x, GuessResult.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package cc.sukazyo.cono.morny.data;
|
package cc.sukazyo.cono.morny.data;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import cc.sukazyo.cono.morny.util.tgapi.ExtraAction;
|
||||||
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import com.pengrad.telegrambot.request.SendSticker;
|
||||||
|
import com.pengrad.telegrambot.response.SendResponse;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 存放 bot 使用到的贴纸
|
* 存放 bot 使用到的贴纸
|
||||||
@ -21,30 +23,42 @@ public class TelegramStickers {
|
|||||||
public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA";
|
public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA";
|
||||||
public static final String ID_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ";
|
public static final String ID_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ";
|
||||||
public static final String ID_NETWORK_ERR = "CAACAgEAAxkBAAID0WNJgNEkD726KW4vZeFlw0FlVVyNAAIXJgACePzGBb50o7O1RbxoKgQ";
|
public static final String ID_NETWORK_ERR = "CAACAgEAAxkBAAID0WNJgNEkD726KW4vZeFlw0FlVVyNAAIXJgACePzGBb50o7O1RbxoKgQ";
|
||||||
public static final String ID_501 = "CAACAgEAAxkBAAIHbGUhJ8zm2Sb_c0YU-DYQ6xb-ZDtaAAKdJwACePzGBTOftDZL6X7vMAQ";
|
|
||||||
|
|
||||||
@Nonnull
|
public static void echoAllStickers (ExtraAction actionObject, long sentChat, int replyToMessageId) {
|
||||||
public static Map<String, String> map () {
|
|
||||||
final LinkedHashMap<String, String> mapping = new LinkedHashMap<>();
|
|
||||||
for (Field object : TelegramStickers.class.getFields()) {
|
for (Field object : TelegramStickers.class.getFields()) {
|
||||||
if (object.getType()==String.class && object.getName().startsWith("ID_")) {
|
if (object.getType()==String.class && object.getName().startsWith("ID_")) {
|
||||||
try {
|
try {
|
||||||
mapping.put(object.getName(), (String)object.get(""));
|
|
||||||
|
final String stickerId = (String)object.get("");
|
||||||
|
SendSticker echo = new SendSticker(sentChat, stickerId);
|
||||||
|
SendMessage echoName = new SendMessage(sentChat, object.getName());
|
||||||
|
if (replyToMessageId!=-1) echo.replyToMessageId(replyToMessageId);
|
||||||
|
SendResponse echoedName = actionObject.exec(echoName);
|
||||||
|
actionObject.exec(echo.replyToMessageId(echoedName.message().messageId()));
|
||||||
|
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mapping;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
public static void echoStickerByID (String stickerFieldID, ExtraAction actionObject, long sentChat, int replyToMessageId) {
|
||||||
public static Map.Entry<String, String> getById (@Nonnull String stickerFieldID)
|
|
||||||
throws NoSuchFieldException {
|
|
||||||
try {
|
try {
|
||||||
// normally get the sticker and echo
|
// normally get the sticker and echo
|
||||||
Field field = TelegramStickers.class.getField(stickerFieldID);
|
Field sticker = TelegramStickers.class.getField(stickerFieldID);
|
||||||
return Map.entry(field.getName(), (String)field.get(""));
|
SendMessage echoName = new SendMessage(sentChat, sticker.getName());
|
||||||
|
SendSticker echo = new SendSticker(sentChat, (String)sticker.get(""));
|
||||||
|
if (replyToMessageId!=-1) echo.replyToMessageId(replyToMessageId);
|
||||||
|
SendResponse echoedName = actionObject.exec(echoName);
|
||||||
|
actionObject.exec(echo.replyToMessageId(echoedName.message().messageId()));
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
// no such sticker found
|
||||||
|
SendSticker echo404 = new SendSticker(sentChat, TelegramStickers.ID_404);
|
||||||
|
if (replyToMessageId!=-1) echo404.replyToMessageId(replyToMessageId);
|
||||||
|
actionObject.exec(echo404);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
// java-reflect get sticker FILE_ID failed
|
// java-reflect get sticker FILE_ID failed
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
@ -0,0 +1,89 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data.ip186;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 {@value #SITE_URL} 进行 {@link #queryIp ip}/{@link #queryWhois whois} 数据查询的工具类
|
||||||
|
*
|
||||||
|
* @since 0.4.2.10
|
||||||
|
*/
|
||||||
|
public class IP186QueryHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求所使用的 HTTP API 站点链接
|
||||||
|
* @since 0.4.2.10
|
||||||
|
*/
|
||||||
|
public static final String SITE_URL = "https://ip.186526.xyz/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进行 {@link #queryIp ip 查询}时所使用的 API 参数.<br>
|
||||||
|
* 目的使 API 直接返回原始数据
|
||||||
|
*/
|
||||||
|
private static final String QUERY_IP_PARAM = "type=json&format=true";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进行 {@link #queryWhois whois 查询}时所使用的 API 参数.<br>
|
||||||
|
* 目的使 API 直接返回原始数据
|
||||||
|
*/
|
||||||
|
private static final String QUERY_WHOIS_PARAM = "type=plain";
|
||||||
|
|
||||||
|
/** 请求时使用的 OkHttp 请求工具实例 */
|
||||||
|
private static final OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 {@value #SITE_URL} 获取 ip 信息.
|
||||||
|
* @see #QUERY_IP_PARAM 发送请求时所使用的 API 参数
|
||||||
|
* @param ip 需要进行查询的 ip
|
||||||
|
* @return 查询结果。data 根据 {@value #SITE_URL} 的规则以 json 序列化
|
||||||
|
* @throws IOException 任何请求或解析错误
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static IP186QueryResponse queryIp (String ip) throws IOException {
|
||||||
|
final String requestUrl = SITE_URL + ip;
|
||||||
|
return commonQuery(requestUrl, QUERY_IP_PARAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 {@value #SITE_URL} 获取域名信息.
|
||||||
|
* @see #QUERY_WHOIS_PARAM 发送请求时所使用的 API 参数
|
||||||
|
* @param domain 需要进行查询的域名
|
||||||
|
* @return 查询结果。data 根据 {@value #SITE_URL} 的规则以 plain 序列化
|
||||||
|
* @throws IOException 任何请求或解析错误
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static IP186QueryResponse queryWhois (String domain) throws IOException {
|
||||||
|
final String requestUrl = SITE_URL + "whois/" + domain;
|
||||||
|
return commonQuery(requestUrl, QUERY_WHOIS_PARAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 {@link #queryWhois(String)} 的结果进行裁剪.
|
||||||
|
* <br>
|
||||||
|
* 将会删除返回内容中 {@code >>> XXX <<<} 行以后的注释串,
|
||||||
|
* 以达到只保留重要信息的目的。
|
||||||
|
*
|
||||||
|
* @see #queryWhois(String)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static IP186QueryResponse queryWhoisPretty (String domain) throws IOException {
|
||||||
|
final IP186QueryResponse raw = queryWhois(domain);
|
||||||
|
return new IP186QueryResponse(raw.url(), raw.body().substring(0, raw.body().indexOf("<<<")+3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static IP186QueryResponse commonQuery (String requestUrl, String queryIpParam) throws IOException {
|
||||||
|
Request request = new Request.Builder().url(requestUrl + "?" + queryIpParam).build();
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
final ResponseBody body = response.body();
|
||||||
|
if (body == null) throw new IOException("Null body.");
|
||||||
|
return new IP186QueryResponse(requestUrl, body.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package cc.sukazyo.cono.morny.data.ip186;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link IP186QueryHandler} 的请求结果数据的通用封装类.
|
||||||
|
*
|
||||||
|
* @since 0.4.2.10
|
||||||
|
* @param url 请求数据的<u>人类可读的</u>来源链接,<b>并非api链接</b>
|
||||||
|
* @param body API 传回的数据内容
|
||||||
|
*/
|
||||||
|
public record IP186QueryResponse(String url, String body) {
|
||||||
|
}
|
73
src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java
Normal file
73
src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnegative;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BiliTool {
|
||||||
|
|
||||||
|
private static final long V_CONV_XOR = 177451812L;
|
||||||
|
private static final long V_CONV_ADD = 8728348608L;
|
||||||
|
private static final char[] BV_TABLE = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF".toCharArray();
|
||||||
|
private static final int TABLE_INT = BV_TABLE.length;
|
||||||
|
private static final Map<Character, Integer> BV_TABLE_REVERSED = new HashMap<>();
|
||||||
|
static { for (int i = 0; i < BV_TABLE.length; i++) BV_TABLE_REVERSED.put(BV_TABLE[i], i); }
|
||||||
|
private static final char[] BV_TEMPLATE = "1 4 1 7 ".toCharArray();
|
||||||
|
private static final int[] BV_TEMPLATE_FILTER = new int[]{9, 8, 1, 6, 2, 4};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a <a href="https://www.bilibili.com/">Bilibili</a> AV video id format to BV id format.
|
||||||
|
* <p>
|
||||||
|
* the AV id is a number; the BV id is a special base58 number, it shows as String in programming.<br>
|
||||||
|
* eg:<br>
|
||||||
|
* while the link <i>{@code https://www.bilibili.com/video/BV17x411w7KC/}</i>
|
||||||
|
* shows the same with <i>{@code https://www.bilibili.com/video/av170001/}</i>,
|
||||||
|
* the AV id is <u>{@code 170001}</u>, the BV id is <u>{@code 17x411w7KC}</u>
|
||||||
|
* <p>
|
||||||
|
* for now , the BV id has 10 digits.
|
||||||
|
* the method <b>available while the <u>av-id < 2^27</u></b>, while it theoretically available when the av-id < 2^30.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.zhihu.com/question/381784377/answer/1099438784">mcfx的回复: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」?</a>
|
||||||
|
*
|
||||||
|
* @param bv the BV id, a string in (a special) base58 number format, <b>without "BV" prefix</b>.
|
||||||
|
* @return the AV id corresponding to this bv id in <a href="https://www.bilibili.com/">Bilibili</a>, formatted as a number.
|
||||||
|
*/
|
||||||
|
@Nonnegative
|
||||||
|
public static long toAv (@Nonnull String bv) {
|
||||||
|
long av = 0;
|
||||||
|
for (int i = 0; i < BV_TEMPLATE_FILTER.length; i++) {
|
||||||
|
av += BV_TABLE_REVERSED.get(bv.charAt(BV_TEMPLATE_FILTER[i])) * Math.pow(TABLE_INT,i);
|
||||||
|
}
|
||||||
|
return (av-V_CONV_ADD)^V_CONV_XOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a <a href="https://www.bilibili.com/">Bilibili</a> BV video id format to AV id format.
|
||||||
|
* <p>
|
||||||
|
* the AV id is a number; the BV id is a special base58 number, it shows as String in programming.<br>
|
||||||
|
* eg:<br>
|
||||||
|
* while the link <i>{@code https://www.bilibili.com/video/BV17x411w7KC/}</i>
|
||||||
|
* shows the same with <i>{@code https://www.bilibili.com/video/av170001/}</i>,
|
||||||
|
* the AV id is <u>{@code 170001}</u>, the BV id is <u>{@code 17x411w7KC}</u>
|
||||||
|
* <p>
|
||||||
|
* for now , the BV id has 10 digits.
|
||||||
|
* the method <b>available while the <u>av-id < 2^27</u></b>, while it theoretically available when the av-id < 2^30.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.zhihu.com/question/381784377/answer/1099438784">mcfx的回复: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」?</a>
|
||||||
|
*
|
||||||
|
* @param av the (base10) AV id.
|
||||||
|
* @return the AV id corresponding to this bv id in <a href="https://www.bilibili.com/">Bilibili</a>,
|
||||||
|
* as a (special) base 58 number format <b>without "BV" prefix</b>.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static String toBv (@Nonnegative long av) {
|
||||||
|
av = (av^V_CONV_XOR)+V_CONV_ADD;
|
||||||
|
final char[] bv = BV_TEMPLATE.clone();
|
||||||
|
for (int i = 0; i < BV_TEMPLATE_FILTER.length; i++) {
|
||||||
|
bv[BV_TEMPLATE_FILTER[i]] = BV_TABLE[(int)(Math.floor(av/(Math.pow(TABLE_INT, i)))%TABLE_INT)];
|
||||||
|
}
|
||||||
|
return String.copyValueOf(bv);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
61
src/main/java/cc/sukazyo/cono/morny/util/CommonConvert.java
Normal file
61
src/main/java/cc/sukazyo/cono/morny/util/CommonConvert.java
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnegative;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进行简单类型转换等工作的类.
|
||||||
|
*/
|
||||||
|
public class CommonConvert {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字节数组转换成 hex 字符串.
|
||||||
|
* @param b 字节数组
|
||||||
|
* @return String 格式的字节数组的 hex 值(每个字节当中没有分隔符)
|
||||||
|
* @see #byteToHex(byte)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static String byteArrayToHex(@Nonnull byte[] b){
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte value : b) {
|
||||||
|
sb.append(byteToHex(value));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一个字节转换成十六进制 hex 字符串.
|
||||||
|
* @param b 字节值
|
||||||
|
* @return String 格式的字节的 hex 值(小写)
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static String byteToHex(byte b) {
|
||||||
|
final String hex = Integer.toHexString(b & 0xff);
|
||||||
|
return hex.length()<2?"0"+hex:hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一个字符串数组按照一定规则连接.
|
||||||
|
* <p>
|
||||||
|
* 连接的方式类似于"数据1+分隔符+数据2+分隔符+...+数据n-1+分隔符+数据n"
|
||||||
|
*
|
||||||
|
* @param array 需要进行连接的字符串数组,数组中每一个元素会是一个数据
|
||||||
|
* @param connector 在每两个传入数据中插入的分隔符
|
||||||
|
* @param startIndex 从传入的数据组中的哪一个位置开始(第一个元素的位置是 {@code 0})
|
||||||
|
* @param stopIndex 从传入的数据组中的哪一个位置停止(元素位置计算方式同上)
|
||||||
|
* @return 连接好的字符串
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static String stringsConnecting (
|
||||||
|
@Nonnull String[] array, @Nonnull String connector, @Nonnegative int startIndex, @Nonnegative int stopIndex
|
||||||
|
) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = startIndex; i < stopIndex; i++) {
|
||||||
|
builder.append(array[i]);
|
||||||
|
builder.append(connector);
|
||||||
|
}
|
||||||
|
builder.append(array[stopIndex]);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
144
src/main/java/cc/sukazyo/cono/morny/util/CommonEncrypt.java
Normal file
144
src/main/java/cc/sukazyo/cono/morny/util/CommonEncrypt.java
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于数据加密或编解码的工具类.
|
||||||
|
* <p>
|
||||||
|
* 出于 java std 中 Base64 的 {@link Base64.Encoder encode}/{@link Base64.Decoder decode} 十分好用,在此不再进行包装。
|
||||||
|
*/
|
||||||
|
public class CommonEncrypt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在使用加密算法处理字符串时默认会使用的字符串编码.
|
||||||
|
* <p>
|
||||||
|
* Morny 使用 UTF-8 编码因为这是一般而言加解密工具的默认行为
|
||||||
|
*/
|
||||||
|
public static final Charset ENCRYPT_STANDARD_CHARSET = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static byte[] hashAsJavaMessageDigest(String algorithm, @Nonnull byte[] data) {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance(algorithm).digest(data);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据的 md5 散列值.
|
||||||
|
*
|
||||||
|
* @param data byte 数组形式的数据体
|
||||||
|
* @return 二进制(byte数组)格式的数据的 md5 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashMd5 (@Nonnull byte[] data) {
|
||||||
|
return hashAsJavaMessageDigest("md5", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得一个字符串的 md5 散列值.
|
||||||
|
* <p>
|
||||||
|
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
|
||||||
|
*
|
||||||
|
* @param originString 要进行散列的字符串
|
||||||
|
* @return 二进制(byte数组)格式的 md5 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashMd5 (String originString) {
|
||||||
|
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据的 sha1 散列值.
|
||||||
|
*
|
||||||
|
* @param data byte 数组形式的数据体
|
||||||
|
* @return 二进制(byte数组)格式的数据的 sha1 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashSha1 (@Nonnull byte[] data) {
|
||||||
|
return hashAsJavaMessageDigest("sha1", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得一个字符串的 sha1 散列值.
|
||||||
|
* <p>
|
||||||
|
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
|
||||||
|
*
|
||||||
|
* @param originString 要进行散列的字符串
|
||||||
|
* @return 二进制(byte数组)格式的 sha1 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashSha1 (String originString) {
|
||||||
|
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据的 sha256 散列值.
|
||||||
|
*
|
||||||
|
* @param data byte 数组形式的数据体
|
||||||
|
* @return 二进制(byte数组)格式的数据的 sha256 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashSha256 (@Nonnull byte[] data) {
|
||||||
|
return hashAsJavaMessageDigest("sha256", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得一个字符串的 sha256 散列值.
|
||||||
|
* <p>
|
||||||
|
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
|
||||||
|
*
|
||||||
|
* @param originString 要进行散列的字符串
|
||||||
|
* @return 二进制(byte数组)格式的 sha256 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashSha256 (String originString) {
|
||||||
|
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据的 sha512 散列值.
|
||||||
|
*
|
||||||
|
* @param data byte 数组形式的数据体
|
||||||
|
* @return 二进制(byte数组)格式的数据的 sha512 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashSha512 (@Nonnull byte[] data) {
|
||||||
|
return hashAsJavaMessageDigest("md5", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得一个字符串的 sha512 散列值.
|
||||||
|
* <p>
|
||||||
|
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
|
||||||
|
*
|
||||||
|
* @param originString 要进行散列的字符串
|
||||||
|
* @return 二进制(byte数组)格式的 sha512 散列值
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static byte[] hashSha512 (String originString) {
|
||||||
|
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static String base64FilenameLint (String inputName) {
|
||||||
|
if (inputName.endsWith(".b64")) {
|
||||||
|
return inputName.substring(0, inputName.length()-".b64".length());
|
||||||
|
} else if (inputName.endsWith(".b64.txt")) {
|
||||||
|
return inputName.substring(0, inputName.length()-".b64.txt".length());
|
||||||
|
} else if (inputName.endsWith(".base64")) {
|
||||||
|
return inputName.substring(0, inputName.length()-".base64".length());
|
||||||
|
} else if (inputName.endsWith(".base64.txt")) {
|
||||||
|
return inputName.substring(0, inputName.length()-".base64.txt".length());
|
||||||
|
} else {
|
||||||
|
return inputName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
src/main/java/cc/sukazyo/cono/morny/util/CommonFormat.java
Normal file
30
src/main/java/cc/sukazyo/cono/morny/util/CommonFormat.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
public class CommonFormat {
|
||||||
|
|
||||||
|
public static final String DATE_TIME_PATTERN_FULL_MILLIS = "yyyy-MM-dd HH:mm:ss:SSS";
|
||||||
|
|
||||||
|
public static String formatDate (long timestamp, int utcOffset) {
|
||||||
|
return DateTimeFormatter.ofPattern(DATE_TIME_PATTERN_FULL_MILLIS).format(LocalDateTime.ofInstant(
|
||||||
|
Instant.ofEpochMilli(timestamp),
|
||||||
|
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(utcOffset))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDuration (long duration) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (duration > 1000 * 60 * 60 * 24) sb.append(duration / (1000*60*60*24)).append("d ");
|
||||||
|
if (duration > 1000 * 60 * 60) sb.append(duration / (1000*60*60) % 24).append("h ");
|
||||||
|
if (duration > 1000 * 60) sb.append(duration / (1000*60) % 60).append("min ");
|
||||||
|
if (duration > 1000) sb.append(duration / 1000 % 60).append("s ");
|
||||||
|
sb.append(duration % 1000).append("ms");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/main/java/cc/sukazyo/cono/morny/util/FileUtils.java
Normal file
28
src/main/java/cc/sukazyo/cono/morny/util/FileUtils.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static String getMD5Three (@Nonnull String path) throws IOException, NoSuchAlgorithmException {
|
||||||
|
final BigInteger bi;
|
||||||
|
final byte[] buffer = new byte[8192];
|
||||||
|
int len;
|
||||||
|
final MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
final FileInputStream fis = new FileInputStream(path);
|
||||||
|
while ((len = fis.read(buffer)) != -1) {
|
||||||
|
md.update(buffer, 0, len);
|
||||||
|
}
|
||||||
|
fis.close();
|
||||||
|
final byte[] b = md.digest();
|
||||||
|
bi = new BigInteger(1, b);
|
||||||
|
return bi.toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class UniversalCommand {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static String[] format (@Nonnull String com) {
|
||||||
|
|
||||||
|
final ArrayList<String> arr = new ArrayList<>();
|
||||||
|
|
||||||
|
final StringBuilder tmp = new StringBuilder();
|
||||||
|
final char[] coma = com.toCharArray();
|
||||||
|
for (int i = 0; i < coma.length; i++) {
|
||||||
|
if (coma[i] == ' ') {
|
||||||
|
if (!tmp.toString().equals("")) { arr.add(tmp.toString()); }
|
||||||
|
tmp.setLength(0);
|
||||||
|
} else if (coma[i] == '"') {
|
||||||
|
while (true) {
|
||||||
|
i++;
|
||||||
|
if (coma[i] == '"') {
|
||||||
|
break;
|
||||||
|
} else if (coma[i] == '\\' && (coma[i+1] == '"' || coma[i+1] == '\\')) {
|
||||||
|
i++;
|
||||||
|
tmp.append(coma[i]);
|
||||||
|
} else {
|
||||||
|
tmp.append(coma[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (coma[i] == '\\' && (coma[i+1] == ' ' || coma[i+1] == '"' || coma[i+1] == '\\')) {
|
||||||
|
i++;
|
||||||
|
tmp.append(coma[i]);
|
||||||
|
} else {
|
||||||
|
tmp.append(coma[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!tmp.toString().equals("")) { arr.add(tmp.toString()); }
|
||||||
|
tmp.setLength(0);
|
||||||
|
|
||||||
|
final String[] out = new String[arr.size()];
|
||||||
|
arr.toArray(out);
|
||||||
|
return out;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException;
|
||||||
|
import com.pengrad.telegrambot.TelegramBot;
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
import com.pengrad.telegrambot.model.ChatMember;
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.GetChatMember;
|
||||||
|
import com.pengrad.telegrambot.response.BaseResponse;
|
||||||
|
|
||||||
|
public class ExtraAction {
|
||||||
|
|
||||||
|
private final TelegramBot bot;
|
||||||
|
|
||||||
|
public ExtraAction (TelegramBot bot) {
|
||||||
|
this.bot = bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExtraAction as (TelegramBot bot) {
|
||||||
|
return new ExtraAction(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserInGroup (User user, Chat chat) {
|
||||||
|
return isUserInGroup(user.id(), chat.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends BaseRequest<T, R>, R extends BaseResponse> R exec (T req) {
|
||||||
|
return exec(req, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends BaseRequest<T, R>, R extends BaseResponse> R exec (T req, String errorMessage) {
|
||||||
|
final R resp = bot.execute(req);
|
||||||
|
if (!resp.isOk()) throw new EventRuntimeException.ActionFailed(
|
||||||
|
(errorMessage.equals("") ? String.valueOf(resp.errorCode()) : errorMessage),
|
||||||
|
resp
|
||||||
|
);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserInGroup (User user, Chat chat, ChatMember.Status permissionLevel) {
|
||||||
|
return isUserInGroup(user.id(), chat.id(), permissionLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserInGroup (long userId, long chatId) {
|
||||||
|
return isUserInGroup(userId, chatId, ChatMember.Status.restricted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserInGroup (long userId, long chatId, ChatMember.Status permissionLevel) {
|
||||||
|
final ChatMember chatMember = exec(new GetChatMember(chatId, userId)).chatMember();
|
||||||
|
return
|
||||||
|
chatMember != null &&
|
||||||
|
UserPermissionLevel.as(chatMember.status()).hasPermission(UserPermissionLevel.as(permissionLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserPermissionLevel {
|
||||||
|
|
||||||
|
CREATOR(3),
|
||||||
|
ADMINISTRATOR(2),
|
||||||
|
MEMBER(1),
|
||||||
|
RESTRICTED(0),
|
||||||
|
LEFT(-1),
|
||||||
|
KICKED(-2);
|
||||||
|
|
||||||
|
final int permissionLevel;
|
||||||
|
|
||||||
|
UserPermissionLevel (int permissionLevel) {
|
||||||
|
this.permissionLevel = permissionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static UserPermissionLevel as (ChatMember.Status status) {
|
||||||
|
return switch (status) {
|
||||||
|
case creator -> CREATOR;
|
||||||
|
case administrator -> ADMINISTRATOR;
|
||||||
|
case member -> MEMBER;
|
||||||
|
case restricted -> RESTRICTED;
|
||||||
|
case left -> LEFT;
|
||||||
|
case kicked -> KICKED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasPermission (UserPermissionLevel required) {
|
||||||
|
return this.permissionLevel >= required.permissionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.UniversalCommand;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class InputCommand {
|
||||||
|
|
||||||
|
|
||||||
|
private final String target;
|
||||||
|
private final String command;
|
||||||
|
private final String[] args;
|
||||||
|
|
||||||
|
private InputCommand (@Nullable String target, @Nonnull String command, @Nonnull String[] args) {
|
||||||
|
this.target = target;
|
||||||
|
this.command = command;
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputCommand (@Nonnull String[] inputArray) {
|
||||||
|
this(parseInputArray(inputArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputCommand (@Nonnull String input) {
|
||||||
|
this(UniversalCommand.format(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputCommand (@Nonnull InputCommand source) {
|
||||||
|
this(source.target, source.command, source.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InputCommand parseInputArray (@Nonnull String[] inputArray) {
|
||||||
|
final String[] cx = inputArray[0].split("@", 2);
|
||||||
|
final String[] args = new String[inputArray.length-1];
|
||||||
|
System.arraycopy(inputArray, 1, args, 0, inputArray.length - 1);
|
||||||
|
return new InputCommand(cx.length == 1 ? null : cx[1], cx[0], args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getTarget () {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public String getCommand () {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public String[] getArgs () {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasArgs () {
|
||||||
|
return args.length != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public String toString() {
|
||||||
|
return String.format("{{%s}@{%s}#{%s}}", command, target, Arrays.toString(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.event;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.response.BaseResponse;
|
||||||
|
|
||||||
|
public class EventRuntimeException extends RuntimeException {
|
||||||
|
|
||||||
|
public EventRuntimeException () {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventRuntimeException (String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ActionFailed extends EventRuntimeException {
|
||||||
|
|
||||||
|
private final BaseResponse response;
|
||||||
|
|
||||||
|
public ActionFailed (BaseResponse response) {
|
||||||
|
super();
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionFailed (String message, BaseResponse response) {
|
||||||
|
super(message);
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseResponse getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class MsgEscape {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static String escapeHtml (@Nonnull String raw) {
|
||||||
|
raw = raw.replaceAll("&", "&");
|
||||||
|
raw = raw.replaceAll("<", "<");
|
||||||
|
raw = raw.replaceAll(">", ">");
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonConvert;
|
||||||
|
import cc.sukazyo.cono.morny.util.CommonEncrypt;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class NamedUtils {
|
||||||
|
|
||||||
|
public static String inlineIds (@Nonnull String tag) {
|
||||||
|
return inlineIds(tag, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String inlineIds (@Nonnull String tag, @Nonnull String taggedData) {
|
||||||
|
return CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(tag+taggedData));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
|
||||||
|
public class TGToString {
|
||||||
|
|
||||||
|
public static TGToStringFromChat as (Chat chat) {
|
||||||
|
return new TGToStringFromChat(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TGToStringFromUser as (User user) {
|
||||||
|
return new TGToStringFromUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TGToStringFromMessage as (Message message) {
|
||||||
|
return new TGToStringFromMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Chat;
|
||||||
|
|
||||||
|
public class TGToStringFromChat {
|
||||||
|
|
||||||
|
private final Chat data;
|
||||||
|
|
||||||
|
public TGToStringFromChat(Chat chat) {
|
||||||
|
this.data = chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toStringFullNameId() {
|
||||||
|
if (data.title() == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot format private chat to group Name+Id format.");
|
||||||
|
}
|
||||||
|
return (data.username() == null) ?
|
||||||
|
(String.format("%s [%d]", data.title(), data.id())) :
|
||||||
|
(String.format("%s {%s}[%d]", data.title(), data.username(), data.id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.Message;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class TGToStringFromMessage extends TGToString {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final Message message;
|
||||||
|
|
||||||
|
public TGToStringFromMessage (@Nonnull Message message) { this.message = message; }
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public String getSenderFirstNameRefHtml () {
|
||||||
|
return message.senderChat()==null ? TGToString.as(message.from()).firstnameRefHtml() : String.format(
|
||||||
|
"<a href='tg://user?id=%d'>%s</a>",
|
||||||
|
message.senderChat().id(),
|
||||||
|
MsgEscape.escapeHtml(message.senderChat().title())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSenderId () {
|
||||||
|
return message.senderChat()==null ? message.from().id() : message.senderChat().id();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
|
||||||
|
public class TGToStringFromUser {
|
||||||
|
|
||||||
|
private final User data;
|
||||||
|
|
||||||
|
public TGToStringFromUser (User user) {
|
||||||
|
this.data = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fullname () {
|
||||||
|
return data.firstName() + (data.lastName()==null ? "" : " "+data.lastName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fullnameRefHtml () {
|
||||||
|
return String.format(
|
||||||
|
"<a href='tg://user?id=%d'>%s</a>",
|
||||||
|
data.id(),
|
||||||
|
MsgEscape.escapeHtml(fullname())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fullnameRefMarkdown () {
|
||||||
|
return String.format(
|
||||||
|
"[%s](tg://user?id=%d)",
|
||||||
|
fullname(),
|
||||||
|
data.id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String firstnameRefHtml () {
|
||||||
|
return String.format(
|
||||||
|
"<a href='tg://user?id=%d'>%s</a>",
|
||||||
|
data.id(),
|
||||||
|
MsgEscape.escapeHtml(data.firstName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String firstnameRefMarkdown () {
|
||||||
|
return String.format(
|
||||||
|
"[%s](tg://user?id=%d)",
|
||||||
|
data.firstName(),
|
||||||
|
data.id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toStringLogTag () {
|
||||||
|
return (data.username()==null ? fullname()+" " : "@"+data.username()) + "[" + data.id() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package cc.sukazyo.cono.morny.util.tgapi.formatting;
|
||||||
|
|
||||||
|
import com.pengrad.telegrambot.model.User;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
|
||||||
|
|
||||||
|
public class TelegramUserInformation {
|
||||||
|
|
||||||
|
public static final String DC_QUERY_SOURCE_SITE = "https://t.me/";
|
||||||
|
public static final Pattern DC_QUERY_PROCESSOR_REGEX = Pattern.compile("(cdn[1-9]).tele(sco.pe|gram-cdn.org)");
|
||||||
|
|
||||||
|
private static final OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getDataCenterFromUsername (String username) {
|
||||||
|
final Request request = new Request.Builder().url(DC_QUERY_SOURCE_SITE + username).build();
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
final ResponseBody body = response.body();
|
||||||
|
if (body == null) return "empty upstream response";
|
||||||
|
final Matcher matcher = DC_QUERY_PROCESSOR_REGEX.matcher(body.string());
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String informationOutputHTML (User user) {
|
||||||
|
|
||||||
|
final StringBuilder userInformation = new StringBuilder();
|
||||||
|
userInformation.append(String.format(
|
||||||
|
"""
|
||||||
|
userid :
|
||||||
|
- <code>%d</code>""",
|
||||||
|
user.id()
|
||||||
|
));
|
||||||
|
if (user.username() == null) {
|
||||||
|
userInformation.append("\nusername : <u>null</u>\ndatacenter : <u>null</u>");
|
||||||
|
} else {
|
||||||
|
userInformation.append(String.format(
|
||||||
|
"""
|
||||||
|
|
||||||
|
username :
|
||||||
|
- <code>%s</code>""",
|
||||||
|
escapeHtml(user.username())
|
||||||
|
));
|
||||||
|
// 依赖 username 的 datacenter 查询
|
||||||
|
final String dataCenter = getDataCenterFromUsername(user.username());
|
||||||
|
if (dataCenter == null) { userInformation.append("\ndatacenter : <u>null</u>"); }
|
||||||
|
else { userInformation.append(String.format("\ndatacenter : <code>%s</code>", escapeHtml(dataCenter))); }
|
||||||
|
}
|
||||||
|
userInformation.append(String.format(
|
||||||
|
"""
|
||||||
|
|
||||||
|
display name :
|
||||||
|
- <code>%s</code>%s""",
|
||||||
|
escapeHtml(user.firstName()),
|
||||||
|
user.lastName()==null ? "" : String.format("\n- <code>%s</code>", escapeHtml(user.lastName()))
|
||||||
|
));
|
||||||
|
if (user.languageCode() != null) {
|
||||||
|
userInformation.append(String.format(
|
||||||
|
"""
|
||||||
|
|
||||||
|
language-code :
|
||||||
|
- <code>%s</code>""",
|
||||||
|
escapeHtml(user.languageCode())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInformation.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 286 KiB |
@ -1,55 +0,0 @@
|
|||||||
ttt///t/////fucj(\tvnxtf{< .' .. .:i` . . ^!`l|-^i+,!_[:1/|{i?//\//jf\\\///\\\\//\\\//////\\/\\\\\\\\\\\\\\//\\\\/\\\\/\\//\\\///\\\\\\\\\\\\\\\\\\\\fnncvvU0O00QCx!!". .. `
|
|
||||||
tt//////////\jzjrucnjt/?{j,,"' . .' .. .":. .;{: ' "`.,1(<."i?)\(-}\\\(((\\/\\\\\\\\\\\\\\\\///\//////\\\\\\\\\\\\\\\\\\\\\\\|\\\\\\///\\///\\\\\\\\|\\\\\\|\\\\\\\\tvXvuXcxn/[<!l~<` `I`.
|
|
||||||
tt//////t////\//|rvx//\(((-;,''" ",.,II..' `. . ^"' . .` .. .. .:: ```!],";";;^ "!?)/_ :li~)1[;<li<(\1(1;+||\||\\\\\\\\\||\\\||\\\\\///\\\\\\\\\\\\\\\\\\\\\\\\\\\||\\\\\\\\//\\\\\\\\\\\\\\\\\/\\||\\\\\\\\\\\\||||\\//t{}[!>Il)({_:.. ."` .,
|
|
||||||
//////////////////\////|)/([}-_<+[]>.^^""[<'`^` .''""`'.`'`"i! ^!>l:' :<" !!.IiI`+l^^`i>_<`??)1;^{\\\\\{|({({|/\]I)\\()\(]}|\\||\|||\/\\\\\\|||\\\\\\\\//\\\\/\\\\||\\\\\\\\//\\\\\\\\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\//\\\\\\\\\\\\////\\\\\\\\\//|{{?{|)[[-;
|
|
||||||
ttt/tt//////////////////{)(\t(/tt/1~I}{-1\_^])1_+[{|(?"<1~>>+!+[}11)}[(1}]};^1\|~_1}{I:-1(I+)(|))|\\/////////\\////\\\/////\\\\\\\\\\\\\\/\//\\///\//||\////|)(//\\///){\/\(11|///({)//({[1\\\\\\\\\\|\/\\\/\//////////\\\\\\\\\//\\\\\///////\|\\\\//////\\///\
|
|
||||||
tttt/////////////\///////\||///////t//|(|)|}|\/(\\(//(l_{{<i!l}}})(()\/\///{{|||\\|\\((\\\\\\\\\\\////\\\\\//\\/\////\///\\\\\\\\\//\\\\/\|}[{|1?{|[i:,,;i<i,,,I~}}<<l^":I,`.':-<l!~l~i:!;,l)\\/\//\/\\\/////\\\//\\////\\\\\\\\//\\\/\\///\\/\\\\\\\\\\\\\\\\//
|
|
||||||
//t//////////////\///////////////////////\//////////////////\/\/////\\\\\\\//\\|||\\/\\\\\\\\\\\\\/\\\\\\\\\\//\\/////////\\\\|))([+)}!<~"^,^.`' .^."~, :` '" `_>. ... ">+<^'I!: ^<(\\1}1//\\\//////////\\///\/\///\\\\\\//\\//\\\\\\\\\\\\\\///\(/\{
|
|
||||||
t////////////////////////////////////////////////////////\/\\///////\\\\\\\\\\\\\\\/\\\\\\||\|\\\\\|\\\\/\\|\\\\\\////((|///}!:,":,^`. .;' ' '^..':. ^!;. .^^ '^^`. '' ...I[{!>:^;_i:'~\ttt/////tt//\\////////////\\\\/\\\\\\/\\\\/\\\\\)}-+[+I??i
|
|
||||||
ttt////////////////////////////////////////////////////////\\//\//\\/\\\\\\\\\\//\\\\\\||\\\/\//\\\\\\\\\/\|\\\////\1;``^;<>+!">__+I `' .. "'. .;" ;;. .:^ ``,,;'` .;]I ,-_-|\////t////t///////\/\\\\//\\\\\\//\\\///////-II<!l_; I
|
|
||||||
/////////////////////////////////////////////////\\/\\\\\////\\//\\/////\///////////tt//\\/\////\/////t/\//\\///\]{\l,`'+< ,i ^i" . .`"l" . .`;?-' .` `>1ttt///tttt/////////\/\/////\\\\////t|+<}?!-]l<{[[1-+]
|
|
||||||
t//////////////////////////////////////\/////////\////////////////\//////\//tttttttttt//////////////////////////)_)t)|}1f/{<.^,^:~: . .. '''^:-|/> '-/}-_?\/)-{?(//\(\tt////\///\\\\\\\///t1.;); .l~` '"
|
|
||||||
///////////////////////////////////////////tt/t(|tt//]+{t\{][|////\//////////ttttt///t//t/////////////\//////|//{[|f}!l<!!I, `' ,!!i+- .`:l;IIll>>~++~<<<!:`. '' '^-l `.,l:.`{{[_:]/1l;>\//]l~?])tt//\\\\\/\\///\\|?<_}["^!;I^;]:. .
|
|
||||||
////////////////////////////////tttt/|{[1)]~!!+>!<_(/|[-<"i!l,]tt//ttt/t////ttt//t///ttttttt////////t//t//ttt){+. :?^ '. l_-!+l;;;|!!>~~il!lllllllllll!!lI:`'. .' :I;]_}>,?tf:.+fft)l+1//\~`'I-(//\/t/|/(-1[)/?>>II:' '.`';-'`
|
|
||||||
/////////////////////////t//()\1_<>il^'''' ,!>;.,.'{tti `~tf(`'-(|fffftttttttttt/tttttttttttt///tttft//(t|]?-+!^ ."`. `. ;!I,. .?{il-\_!~<>>!lII;IllIIIIIllllllllllI;;:,:,' '"^`(f{+{>' .<{t(I!}/||t> ^(//}>;:1\]: "[:"` ^<: . II.'..
|
|
||||||
///////////////ttt//tt((-!+}"'^. I, ,?<:' ,:;!>~',!_~{}-1]`^!}_+\ttttttt/tttttttfff/tt\(||]-?+;,:"l" '..'.. ?]l:" -(lI;,~?~!IIIlllI:IIlI;IIIIlllllllIIIIIllII!; . . '^^;~), "~!}\/t//\\/_. '</||1?-)/\\)+_1>". '_i !i'''
|
|
||||||
tt//t///ttt///(]<>l>][l"'.`,. ^.^. ii ;; ~>>>. .i~I'^^<}), .;|tfftttttttttttf\]}t-!,,I` .^ '. !: . .",I;. ^,I<)/-l:;llllllI;lIll;;IIIIlllIllIlIIlIllI;><. ' .;}". '.:+](ft\}(t/t{;<\{l^>}!^l\/{>1/t(lI:I!+<<". ':"
|
|
||||||
t//tttt|?+!I!:' '` .`. ...... `^ "<^.;`^"'`,!".,^^^.,?)!. [f/+>(/tttft\tff|+^,!' '^: >[,++:`' .I^ . _?!:^. ;~{/<II:IllIlllI;l;IlI;;IIIIIIlIIIIIIllIlllI+- "+;,...<\;^_(/~}t/(+ ^(/?.:|)il)\>?//)! __::. ':. '.
|
|
||||||
t/\}[{]",il'`!-<-]:`'^` .. '' .^+:'. .^'"i:`^. ';`:<_|>'.?/t/!"<)ffftf)]]!'II.,l ^' ''. '";" .' `Il, ;]>]j_;lI;ll;!!llIII>~IIIIII;:<iIlIIlII;llllll;(> ;,~.',.<:`, 'I_|\; .i|/]^ ?(}\/////\i' '' ....'^
|
|
||||||
tf1<}i `^. `I` .I?"'. . . ^' .^' .'` .". >}_.I|t{_(tf({~,~(); ')t};.><,. .. . .. . . .]}^{j1IlllIlI!1IlllII?{IIIIIlI;[1!I;IIllIIlIIllI<x" '.!+~!,``...:. 'l]?l"i{/\><]_;+/t\(|\/1,' "` ...
|
|
||||||
)+::((:^' ll .,` . . ..'. ' :+'`{tj{,l: ^;"..;!"^.I?' '~; .` .'. . `1+ [x?-:lIlll!]r-IllI~~{~I>lllll[i\--+;;I~IIIII!l;x] "I"-<<_> >i.' l{}:itf/}[/\)(\}))|(:^^..'. `".
|
|
||||||
>.._f|i.:l,;^^''__. .^' `' "+<!~)?,,[>,`]1i`!1_. ^l: .". .` '1I +JIt!IIlll;]\<vlIlI(;I\i+<iII>) >1}c(_i(!IllI_l;(f. ,_";~~+^ .. .;-i '+([i+: !//1](||/\(?:^..^^
|
|
||||||
i'"}_,.` ''^... '. `. ^<`_> .. +x??_~]:[|!,.ll` . {+ ;Y[^|,>~IlIIf\ {/;I!\ [[-'<+l-{ _??]f\n]lllI[!;1v` `+"]-}]~" ..'`l, ''i-` l+?\\\/t{!)t[:' .^^
|
|
||||||
;,:: :,^..;:. . i+..;^ `_<!,!~II,`. '' . "t..\n^!]!i]<II!n> ]]<-?l``-]' I>]?+<l~<!._n_IllI1+i}J: ...l!,'`' `. . .~>. ^-|\\_I?]{t/?` ....
|
|
||||||
^(\]I^~?;."!" . .. . ^<^ :( >t) _[il>|+:(U<1nYQ0Xx\> . .~xcXXYzx(n?IllI}">xCI .:1]_-" . .^. `}>!}((1-^,+?" ..
|
|
||||||
1+,~I.<! .`' .. `x"(+1 >(l<?_1}Cj!f\?l"' '"I~]|_;Ill{ vQ" ':,~_' .. .. .`^[(`,?\||\+ '," .
|
|
||||||
}<. `^]!.;lI>' . ... ~|r:;`.+I?\};+t) "".-?;lI>(;]xn. '_>]!+. '^'`l:11l[|((+?: .
|
|
||||||
[-`.':;..""' lv|. .:_(;I!u> ^,",^. .;?I]?IlI}n[</( "+'.. .I]; ',;!\/\|~I}" '";'.
|
|
||||||
.,><I>^ ')(I' `tlII>x1" <}1{)l "~+ |[II;\[:~zl .i;. .... `"i\\}]..!' ^....
|
|
||||||
-" ` ' . ":` .|+<<;!\U)>^ '^`' ^"I?)c-;<if]>j/ <1I~;` .!}\(: .;"`'
|
|
||||||
' 'l, .' ~[><+;!f()nn|]!:' ..^:!+1fcjx}}v!_)})>|n` ~^ ^;"'` .<+I<)/||\i'"<"'^ `.
|
|
||||||
II ^}_'+_!fI?_/-jJjUr\\ucJJz\|J>}?-j{]^ni<z; : `' .-+~->" .;](),.;-<`' .^ .^'
|
|
||||||
+[" +]{.`i;I</-jmvxjj)<^ !0~l-?+` : '" '` .' l\)-++{f\" ',^^^ .^ ,!` ''
|
|
||||||
.>; ::."!??l.^Ywj}<, (n, ,~_:` .,, ` `` ' '... ^+-l,]}]}\j/!. . ` 'I<~`'{tl..^`
|
|
||||||
'' .,<{[>" i/i" `-[; ,<_[>^i_l,:^_! ',+l.. ^,:,,. ;~>l;^ l> ',;I^???~,'l".. ..
|
|
||||||
;{?l. !+ .. .<1i '^' "}|{:-+-;?\[)-] ^:l1-:. '' '`, . ';.`~^ '. ..^`.
|
|
||||||
'i+;]}!,. <))\!<|ji >((_}}?t)}\\v|]?jI!), "lf!l. ... .^ . ".
|
|
||||||
.+{>` l/z\!,>""I+~_){]<t~-(! .!nt_]]]])1)]]\( ?_' :( . . `:. `; ." ... ..
|
|
||||||
Ir. ..>vQjut_~~>>>_-<]<-)f":l_v){\/1}}}{t/\0?z~. ^' `-l . . .. .
|
|
||||||
l\ :_>>i:^+\)_-]!:>-+<?\Uzfjj-/mxt|[??/f)??}/mc1;. . . . ^' .
|
|
||||||
I1I`l?-}l>l'...`^;1!^ 'l>})l\n\Qt?]?]})1{][[(XC>^ ...
|
|
||||||
^-+^.i-((?!"`:>l<[<!tc_:;<+>~<]nQY+?????][{\cmO||l . ''..
|
|
||||||
'|: '^[{~)\_+++))1{uxnvt(t){{[[u0\1|({1()){-?|xfc: ..
|
|
||||||
.<-, ]-]]]})11)){{{{}{{)|{}}{1{111{11{{}}]_!"x\]Xf
|
|
||||||
,]<\}][[[[[[[[[[[[[[[]][[[[[[[[][[[[[[]?-+!YC{z} .`
|
|
||||||
"_[}?]][[[[[[[[[[[[[[[[[[][[[[[[[[[[[[[[[]vn\?. ^
|
|
||||||
^. ;{_(_??][[[[[[[[[[[[[[[[[|[[[[[[[[[[[[[[[[v_(]^
|
|
||||||
.' '. :t>/?[[[[[[[[[[[[[[[[[[[]t\][[[[[[[[[[][]?u;()_ .. .'. . .
|
|
||||||
`. .` :)!j_]][[[]]]]][[[[[[[[[[}j(/{[[[[[][[}1{~n!)ft . .. .. . .. .
|
|
||||||
". ..' .` . "' .^":;~ti{\1]][]]]??]]]]][]]??[){[}[[[[?+}]<I"]({~c' .,",. .. ', .'`I' ^l.
|
|
||||||
. .. .'. ..'.... '.;,.'.":`,_: '` . '"^. '<:,'. 'l:`'. . ';"'. .i~I:;,,`.^!{[?[[[[[[[[[[[[[[]?]]]])t11])I;:. '^c^ .. '. . .' .^ .."?, '`
|
|
||||||
' '^. `. . I;. . `^ '... ...``'. ,">!^':``^``,`;I.!<>?>:??i:;-;,<_..^,Ii: 'l,i+```' ..'!; ''. ?~.'<+ .li!:,1?}[[[[[[[[[[[[[[[[[[[[)1}]t[:, "O" . .' . ^!' . ". i+' .
|
|
||||||
^. . .':`'`~/1,-<-~^'^'^^,`.."i_>^. `1t]!,^I]l^;`,I::_?]?[!:;`.`"'`l!l<1f{~>;]\1(]I>~l!l[<,,;`lI,~},^>!>l<l[-' I-+:i_!ll::l"`>'...'": 'x' ]l>i .:I1[~]]]]}}}}}[[}}[}}}}}[[]]]??1}}[}~;:>vx. :;..:??,.' ^` I;>.";:"^' .. ^'.^"" ' .!},' .
|
|
||||||
~!;:!".":i"^_/|]^li(\1;;it{' .[\fft+<}(/{}/)|f||<tj\/1//jj[<<~])11?''.`;\)l!(ff~!\j1-|\t\f\//||>'.^{/[)?!:(?+-,I+fjtil"'"+fj{i:',!!;!!^:.`r. r; !l'"i1?!i>~+_?][[[?-???]][[]?-+-]??+~<_{[_l?> ''^l;l`-}<`^.i>+l ``;I":+?!~-l ,>>l.'.;. ':!!(/!":,I
|
|
||||||
/t<I^^!{{-;[[-~|1\jj\?(/)f{~~+}}//t(?(ft}{tf]j1>1ffft+{jff/ttff)];)?1(/tt\/t/tfttttfftf/1\|t\|/?<_]_]{<_]/f({fffjttf/[i>1//|tft|" :<~:+}, ]>if" .:-~ >) ^`^l)f(_<??)\}}(t|}>{/\}-+1\()t-{j/]!:^'l<]\)+ ."_?I_{
|
|
||||||
ffft)|)(t[_-{tjjrjrj/{(||}(rjj\1)I<\((ffj/rjffttjffftrjfffrtfff/f[1jjffffftt//)}tttff/ttt[<{rj}tf1?<:~{/j)>)fttf|?)tfffftt1_;+tf1-1|~i1, >;:} '1_ ;( .. .. `:"_1{}tjtvj)vjr/|jfff/<(tf)+1/)1j)~~-[j[l|[(/\j{:-]]([}\t
|
|
@ -1,29 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import cc.sukazyo.messiva.appender.ConsoleAppender
|
|
||||||
import cc.sukazyo.messiva.formatter.SimpleFormatter
|
|
||||||
import cc.sukazyo.messiva.log.LogLevel
|
|
||||||
import cc.sukazyo.messiva.logger.Logger
|
|
||||||
|
|
||||||
import java.io.{PrintWriter, StringWriter}
|
|
||||||
|
|
||||||
object Log {
|
|
||||||
|
|
||||||
val logger: Logger = Logger(
|
|
||||||
ConsoleAppender(
|
|
||||||
SimpleFormatter()
|
|
||||||
)
|
|
||||||
).minLevel(LogLevel.INFO)
|
|
||||||
|
|
||||||
def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevel.DEBUG.level
|
|
||||||
|
|
||||||
def debug(is: Boolean): Unit =
|
|
||||||
if is then logger.minLevel(LogLevel.ALL)
|
|
||||||
else logger.minLevel(LogLevel.INFO)
|
|
||||||
|
|
||||||
def exceptionLog (e: Throwable): String =
|
|
||||||
val stackTrace = StringWriter()
|
|
||||||
e printStackTrace PrintWriter(stackTrace)
|
|
||||||
stackTrace toString
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
object MornyAbout {
|
|
||||||
|
|
||||||
val MORNY_PREVIEW_IMAGE_ASCII: String =
|
|
||||||
try { MornyAssets.pack getResource "texts/server-hello.txt" readAsString }
|
|
||||||
catch case e: IOException =>
|
|
||||||
throw RuntimeException("Cannot read MORNY_PREVIEW_IMAGE_ASCII from assets pack", e)
|
|
||||||
|
|
||||||
val MORNY_SOURCECODE_LINK = "https://github.com/Eyre-S/Coeur-Morny-Cono"
|
|
||||||
val MORNY_SOURCECODE_SELF_HOSTED_MIRROR_LINK = "https://storage.sukazyo.cc/Eyre_S/Coeur-Morny-Cono"
|
|
||||||
val MORNY_ISSUE_TRACKER_LINK = "https://github.com/Eyre-S/Coeur-Morny-Cono/issues"
|
|
||||||
val MORNY_USER_GUIDE_LINK = "https://book.sukazyo.cc/morny"
|
|
||||||
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import cc.sukazyo.restools.ResourcesPackage
|
|
||||||
|
|
||||||
object MornyAssets {
|
|
||||||
|
|
||||||
class AssetsException (caused: Throwable) extends Exception("Cannot read assets file.", caused)
|
|
||||||
|
|
||||||
val pack: ResourcesPackage = ResourcesPackage(MornyAssets.getClass, "assets_morny")
|
|
||||||
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.bot.command.MornyCommands
|
|
||||||
import cc.sukazyo.cono.morny.daemon.MornyDaemons
|
|
||||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur.THREAD_SERVER_EXIT
|
|
||||||
import cc.sukazyo.cono.morny.bot.api.EventListenerManager
|
|
||||||
import cc.sukazyo.cono.morny.bot.event.{MornyEventListeners, MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
|
|
||||||
import cc.sukazyo.cono.morny.bot.query.MornyQueries
|
|
||||||
import com.pengrad.telegrambot.TelegramBot
|
|
||||||
import com.pengrad.telegrambot.request.GetMe
|
|
||||||
|
|
||||||
import scala.util.boundary
|
|
||||||
import scala.util.boundary.break
|
|
||||||
|
|
||||||
object MornyCoeur {
|
|
||||||
|
|
||||||
val THREAD_SERVER_EXIT = "system-exit"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class MornyCoeur (using val config: MornyConfig) {
|
|
||||||
|
|
||||||
given MornyCoeur = this
|
|
||||||
|
|
||||||
///>>> BLOCK START instance configure & startup stage 1
|
|
||||||
|
|
||||||
logger info "Coeur starting..."
|
|
||||||
|
|
||||||
logger info s"args key:\n ${config.telegramBotKey}"
|
|
||||||
if config.telegramBotUsername ne null then
|
|
||||||
logger info s"login as:\n ${config.telegramBotUsername}"
|
|
||||||
|
|
||||||
private val __loginResult: LoginResult = login() match
|
|
||||||
case some: Some[LoginResult] => some.get
|
|
||||||
case None =>
|
|
||||||
logger error "Login to bot failed."
|
|
||||||
System exit -1
|
|
||||||
throw RuntimeException()
|
|
||||||
|
|
||||||
configure_exitCleanup()
|
|
||||||
|
|
||||||
///<<< BLOCK END instance configure & startup stage 1
|
|
||||||
|
|
||||||
/** [[TelegramBot]] account of this Morny */
|
|
||||||
val account: TelegramBot = __loginResult.account
|
|
||||||
/** [[account]]'s telegram username */
|
|
||||||
val username: String = __loginResult.username
|
|
||||||
/** [[account]]'s telegram user id */
|
|
||||||
val userid: Long = __loginResult.userid
|
|
||||||
|
|
||||||
/** current Morny's [[MornyTrusted]] instance */
|
|
||||||
val trusted: MornyTrusted = MornyTrusted()
|
|
||||||
|
|
||||||
val daemons: MornyDaemons = MornyDaemons()
|
|
||||||
//noinspection ScalaWeakerAccess
|
|
||||||
val eventManager: EventListenerManager = EventListenerManager()
|
|
||||||
eventManager register MornyOnUpdateTimestampOffsetLock()
|
|
||||||
val commands: MornyCommands = MornyCommands()
|
|
||||||
//noinspection ScalaWeakerAccess
|
|
||||||
val queries: MornyQueries = MornyQueries()
|
|
||||||
eventManager register MornyOnTelegramCommand(using commands)
|
|
||||||
eventManager register MornyOnInlineQuery(using queries)
|
|
||||||
//noinspection ScalaUnusedSymbol
|
|
||||||
val events: MornyEventListeners = MornyEventListeners(using eventManager)
|
|
||||||
|
|
||||||
/** inner value: about why morny exit, used in [[daemon.MornyReport]]. */
|
|
||||||
private var whileExit_reason: Option[AnyRef] = None
|
|
||||||
def exitReason: Option[AnyRef] = whileExit_reason
|
|
||||||
val coeurStartTimestamp: Long = ServerMain.systemStartupTime
|
|
||||||
|
|
||||||
///>>> BLOCK START instance configure & startup stage 2
|
|
||||||
|
|
||||||
daemons.start()
|
|
||||||
logger info "start telegram event listening"
|
|
||||||
account setUpdatesListener eventManager
|
|
||||||
if config.commandLoginRefresh then
|
|
||||||
logger info "resetting telegram command list"
|
|
||||||
commands.automaticTGListUpdate()
|
|
||||||
|
|
||||||
daemons.reporter.reportCoeurMornyLogin()
|
|
||||||
logger info "Coeur start complete."
|
|
||||||
|
|
||||||
///<<< BLOCK END instance configure & startup stage 2
|
|
||||||
|
|
||||||
def saveDataAll(): Unit = {
|
|
||||||
// nothing to do
|
|
||||||
logger info "done all save action."
|
|
||||||
}
|
|
||||||
|
|
||||||
private def exitCleanup (): Unit = {
|
|
||||||
daemons.reporter.reportCoeurExit()
|
|
||||||
account.shutdown()
|
|
||||||
logger info "stopped bot account"
|
|
||||||
daemons.stop()
|
|
||||||
if config.commandLogoutClear then
|
|
||||||
commands.automaticTGListRemove()
|
|
||||||
logger info "done exit cleanup"
|
|
||||||
}
|
|
||||||
|
|
||||||
private def configure_exitCleanup (): Unit = {
|
|
||||||
Runtime.getRuntime.addShutdownHook(new Thread(() => exitCleanup(), THREAD_SERVER_EXIT))
|
|
||||||
}
|
|
||||||
|
|
||||||
def exit (status: Int, reason: AnyRef): Unit =
|
|
||||||
whileExit_reason = Some(reason)
|
|
||||||
System exit status
|
|
||||||
|
|
||||||
private case class LoginResult(account: TelegramBot, username: String, userid: Long)
|
|
||||||
|
|
||||||
private def login (): Option[LoginResult] = {
|
|
||||||
|
|
||||||
val builder = TelegramBot.Builder(config.telegramBotKey)
|
|
||||||
var api_bot = config.telegramBotApiServer
|
|
||||||
var api_file = config.telegramBotApiServer4File
|
|
||||||
if (api_bot ne null)
|
|
||||||
if api_bot endsWith "/" then api_bot = api_bot dropRight 1
|
|
||||||
if !(api_bot endsWith "/bot") then api_bot += "/bot"
|
|
||||||
builder.apiUrl(api_bot)
|
|
||||||
if (api_file ne null)
|
|
||||||
if api_file endsWith "/file/" then api_file = api_file dropRight 1
|
|
||||||
if !(api_file endsWith "/file/bot") then api_file += "/file/bot"
|
|
||||||
builder.apiUrl(api_bot)
|
|
||||||
if ((api_bot ne null) || (api_file ne null))
|
|
||||||
logger info
|
|
||||||
s"""Telegram bot api set to:
|
|
||||||
|- bot: $api_bot
|
|
||||||
|- file: $api_file"""
|
|
||||||
.stripMargin
|
|
||||||
|
|
||||||
val account = builder build
|
|
||||||
|
|
||||||
logger info "Trying to login..."
|
|
||||||
boundary[Option[LoginResult]] {
|
|
||||||
for (i <- 0 to 3) {
|
|
||||||
if i > 0 then logger info "retrying..."
|
|
||||||
try {
|
|
||||||
val remote = (account execute GetMe()).user
|
|
||||||
if ((config.telegramBotUsername ne null) && config.telegramBotUsername != remote.username)
|
|
||||||
throw RuntimeException(s"Required the bot @${config.telegramBotUsername} but @${remote.username} logged in")
|
|
||||||
logger info s"Succeed logged in to @${remote.username}"
|
|
||||||
break(Some(LoginResult(account, remote.username, remote.id)))
|
|
||||||
} catch
|
|
||||||
case r: boundary.Break[Option[LoginResult]] => throw r
|
|
||||||
case e =>
|
|
||||||
logger error
|
|
||||||
s"""${exceptionLog(e)}
|
|
||||||
|login failed"""
|
|
||||||
.stripMargin
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class MornyConfig {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表示一个字段的值属于敏感数据,不应该被执行打印等操作。
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
|
||||||
public @interface Sensitive {}
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* Config props Names Definition *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
public static final String PROP_TOKEN_KEY_DEFAULT = "TELEGRAM_BOT_API_TOKEN";
|
|
||||||
public static final String PROP_TOKEN_MORNY_KEY = "MORNY_TG_TOKEN";
|
|
||||||
public static final String[] PROP_TOKEN_KEY = {PROP_TOKEN_KEY_DEFAULT, PROP_TOKEN_MORNY_KEY};
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* telegram bot login config *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Morny Telegram 使用的 API 服务器.
|
|
||||||
* <p>
|
|
||||||
* 不设定的话,默认将会使用 {@code https://api.telegram.org/bot}
|
|
||||||
*/
|
|
||||||
@Nullable public final String telegramBotApiServer;
|
|
||||||
/**
|
|
||||||
* Morny Telegram 使用的 API 服务器的 file 服务路径.
|
|
||||||
* <p>
|
|
||||||
* 不设定的话,默认将会使用 {@value com.pengrad.telegrambot.impl.FileApi#FILE_API}
|
|
||||||
*/
|
|
||||||
@Nullable public final String telegramBotApiServer4File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* morny 使用的 telegram bot 的 bot api token.
|
|
||||||
* <p>
|
|
||||||
* 这个值必须设定。
|
|
||||||
*/
|
|
||||||
@Nonnull @Sensitive public final String telegramBotKey;
|
|
||||||
/**
|
|
||||||
* morny 所使用的 bot 的 username.
|
|
||||||
* <p>
|
|
||||||
* 如果设定了这个值,则在 morny 登录 bot 时将会检查所登录的 bot 的 username 是否和这里设定的 username 匹配。
|
|
||||||
* 如果不匹配,则会拒绝登录然后报错。
|
|
||||||
* <p>
|
|
||||||
* 如果没有设定这个值,则不会对登录 bot 的 username 进行限制。
|
|
||||||
*/
|
|
||||||
@Nullable public final String telegramBotUsername;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* morny trusted config *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* morny 的主人.
|
|
||||||
* <p>
|
|
||||||
* 这项值的对象总是会被{@link MornyTrusted 信任管理器}认为是可信任的
|
|
||||||
*/
|
|
||||||
public final long trustedMaster;
|
|
||||||
/**
|
|
||||||
* morny 可信群聊的 id.
|
|
||||||
* <p>
|
|
||||||
* {@link MornyTrusted 信任管理器}将会认为这个群聊中的所有拥有
|
|
||||||
* {@link com.pengrad.telegrambot.model.ChatMember.Status#administrator administrator} 权限的成员是可信任的。
|
|
||||||
* <p>
|
|
||||||
* id 需要符合 bot api 标准。
|
|
||||||
*/
|
|
||||||
public final long trustedChat;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* system: event ignore *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
public final boolean eventIgnoreOutdated;
|
|
||||||
/**
|
|
||||||
* morny 的事件忽略前缀时间<br>
|
|
||||||
* <br>
|
|
||||||
* {@link cc.sukazyo.cono.morny.bot.event.MornyOnUpdateTimestampOffsetLock}
|
|
||||||
* 会根据这里定义的时间戳取消掉比此时间更早的事件链
|
|
||||||
*/
|
|
||||||
public final long eventOutdatedTimestamp;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* system: command list automation *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
public final boolean commandLoginRefresh;
|
|
||||||
public final boolean commandLogoutClear;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* system: morny report *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 控制 Morny Coeur 系统的报告的报告对象.
|
|
||||||
* @since 1.0.0-alpha5
|
|
||||||
*/
|
|
||||||
public final long reportToChat;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* function: dinner query tool *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
@Nonnull public final Set<Long> dinnerTrustedReaders;
|
|
||||||
public final long dinnerChatId;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* function: medication timer *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
public final long medicationNotifyToChat;
|
|
||||||
|
|
||||||
@Nonnull public final ZoneOffset medicationTimerUseTimezone;
|
|
||||||
|
|
||||||
@Nonnull public final Set<Integer> medicationNotifyAt;
|
|
||||||
|
|
||||||
/* ======================================= *
|
|
||||||
* End Configs | ConfigBuilder *
|
|
||||||
* ======================================= */
|
|
||||||
|
|
||||||
private MornyConfig (@Nonnull Prototype prototype) throws CheckFailure {
|
|
||||||
this.telegramBotApiServer = prototype.telegramBotApiServer;
|
|
||||||
this.telegramBotApiServer4File = prototype.telegramBotApiServer4File;
|
|
||||||
if (prototype.telegramBotKey == null) throw new CheckFailure.NullTelegramBotKey();
|
|
||||||
this.telegramBotKey = prototype.telegramBotKey;
|
|
||||||
this.telegramBotUsername = prototype.telegramBotUsername;
|
|
||||||
this.trustedMaster = prototype.trustedMaster;
|
|
||||||
this.trustedChat = prototype.trustedChat;
|
|
||||||
this.eventIgnoreOutdated = prototype.eventIgnoreOutdated;
|
|
||||||
if (prototype.eventOutdatedTimestamp < 1) throw new CheckFailure.UnsetEventOutdatedTimestamp();
|
|
||||||
this.eventOutdatedTimestamp = prototype.eventOutdatedTimestamp;
|
|
||||||
this.commandLoginRefresh = prototype.commandLoginRefresh;
|
|
||||||
this.commandLogoutClear = prototype.commandLogoutClear;
|
|
||||||
this.dinnerTrustedReaders = prototype.dinnerTrustedReaders;
|
|
||||||
this.dinnerChatId = prototype.dinnerChatId;
|
|
||||||
this.reportToChat = prototype.reportToChat;
|
|
||||||
this.medicationNotifyToChat = prototype.medicationNotifyToChat;
|
|
||||||
this.medicationTimerUseTimezone = prototype.medicationTimerUseTimezone;
|
|
||||||
prototype.medicationNotifyAt.forEach(i -> { if (i < 0 || i > 23) throw new CheckFailure.UnavailableTimeInMedicationNotifyAt(); });
|
|
||||||
this.medicationNotifyAt = prototype.medicationNotifyAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CheckFailure extends RuntimeException {
|
|
||||||
public static class NullTelegramBotKey extends CheckFailure {}
|
|
||||||
public static class UnsetEventOutdatedTimestamp extends CheckFailure {}
|
|
||||||
public static class UnavailableTimeInMedicationNotifyAt extends CheckFailure {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Prototype {
|
|
||||||
|
|
||||||
public MornyConfig build () {
|
|
||||||
return new MornyConfig(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable public String telegramBotApiServer = null;
|
|
||||||
@Nullable public String telegramBotApiServer4File = null;
|
|
||||||
@Nullable public String telegramBotKey = null;
|
|
||||||
@Nullable public String telegramBotUsername = null;
|
|
||||||
public long trustedMaster = -1L;
|
|
||||||
public long trustedChat = -1L;
|
|
||||||
public boolean eventIgnoreOutdated = false;
|
|
||||||
public long eventOutdatedTimestamp = -1;
|
|
||||||
public boolean commandLoginRefresh = false;
|
|
||||||
public boolean commandLogoutClear = false;
|
|
||||||
@Nonnull public final Set<Long> dinnerTrustedReaders = new HashSet<>();
|
|
||||||
public long dinnerChatId = -1L;
|
|
||||||
public long reportToChat = -1L;
|
|
||||||
public long medicationNotifyToChat = -1L;
|
|
||||||
@Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC;
|
|
||||||
@Nonnull public final Set<Integer> medicationNotifyAt = new HashSet<>();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.internal.BuildConfigField
|
|
||||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
|
||||||
import cc.sukazyo.cono.morny.util.FileUtils
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URISyntaxException
|
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
|
|
||||||
object MornySystem {
|
|
||||||
|
|
||||||
@BuildConfigField val VERSION: String = BuildConfig.VERSION
|
|
||||||
@BuildConfigField val VERSION_FULL: String = BuildConfig.VERSION_FULL
|
|
||||||
@BuildConfigField val VERSION_BASE: String = BuildConfig.VERSION_BASE
|
|
||||||
@BuildConfigField val VERSION_DELTA: String = BuildConfig.VERSION_DELTA
|
|
||||||
@BuildConfigField val CODENAME: String = BuildConfig.CODENAME
|
|
||||||
@BuildConfigField val CODE_STORE: String = BuildConfig.CODE_STORE
|
|
||||||
//noinspection ScalaWeakerAccess
|
|
||||||
@BuildConfigField val COMMIT_PATH: String = BuildConfig.COMMIT_PATH
|
|
||||||
|
|
||||||
@BuildConfigField
|
|
||||||
def isUseDelta: Boolean = VERSION_DELTA ne null
|
|
||||||
|
|
||||||
@BuildConfigField
|
|
||||||
def isGitBuild: Boolean = BuildConfig.COMMIT ne null
|
|
||||||
|
|
||||||
@BuildConfigField
|
|
||||||
def isCleanBuild: Boolean = BuildConfig.CLEAN_BUILD
|
|
||||||
|
|
||||||
def currentCodePath: String|Null =
|
|
||||||
if ((COMMIT_PATH eq null) || (!isGitBuild)) null
|
|
||||||
else COMMIT_PATH.formatted(BuildConfig.COMMIT)
|
|
||||||
|
|
||||||
def getJarMD5: String = {
|
|
||||||
try {
|
|
||||||
FileUtils.getMD5Three(MornySystem.getClass.getProtectionDomain.getCodeSource.getLocation.toURI.getPath)
|
|
||||||
} catch
|
|
||||||
//noinspection ScalaUnnecessaryParentheses
|
|
||||||
case _: (IOException|URISyntaxException) =>
|
|
||||||
"<non-jar-runtime>"
|
|
||||||
case n: NoSuchAlgorithmException =>
|
|
||||||
logger error exceptionLog(n)
|
|
||||||
// MornyReport.exception(n, "<coeur-md5/calculation-error>") // todo: will not implemented
|
|
||||||
"<calculation-error>"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.{LimboChat, LimboUser}
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Chat.*
|
|
||||||
import cc.sukazyo.cono.morny.Log.logger
|
|
||||||
import com.pengrad.telegrambot.model.ChatMember.Status
|
|
||||||
import com.pengrad.telegrambot.TelegramBot
|
|
||||||
|
|
||||||
class MornyTrusted (using coeur: MornyCoeur)(using config: MornyConfig) {
|
|
||||||
|
|
||||||
if config.trustedMaster == -1 then
|
|
||||||
logger warn "You have not set your Morny's master.\n it may have some issues on controlling your bot."
|
|
||||||
|
|
||||||
def isTrusted (userId: Long): Boolean =
|
|
||||||
given TelegramBot = coeur.account
|
|
||||||
if userId == config.trustedMaster then true
|
|
||||||
else if config.trustedChat == -1 then false
|
|
||||||
else LimboChat(config.trustedChat) memberHasPermission(LimboUser(userId), Status.administrator)
|
|
||||||
|
|
||||||
def isTrusted_dinnerReader (userId: Long): Boolean =
|
|
||||||
if userId == config.trustedMaster then true
|
|
||||||
else config.dinnerTrustedReaders contains userId
|
|
||||||
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.Log.logger
|
|
||||||
import cc.sukazyo.cono.morny.MornyConfig.CheckFailure
|
|
||||||
import cc.sukazyo.cono.morny.util.CommonFormat
|
|
||||||
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import scala.collection.mutable.ArrayBuffer
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
object ServerMain {
|
|
||||||
|
|
||||||
private val THREAD_MORNY_INIT: String = "morny-init"
|
|
||||||
|
|
||||||
val systemStartupTime: Long = System.currentTimeMillis()
|
|
||||||
|
|
||||||
def main (args: Array[String]): Unit = {
|
|
||||||
|
|
||||||
val config = new MornyConfig.Prototype()
|
|
||||||
var mode_echoVersion = false
|
|
||||||
var mode_echoHello = false
|
|
||||||
var showHello = true
|
|
||||||
|
|
||||||
config.eventOutdatedTimestamp = systemStartupTime
|
|
||||||
|
|
||||||
val unknownArgs = ArrayBuffer[String]()
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while (i < args.length) {
|
|
||||||
args(i) match {
|
|
||||||
|
|
||||||
case "-d" | "--dbg" | "--debug" => Log.debug(true)
|
|
||||||
|
|
||||||
case "--no-hello" | "-hf" | "--quiet" | "-q" => showHello = false
|
|
||||||
case "--only-hello" | "-ho" | "-o" | "-hi" => mode_echoHello = true
|
|
||||||
case "--version" | "-v" => mode_echoVersion = true
|
|
||||||
|
|
||||||
case "--outdated-block" | "-ob" => config.eventIgnoreOutdated = true
|
|
||||||
|
|
||||||
case "--api" | "-a" => i+=1 ; config.telegramBotApiServer = args(i)
|
|
||||||
case "--api-files" | "files-api" | "-af" => i+=1; config.telegramBotApiServer4File = args(i)
|
|
||||||
|
|
||||||
case "--token" | "-t" => i+=1 ; config.telegramBotKey = args(i)
|
|
||||||
case "--username" | "-u" => i+=1 ; config.telegramBotUsername = args(i)
|
|
||||||
|
|
||||||
case "--master" | "-mm" => i+=1 ; config.trustedMaster = args(i)toLong
|
|
||||||
case "--trusted-chat" | "-trs" => i+=1 ; config.trustedChat = args(i)toLong
|
|
||||||
case "--report-to" => i+=1; config.reportToChat = args(i)toLong
|
|
||||||
|
|
||||||
case "--trusted-reader-dinner" | "-trsd" => i+=1 ; config.dinnerTrustedReaders add (args(i)toLong)
|
|
||||||
case "--dinner-chat" | "-chd" => i+=1 ; config.dinnerChatId = args(i)toLong
|
|
||||||
|
|
||||||
case "--medication-notify-chat" | "-medc" => i+=1 ; config.medicationNotifyToChat = args(i)toLong
|
|
||||||
case "--medication-notify-timezone" | "-medtz" =>
|
|
||||||
i+=1
|
|
||||||
config.medicationTimerUseTimezone = ZoneOffset.ofHours(args(i)toInt)
|
|
||||||
case "--medication-notify-times" | "-medt" =>
|
|
||||||
i+=1
|
|
||||||
for (u <- args(i) split ",") {
|
|
||||||
config.medicationNotifyAt add (u toInt)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "--auto-cmd-list" | "-ca" => config.commandLoginRefresh = true
|
|
||||||
case "--auto-cmd-remove" | "-cr" => config.commandLogoutClear = true
|
|
||||||
case "--auto-cmd" | "-cmd" | "-c" =>
|
|
||||||
config.commandLoginRefresh = true
|
|
||||||
config.commandLogoutClear = true
|
|
||||||
|
|
||||||
case _ => unknownArgs append args(i)
|
|
||||||
|
|
||||||
}
|
|
||||||
i+=1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setup launch params from ENVIRONMENT
|
|
||||||
var propToken: String = null
|
|
||||||
var propTokenKey: String = null
|
|
||||||
for (iKey <- MornyConfig.PROP_TOKEN_KEY) {
|
|
||||||
if ((System getenv iKey) != null) {
|
|
||||||
propToken = System getenv iKey
|
|
||||||
propTokenKey = iKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Output startup message
|
|
||||||
/// process startup params - like startup mode
|
|
||||||
///
|
|
||||||
|
|
||||||
if (showHello) logger info MornyAbout.MORNY_PREVIEW_IMAGE_ASCII
|
|
||||||
if (mode_echoHello) return;
|
|
||||||
|
|
||||||
if (unknownArgs.nonEmpty) logger warn
|
|
||||||
s"""Can't understand arg to some meaning
|
|
||||||
| ${unknownArgs mkString "\n "}"""
|
|
||||||
.stripMargin
|
|
||||||
|
|
||||||
if (Log debug)
|
|
||||||
logger warn
|
|
||||||
"""Debug log output enabled.
|
|
||||||
| It may lower your performance, make sure that you are not in production environment."""
|
|
||||||
.stripMargin
|
|
||||||
|
|
||||||
if (mode_echoVersion) {
|
|
||||||
|
|
||||||
logger info
|
|
||||||
s"""Morny Cono Version
|
|
||||||
|- version :
|
|
||||||
| Morny ${MornySystem.CODENAME toUpperCase}
|
|
||||||
| ${MornySystem.VERSION_BASE}${if (MornySystem.isUseDelta) "-δ"+MornySystem.VERSION_DELTA else ""}
|
|
||||||
|- md5hash :
|
|
||||||
| ${MornySystem.getJarMD5}
|
|
||||||
|- gitstat :
|
|
||||||
|${ if (MornySystem.isGitBuild) {
|
|
||||||
s""" on commit ${if (MornySystem.isCleanBuild) "- clean-build" else "<δ/non-clean-build>"}
|
|
||||||
| ${BuildConfig.COMMIT}"""
|
|
||||||
.stripMargin
|
|
||||||
} else " <non-git-build>"}
|
|
||||||
|- buildtd :
|
|
||||||
| ${BuildConfig.CODE_TIMESTAMP}
|
|
||||||
| ${CommonFormat.formatDate(BuildConfig.CODE_TIMESTAMP, 0)} [UTC]"""
|
|
||||||
.stripMargin
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
logger info
|
|
||||||
s"""ServerMain.java Loaded >>>
|
|
||||||
|- version ${MornySystem.VERSION_FULL}
|
|
||||||
|- Morny ${MornySystem.CODENAME toUpperCase}
|
|
||||||
|- <${MornySystem.getJarMD5}> [${BuildConfig.CODE_TIMESTAMP}]""".stripMargin
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Check Coeur arguments
|
|
||||||
/// finally start Coeur Program
|
|
||||||
///
|
|
||||||
|
|
||||||
if (propToken != null) {
|
|
||||||
config.telegramBotKey = propToken
|
|
||||||
logger info s"Parameter <token> set by EnvVar $$$propTokenKey"
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.currentThread setName THREAD_MORNY_INIT
|
|
||||||
|
|
||||||
try
|
|
||||||
MornyCoeur(using config build)
|
|
||||||
catch {
|
|
||||||
case _: CheckFailure.NullTelegramBotKey =>
|
|
||||||
logger.info("Parameter required has no value:\n --token.")
|
|
||||||
case e: CheckFailure =>
|
|
||||||
logger.error("Unknown failure occurred while starting ServerMain!:")
|
|
||||||
e.printStackTrace(System.out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.api
|
|
||||||
|
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
|
|
||||||
trait EventListener () {
|
|
||||||
|
|
||||||
def onMessage (using Update): Boolean = false
|
|
||||||
def onEditedMessage (using Update): Boolean = false
|
|
||||||
def onChannelPost (using Update): Boolean = false
|
|
||||||
def onEditedChannelPost (using Update): Boolean = false
|
|
||||||
def onInlineQuery (using Update): Boolean = false
|
|
||||||
def onChosenInlineResult (using Update): Boolean = false
|
|
||||||
def onCallbackQuery (using Update): Boolean = false
|
|
||||||
def onShippingQuery (using Update): Boolean = false
|
|
||||||
def onPreCheckoutQuery (using Update): Boolean = false
|
|
||||||
def onPoll (using Update): Boolean = false
|
|
||||||
def onPollAnswer (using Update): Boolean = false
|
|
||||||
def onMyChatMemberUpdated (using Update): Boolean = false
|
|
||||||
def onChatMemberUpdated (using Update): Boolean = false
|
|
||||||
def onChatJoinRequest (using Update): Boolean = false
|
|
||||||
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.api
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.{Log, MornyCoeur}
|
|
||||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
|
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
import com.pengrad.telegrambot.UpdatesListener
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
/** Contains a [[mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]].
|
|
||||||
*
|
|
||||||
* Implemented [[process]] in [[UpdatesListener]] so it can directly used in [[com.pengrad.telegrambot.TelegramBot.setupListener]].
|
|
||||||
*
|
|
||||||
* @param coeur the [[MornyCoeur]] context.
|
|
||||||
*/
|
|
||||||
class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
|
||||||
|
|
||||||
private val listeners = mutable.Queue.empty[EventListener]
|
|
||||||
|
|
||||||
def register (listeners: EventListener*): Unit =
|
|
||||||
this.listeners ++= listeners
|
|
||||||
|
|
||||||
private class EventRunner (using event: Update) extends Thread {
|
|
||||||
this setName s"evt-${event.updateId()}-nn"
|
|
||||||
private def updateThreadName (t: String): Unit =
|
|
||||||
this setName s"evt-${event.updateId()}-$t"
|
|
||||||
|
|
||||||
override def run (): Unit = {
|
|
||||||
for (i <- listeners) {
|
|
||||||
object status:
|
|
||||||
var _status = 0
|
|
||||||
def isOk: Boolean = _status > 0
|
|
||||||
def check (u: Boolean): Unit = if u then _status = _status + 1
|
|
||||||
try {
|
|
||||||
updateThreadName("message")
|
|
||||||
if event.message ne null then status check i.onMessage
|
|
||||||
updateThreadName("edited-message")
|
|
||||||
if event.editedMessage ne null then status check i.onEditedMessage
|
|
||||||
updateThreadName("channel-post")
|
|
||||||
if event.channelPost ne null then status check i.onChannelPost
|
|
||||||
updateThreadName("edited-channel-post")
|
|
||||||
if event.editedChannelPost ne null then status check i.onEditedChannelPost
|
|
||||||
updateThreadName("inline-query")
|
|
||||||
if event.inlineQuery ne null then status check i.onInlineQuery
|
|
||||||
updateThreadName("chosen-inline-result")
|
|
||||||
if event.chosenInlineResult ne null then status check i.onChosenInlineResult
|
|
||||||
updateThreadName("callback-query")
|
|
||||||
if event.callbackQuery ne null then status check i.onCallbackQuery
|
|
||||||
updateThreadName("shipping-query")
|
|
||||||
if event.shippingQuery ne null then status check i.onShippingQuery
|
|
||||||
updateThreadName("pre-checkout-query")
|
|
||||||
if event.preCheckoutQuery ne null then status check i.onPreCheckoutQuery
|
|
||||||
updateThreadName("poll")
|
|
||||||
if event.poll ne null then status check i.onPoll
|
|
||||||
updateThreadName("poll-answer")
|
|
||||||
if event.pollAnswer ne null then status check i.onPollAnswer
|
|
||||||
updateThreadName("my-chat-member")
|
|
||||||
if event.myChatMember ne null then status check i.onMyChatMemberUpdated
|
|
||||||
updateThreadName("chat-member")
|
|
||||||
if event.chatMember ne null then status check i.onChatMemberUpdated
|
|
||||||
updateThreadName("chat-join-request")
|
|
||||||
if event.chatJoinRequest ne null then status check i.onChatJoinRequest
|
|
||||||
} catch case e => {
|
|
||||||
val errorMessage = StringBuilder()
|
|
||||||
errorMessage ++= "Event throws unexpected exception:\n"
|
|
||||||
errorMessage ++= (exceptionLog(e) indent 4)
|
|
||||||
e match
|
|
||||||
case actionFailed: EventRuntimeException.ActionFailed =>
|
|
||||||
errorMessage ++= "\ntg-api action: response track: "
|
|
||||||
errorMessage ++= (GsonBuilder().setPrettyPrinting().create().toJson(
|
|
||||||
actionFailed.response
|
|
||||||
) indent 4) ++= "\n"
|
|
||||||
case _ =>
|
|
||||||
logger error errorMessage.toString
|
|
||||||
coeur.daemons.reporter.exception(e, "on event running")
|
|
||||||
}
|
|
||||||
if (status isOk) return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
import java.util
|
|
||||||
import scala.jdk.CollectionConverters.*
|
|
||||||
/** Delivery the telegram [[Update]]s.
|
|
||||||
*
|
|
||||||
* The implementation of [[UpdatesListener]].
|
|
||||||
*
|
|
||||||
* For each [[Update]], create an [[EventRunner]] for it, and
|
|
||||||
* start the it.
|
|
||||||
*
|
|
||||||
* @return [[UpdatesListener.CONFIRMED_UPDATES_ALL]], for all Updates
|
|
||||||
* should be processed in [[EventRunner]] created for it.
|
|
||||||
*/
|
|
||||||
override def process (updates: util.List[Update]): Int = {
|
|
||||||
for (update <- updates.asScala)
|
|
||||||
EventRunner(using update).start()
|
|
||||||
UpdatesListener.CONFIRMED_UPDATES_ALL
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.Log.logger
|
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
|
||||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
|
||||||
import com.pengrad.telegrambot.model.{Chat, Update}
|
|
||||||
import com.pengrad.telegrambot.request.{DeleteMessage, GetChatMember, SendSticker}
|
|
||||||
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
class DirectMsgClear (using coeur: MornyCoeur) extends ISimpleCommand {
|
|
||||||
|
|
||||||
override val name: String = "r"
|
|
||||||
override val aliases: Array[ICommandAlias] | Null = null
|
|
||||||
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = {
|
|
||||||
|
|
||||||
logger debug "executing command /r"
|
|
||||||
if (event.message.replyToMessage == null) return;
|
|
||||||
logger trace "message is a reply"
|
|
||||||
if (event.message.replyToMessage.from.id != coeur.userid) return;
|
|
||||||
logger trace "message replied is from me"
|
|
||||||
if (System.currentTimeMillis/1000 - event.message.replyToMessage.date > 48*60*60) return;
|
|
||||||
logger trace "message is not outdated(48 hrs ago)"
|
|
||||||
|
|
||||||
val isTrusted = coeur.trusted isTrusted event.message.from.id
|
|
||||||
// todo:
|
|
||||||
// it does not work. due to the Telegram Bot API doesn't provide
|
|
||||||
// nested replyToMessage, so currently the trusted check by
|
|
||||||
// replyToMessage.replyToMessage will not work!
|
|
||||||
def _isReplyTrusted: Boolean =
|
|
||||||
if (event.message.replyToMessage.replyToMessage == null) false
|
|
||||||
else if (event.message.replyToMessage.replyToMessage.from.id == event.message.from.id) true
|
|
||||||
else false
|
|
||||||
|
|
||||||
if (isTrusted || _isReplyTrusted) {
|
|
||||||
|
|
||||||
coeur.account exec DeleteMessage(
|
|
||||||
event.message.chat.id, event.message.replyToMessage.messageId
|
|
||||||
)
|
|
||||||
|
|
||||||
def _isPrivate: Boolean = event.message.chat.`type` == Chat.Type.Private
|
|
||||||
def _isPermission: Boolean =
|
|
||||||
(coeur.account exec GetChatMember(event.message.chat.id, event.message.from.id))
|
|
||||||
.chatMember.canDeleteMessages
|
|
||||||
if (_isPrivate || _isPermission) {
|
|
||||||
coeur.account exec DeleteMessage(event.message.chat.id, event.message.messageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else coeur.account exec SendSticker(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramStickers ID_403
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,230 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.Log.logger
|
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
|
||||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
|
||||||
import cc.sukazyo.cono.morny.util.CommonEncrypt
|
|
||||||
import cc.sukazyo.cono.morny.util.CommonEncrypt.*
|
|
||||||
import cc.sukazyo.cono.morny.util.ConvertByteHex.toHex
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
|
||||||
import com.pengrad.telegrambot.model.{PhotoSize, Update}
|
|
||||||
import com.pengrad.telegrambot.model.request.ParseMode
|
|
||||||
import com.pengrad.telegrambot.request.{GetFile, SendDocument, SendMessage, SendSticker}
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.Base64
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
/** Provides Telegram Command __`/encrypt`__. */
|
|
||||||
class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
|
|
||||||
|
|
||||||
override val name: String = "encrypt"
|
|
||||||
override val aliases: Array[ICommandAlias] | Null = null
|
|
||||||
override val paramRule: String = "[algorithm|(l)] [(uppercase)]"
|
|
||||||
override val description: String = "通过指定算法加密回复的内容 (目前只支持文本)"
|
|
||||||
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = {
|
|
||||||
|
|
||||||
val args = command.args
|
|
||||||
|
|
||||||
// show a simple help page
|
|
||||||
if ((args isEmpty) || ((args(0) equals "l") && (args.length == 1)))
|
|
||||||
echoHelp(event.message.chat.id, event.message.messageId)
|
|
||||||
return
|
|
||||||
|
|
||||||
// for mod-params:
|
|
||||||
// mod-params is the args belongs to the encrypt algorithm.
|
|
||||||
// due to the algorithm is defined in the 1st (array(0)) arg,
|
|
||||||
// so the mod-params is which defined since the 2nd arg. also
|
|
||||||
// due to there's only one mod-param yet (it is uppercase),
|
|
||||||
// so the algorithm will be and must be in the 2nd arg.
|
|
||||||
/** inner function: is input `arg` means mod-param ''uppercase'' */
|
|
||||||
def _is_mod_u(arg: String): Boolean =
|
|
||||||
if (arg equalsIgnoreCase "uppercase") return true
|
|
||||||
if (arg equalsIgnoreCase "u") return true
|
|
||||||
if (arg equalsIgnoreCase "upper") return true
|
|
||||||
false
|
|
||||||
val mod_uppercase = if (args.length > 1) {
|
|
||||||
if (args.length < 3 && _is_mod_u(args(1))) true
|
|
||||||
else
|
|
||||||
coeur.account exec SendSticker(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramStickers ID_404
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
} else false
|
|
||||||
|
|
||||||
// BLOCK: get input
|
|
||||||
// for now, only support getting data from replied message, and
|
|
||||||
// this message CAN ONLY have texts or an universal file: if the
|
|
||||||
// universal files are not only one, only the first one can be get.
|
|
||||||
// - do NOT SUPPORT telegram inline image/video/autio yet
|
|
||||||
// - do NOT SUPPORT multi-file yet
|
|
||||||
// todo: support inline image/video/audio file and multi-files.
|
|
||||||
/** inner trait: the encryptable data abstract */
|
|
||||||
trait XEncryptable { /** standards data to [[Array]]`[`[[Byte]]`]` for processing */ val asByteArray: Array[Byte] }
|
|
||||||
/** inner class: the [[XEncryptable]] implementation of binary([[Array]]`[`[[Byte]]`]`) data (file or something) */
|
|
||||||
case class XFile (data: Array[Byte], name: String) extends XEncryptable:
|
|
||||||
val asByteArray: Array[Byte] = data
|
|
||||||
/** inner class: the [[XEncryptable]] implementation of [[String]] data */
|
|
||||||
case class XText (data: String) extends XEncryptable:
|
|
||||||
val asByteArray: Array[Byte] = data getBytes CommonEncrypt.ENCRYPT_STANDARD_CHARSET
|
|
||||||
val input: XEncryptable =
|
|
||||||
val _r = event.message.replyToMessage
|
|
||||||
if ((_r ne null) && (_r.document ne null)) {
|
|
||||||
try {XFile(
|
|
||||||
coeur.account getFileContent (coeur.account exec GetFile(_r.document.fileId)).file,
|
|
||||||
_r.document.fileName
|
|
||||||
)} catch case e: IOException =>
|
|
||||||
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
|
|
||||||
coeur.daemons.reporter.exception(e, "NetworkRequest error: TelegramFileAPI")
|
|
||||||
return
|
|
||||||
} else if ((_r ne null) && (_r.photo ne null)) {
|
|
||||||
try {
|
|
||||||
var _photo_origin: PhotoSize = null
|
|
||||||
var _photo_size: Long = 0
|
|
||||||
for (size <- _r.photo)
|
|
||||||
val _size = (size.width longValue)*size.height
|
|
||||||
if (_photo_size < _size)
|
|
||||||
_photo_origin = size
|
|
||||||
_photo_size = _size
|
|
||||||
if (_photo_origin eq null) throw IllegalArgumentException("no photo from api.")
|
|
||||||
import cc.sukazyo.cono.morny.util.UseRandom.rand_id
|
|
||||||
XFile(
|
|
||||||
coeur.account getFileContent (coeur.account exec GetFile(_photo_origin.fileId)).file,
|
|
||||||
s"photo$rand_id.png"
|
|
||||||
)
|
|
||||||
} catch
|
|
||||||
case e: IOException =>
|
|
||||||
//noinspection DuplicatedCode
|
|
||||||
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
|
|
||||||
coeur.daemons.reporter.exception(e, "NetworkRequest error: TelegramFileAPI")
|
|
||||||
return
|
|
||||||
case e: IllegalArgumentException =>
|
|
||||||
logger warn s"FileProcess error: PhotoSize:\n\t${e.getMessage}"
|
|
||||||
coeur.daemons.reporter.exception(e, "FileProcess error: PhotoSize")
|
|
||||||
return
|
|
||||||
} else if ((_r ne null) && (_r.text ne null)) {
|
|
||||||
XText(_r.text)
|
|
||||||
} else {
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
"<i><u>null</u></i>"
|
|
||||||
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// END BLOCK: get input
|
|
||||||
|
|
||||||
// BLOCK: encrypt
|
|
||||||
/** inner class: encrypt result implementation of text-like (can be described as [[String]]). */
|
|
||||||
trait EXTextLike { val text: String }
|
|
||||||
/** inner class: encrypt result implementation of a file */
|
|
||||||
case class EXFile (result: Array[Byte], resultName: String)
|
|
||||||
/** inner class: [[EXTextLike]] implementation of just normal text */
|
|
||||||
case class EXText (text: String) extends EXTextLike
|
|
||||||
/** inner class: [[EXTextLike]] implementation of a special type: hash value */
|
|
||||||
case class EXHash (text: String) extends EXTextLike
|
|
||||||
/** generate encrypt result by making normal encrypt: output type == input type */
|
|
||||||
def genResult_encrypt (source: XEncryptable, processor: Array[Byte]=>Array[Byte], filenameProcessor: String=>String): EXFile|EXText = {
|
|
||||||
source match
|
|
||||||
case x_file: XFile => EXFile(processor(x_file asByteArray), filenameProcessor(x_file.name))
|
|
||||||
case x: XText => EXText(String(processor(x asByteArray), CommonEncrypt.ENCRYPT_STANDARD_CHARSET))
|
|
||||||
}
|
|
||||||
/** generate encrypt result by making hash: output type == hash value */
|
|
||||||
def genResult_hash (source: XEncryptable, processor: Array[Byte]=>Array[Byte]): EXHash =
|
|
||||||
val hashed = processor(source asByteArray) toHex;
|
|
||||||
EXHash(if mod_uppercase then hashed toUpperCase else hashed)
|
|
||||||
val result: EXHash|EXFile|EXText = args(0) match
|
|
||||||
case "base64" | "b64" | "base64url" | "base64u" | "b64u" =>
|
|
||||||
val _tool_b64 =
|
|
||||||
if args(0) contains "u" then Base64.getUrlEncoder
|
|
||||||
else Base64.getEncoder
|
|
||||||
genResult_encrypt(
|
|
||||||
input,
|
|
||||||
_tool_b64.encode,
|
|
||||||
n => n+".b64.txt"
|
|
||||||
)
|
|
||||||
case "base64decode" | "base64d" | "b64d" | "base64url-decode" | "base64ud" | "b64ud" =>
|
|
||||||
val _tool_b64d =
|
|
||||||
if args(0) contains "u" then Base64.getUrlDecoder
|
|
||||||
else Base64.getDecoder
|
|
||||||
try { genResult_encrypt(
|
|
||||||
input,
|
|
||||||
_tool_b64d.decode,
|
|
||||||
CommonEncrypt.lint_base64FileName
|
|
||||||
) } catch case _: IllegalArgumentException =>
|
|
||||||
coeur.account exec SendSticker(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramStickers ID_404 // todo: is here better erro notify?
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
case "md5" => genResult_hash(input, MD5)
|
|
||||||
case "sha1" => genResult_hash(input, SHA1)
|
|
||||||
case "sha256" => genResult_hash(input, SHA256)
|
|
||||||
case "sha512" => genResult_hash(input, SHA512)
|
|
||||||
case _ =>
|
|
||||||
coeur.account exec SendSticker(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramStickers ID_404
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return;
|
|
||||||
// END BLOCK: encrypt
|
|
||||||
|
|
||||||
// output
|
|
||||||
result match
|
|
||||||
case _file: EXFile =>
|
|
||||||
coeur.account exec SendDocument(
|
|
||||||
event.message.chat.id,
|
|
||||||
_file.result
|
|
||||||
).fileName(_file.resultName).replyToMessageId(event.message.messageId)
|
|
||||||
case _text: EXTextLike =>
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
s"<pre><code>${h(_text.text)}</code></pre>"
|
|
||||||
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** echo help to a specific message in a specific chat.
|
|
||||||
*
|
|
||||||
* === the help message ===
|
|
||||||
* The first paragraph lists available encrypt algorithms and its alias,
|
|
||||||
* each line have one algorithm where the first name highlighted is the
|
|
||||||
* main name and following is aliases separated with `,`.
|
|
||||||
* with the separator `---`, the second paragraph lists available mods
|
|
||||||
* for algorithms, displays with the same rule of algorithms, with an extra
|
|
||||||
* italic text following describes its usage environment.
|
|
||||||
*
|
|
||||||
* when output to telegram just like:
|
|
||||||
* <blockquote>
|
|
||||||
* '''__base64__''', b64<br>
|
|
||||||
* '''__base64url__''', base64u, b64u<br>
|
|
||||||
* '''__base64decode__''', base64d, b64d<br>
|
|
||||||
* '''__base64url-decode__''', base64ud, b64ud<br>
|
|
||||||
* '''__sha1__'''<br>
|
|
||||||
* '''__sha256__'''<br>
|
|
||||||
* '''__sha512__'''<br>
|
|
||||||
* '''__md5__'''<br>
|
|
||||||
* ---<br>
|
|
||||||
* '''__uppercase__''', upper, u ''(sha1/sha256/sha512/md5 only)''
|
|
||||||
* </blockquote>
|
|
||||||
*/
|
|
||||||
private def echoHelp(chat: Long, replyTo: Int): Unit =
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
chat,
|
|
||||||
s"""<b><u>base64</u></b>, b64
|
|
||||||
|<b><u>base64url</u></b>, base64u, b64u
|
|
||||||
|<b><u>base64decode</u></b>, base64d, b64d
|
|
||||||
|<b><u>base64url-decode</u></b>, base64ud, b64ud
|
|
||||||
|<b><u>sha1</u></b>
|
|
||||||
|<b><u>sha256</u></b>
|
|
||||||
|<b><u>sha512</u></b>
|
|
||||||
|<b><u>md5</u></b>
|
|
||||||
|---
|
|
||||||
|<b><i>uppercase</i></b>, upper, u <i>(sha1/sha256/sha512/md5 only)</i>"""
|
|
||||||
.stripMargin
|
|
||||||
).replyToMessageId(replyTo).parseMode(ParseMode HTML)
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
|
||||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
import com.pengrad.telegrambot.request.SendSticker
|
|
||||||
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
class EventHack (using coeur: MornyCoeur) extends ITelegramCommand {
|
|
||||||
|
|
||||||
override val name: String = "event_hack"
|
|
||||||
override val aliases: Array[ICommandAlias] | Null = null
|
|
||||||
override val paramRule: String = "[(user|group|any)]"
|
|
||||||
override val description: String = "输出 bot 下一个获取到的事件序列化数据"
|
|
||||||
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = {
|
|
||||||
|
|
||||||
import coeur.daemons.eventHack.{registerHack, HackType}
|
|
||||||
|
|
||||||
val x_mode = if (command.args nonEmpty) command.args(0) else ""
|
|
||||||
|
|
||||||
def done_ok =
|
|
||||||
coeur.account exec SendSticker(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramStickers ID_WAITING
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
def done_forbiddenForAny =
|
|
||||||
coeur.account exec SendSticker(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramStickers ID_403
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
|
|
||||||
def doRegister (t: HackType): Unit =
|
|
||||||
registerHack(
|
|
||||||
event.message.messageId longValue,
|
|
||||||
event.message.from.id,
|
|
||||||
event.message.chat.id,
|
|
||||||
t
|
|
||||||
)
|
|
||||||
x_mode match
|
|
||||||
case "any" =>
|
|
||||||
if (coeur.trusted isTrusted event.message.from.id)
|
|
||||||
doRegister(HackType ANY)
|
|
||||||
done_ok
|
|
||||||
else done_forbiddenForAny
|
|
||||||
case "group" =>
|
|
||||||
doRegister(HackType GROUP)
|
|
||||||
done_ok
|
|
||||||
case _ =>
|
|
||||||
doRegister(HackType USER)
|
|
||||||
done_ok
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.{InputCommand, Standardize}
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
import com.pengrad.telegrambot.model.request.ParseMode
|
|
||||||
import com.pengrad.telegrambot.request.{GetChatMember, SendMessage}
|
|
||||||
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
class GetUsernameAndId (using coeur: MornyCoeur) extends ITelegramCommand {
|
|
||||||
|
|
||||||
override val name: String = "user"
|
|
||||||
override val aliases: Array[ICommandAlias] | Null = null
|
|
||||||
override val paramRule: String = "[userid]"
|
|
||||||
override val description: String = "获取指定或回复的用户相关信息"
|
|
||||||
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = {
|
|
||||||
|
|
||||||
val args = command.args
|
|
||||||
|
|
||||||
if (args.length > 1)
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
"[Unavailable] Too much arguments."
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
|
|
||||||
val userId: Long =
|
|
||||||
if (args nonEmpty) {
|
|
||||||
try args(0) toLong
|
|
||||||
catch case e: NumberFormatException =>
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
s"[Unavailable] ${e.getMessage}"
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
} else if (event.message.replyToMessage eq null) event.message.from.id
|
|
||||||
else event.message.replyToMessage.from.id
|
|
||||||
|
|
||||||
val response = coeur.account execute GetChatMember(event.message.chat.id, userId)
|
|
||||||
|
|
||||||
if (response.chatMember eq null)
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
"[Unavailable] user not found."
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
|
|
||||||
val user = response.chatMember.user
|
|
||||||
|
|
||||||
if (user.id == Standardize.CHANNEL_SPEAKER_MAGIC_ID)
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
"<code>$__channel_identify</code>"
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
TelegramUserInformation getFormattedInformation user
|
|
||||||
).replyToMessageId(event.message.messageId()).parseMode(ParseMode HTML)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
|
|
||||||
/** One alias definition, contains the necessary message of how
|
|
||||||
* to process the alias.
|
|
||||||
*/
|
|
||||||
trait ICommandAlias {
|
|
||||||
|
|
||||||
/** The alias name.
|
|
||||||
*
|
|
||||||
* same with the command name, it is the unique identifier of this alias.
|
|
||||||
*/
|
|
||||||
val name: String
|
|
||||||
/** If the alias should be listed while list commands to end-user.
|
|
||||||
*
|
|
||||||
* The alias can only be listed when the parent command can be listed
|
|
||||||
* (meanwhile the parent command implemented [[ITelegramCommand]]). If the
|
|
||||||
* parent command cannot be listed, it will always cannot be listed.
|
|
||||||
*/
|
|
||||||
val listed: Boolean
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Default implementations of [[ICommandAlias]]. */
|
|
||||||
object ICommandAlias {
|
|
||||||
|
|
||||||
/** Alias which can be listed to end-user.
|
|
||||||
*
|
|
||||||
* the [[ICommandAlias.listed]] value is always true.
|
|
||||||
*
|
|
||||||
* @param name The alias name, see more in [[ICommandAlias.name]]
|
|
||||||
*/
|
|
||||||
case class ListedAlias (name: String) extends ICommandAlias:
|
|
||||||
override val listed = true
|
|
||||||
|
|
||||||
/** Alias which cannot be listed to end-user.
|
|
||||||
*
|
|
||||||
* the [[ICommandAlias.listed]] value is always false.
|
|
||||||
*
|
|
||||||
* @param name The alias name, see more in [[ICommandAlias.name]]
|
|
||||||
*/
|
|
||||||
case class HiddenAlias (name: String) extends ICommandAlias:
|
|
||||||
override val listed = false
|
|
||||||
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.MornyCoeur
|
|
||||||
import cc.sukazyo.cono.morny.data.ip186.IP186QueryHandler
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
import com.pengrad.telegrambot.model.request.ParseMode
|
|
||||||
import com.pengrad.telegrambot.request.SendMessage
|
|
||||||
|
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
class IP186Query (using coeur: MornyCoeur) {
|
|
||||||
|
|
||||||
private enum Subs (val cmd: String):
|
|
||||||
case IP extends Subs("ip")
|
|
||||||
case WHOIS extends Subs("whois")
|
|
||||||
|
|
||||||
object IP extends ITelegramCommand:
|
|
||||||
override val name: String = "ip"
|
|
||||||
override val aliases: Array[ICommandAlias]|Null = null
|
|
||||||
override val paramRule: String = "[ip]"
|
|
||||||
override val description: String = "通过 https://ip.186526.xyz 查询 ip 资料"
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = query
|
|
||||||
object Whois extends ITelegramCommand:
|
|
||||||
override val name: String = "whois"
|
|
||||||
override val aliases: Array[ICommandAlias]|Null = null
|
|
||||||
override val paramRule: String = "[domain]"
|
|
||||||
override val description: String = "通过 https://ip.186526.xyz 查询域名资料"
|
|
||||||
override def execute (using command: InputCommand, event: Update): Unit = query
|
|
||||||
|
|
||||||
private def query (using event: Update, command: InputCommand): Unit = {
|
|
||||||
|
|
||||||
val target: String|Null =
|
|
||||||
if (command.args isEmpty)
|
|
||||||
if event.message.replyToMessage eq null then null else event.message.replyToMessage.text
|
|
||||||
else if (command.args.length > 1)
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
"[Unavailable] Too much arguments."
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return
|
|
||||||
else command.args(0)
|
|
||||||
|
|
||||||
if (target eq null)
|
|
||||||
coeur.account exec new SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
"[Unavailable] No ip defined."
|
|
||||||
).replyToMessageId(event.message.messageId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
|
||||||
try {
|
|
||||||
|
|
||||||
val response = command.command match
|
|
||||||
case Subs.IP.cmd => IP186QueryHandler.query_ip(target)
|
|
||||||
case Subs.WHOIS.cmd => IP186QueryHandler.query_whoisPretty(target)
|
|
||||||
case _ => throw IllegalArgumentException(s"Unknown 186-IP query method ${command.command}")
|
|
||||||
|
|
||||||
coeur.account exec SendMessage(
|
|
||||||
event.message.chat.id,
|
|
||||||
s"""${h(response.url)}
|
|
||||||
|<code>${h(response.body)}</code>"""
|
|
||||||
.stripMargin
|
|
||||||
).parseMode(ParseMode HTML).replyToMessageId(event.message.messageId)
|
|
||||||
|
|
||||||
} catch case e: Exception =>
|
|
||||||
coeur.account exec new SendMessage(
|
|
||||||
event.message().chat().id(),
|
|
||||||
s"""[Exception] in query:
|
|
||||||
|<code>${h(e.getMessage)}</code>"""
|
|
||||||
.stripMargin
|
|
||||||
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package cc.sukazyo.cono.morny.bot.command
|
|
||||||
|
|
||||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
|
||||||
import com.pengrad.telegrambot.model.Update
|
|
||||||
|
|
||||||
/** A simple command.
|
|
||||||
*
|
|
||||||
* Contains only [[name]] and [[aliases]].
|
|
||||||
*
|
|
||||||
* Won't be listed to end-user. if you want the command listed,
|
|
||||||
* see [[ITelegramCommand]].
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
trait ISimpleCommand {
|
|
||||||
|
|
||||||
/** the main name of the command.
|
|
||||||
*
|
|
||||||
* must have a value as the unique identifier of this command.
|
|
||||||
*/
|
|
||||||
val name: String
|
|
||||||
/** aliases of the command.
|
|
||||||
*
|
|
||||||
* Alias means it is the same to call [[name main name]] when call this.
|
|
||||||
* There can be multiple aliases. But notice that, although alias is not
|
|
||||||
* the unique identifier, it uses the same namespace with [[name]], means
|
|
||||||
* it also cannot be duplicate with other [[name]] or [[aliases]].
|
|
||||||
*
|
|
||||||
* It can be [[Null]], means no aliases.
|
|
||||||
*/
|
|
||||||
val aliases: Array[ICommandAlias]|Null
|
|
||||||
|
|
||||||
/** The work code of this command.
|
|
||||||
*
|
|
||||||
* @param command The parsed input command which called this command.
|
|
||||||
* @param event The raw event which called this command.
|
|
||||||
*/
|
|
||||||
def execute (using command: InputCommand, event: Update): Unit
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user