Compare commits
No commits in common. "master" and "cli" have entirely different histories.
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/.idea/
|
||||
jelegram.iml
|
||||
/target/
|
||||
/.cache/
|
||||
/run/*
|
226
pom.xml
Normal file
226
pom.xml
Normal file
|
@ -0,0 +1,226 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>it.cavallium</groupId>
|
||||
<artifactId>jelegram</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<teavm.version>0.7.0-dev-1209</teavm.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>teavm-dev</id>
|
||||
<url>https://teavm.org/maven/repository</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>mchv</id>
|
||||
<name>MCHV Apache Maven Packages</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>teavm-dev</id>
|
||||
<url>https://teavm.org/maven/repository</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-java-bom</artifactId>
|
||||
<version>2.8.9.2</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Servlet 3.1 specification -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>23.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-natives-linux-amd64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-natives-windows-amd64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-natives-linux-aarch64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-natives-osx-amd64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli-shell-jline3</artifactId>
|
||||
<version>4.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.fusesource.jansi</groupId>
|
||||
<artifactId>jansi</artifactId>
|
||||
<version>2.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.14.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||
<version>2.19.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<release>17</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>4.0.0-M4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<!--<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>picocli.shell.jline3.example.Example</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
|
||||
<!-- note that the main class is set *here* -->
|
||||
|
||||
<mainClass>picocli.shell.jline3.example.Example</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- now make the jar chmod +x style executable -->
|
||||
<plugin>
|
||||
<groupId>org.skife.maven</groupId>
|
||||
<artifactId>really-executable-jar-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<configuration>
|
||||
<!-- value of flags will be interpolated into the java invocation -->
|
||||
<!-- as "java $flags -jar ..." -->
|
||||
<flags>-Xmx256M</flags>
|
||||
|
||||
<!-- (optional) name for binary executable, if not set will just -->
|
||||
<!-- make the regular jar artifact executable -->
|
||||
<programFile>jelegram</programFile>
|
||||
|
||||
<!-- (optional) support other packaging formats than jar -->
|
||||
<!-- <allowOtherTypes>true</allowOtherTypes> -->
|
||||
|
||||
<!-- (optional) name for a file that will define what script gets -->
|
||||
<!-- embedded into the executable jar. This can be used to -->
|
||||
<!-- override the default startup script which is -->
|
||||
<!-- `#!/bin/sh -->
|
||||
<!-- -->
|
||||
<!-- exec java " + flags + " -jar "$0" "$@" -->
|
||||
<!-- <scriptFile>src/packaging/someScript.extension</scriptFile> -->
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>really-executable-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
15
src/main/java/module-info.java
Normal file
15
src/main/java/module-info.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
module jelegram {
|
||||
requires info.picocli;
|
||||
requires org.jline;
|
||||
requires org.fusesource.jansi;
|
||||
requires tdlight.api;
|
||||
requires tdlight.java;
|
||||
requires com.google.zxing;
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.annotation;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires it.unimi.dsi.fastutil.core;
|
||||
|
||||
exports picocli.shell.jline3;
|
||||
opens picocli.shell.jline3.example;
|
||||
}
|
310
src/main/java/picocli/shell/jline3/PicocliCommands.java
Normal file
310
src/main/java/picocli/shell/jline3/PicocliCommands.java
Normal file
|
@ -0,0 +1,310 @@
|
|||
package picocli.shell.jline3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jline.builtins.Options.HelpException;
|
||||
import org.jline.console.ArgDesc;
|
||||
import org.jline.console.CmdDesc;
|
||||
import org.jline.console.CommandRegistry;
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.Completer;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.impl.completer.ArgumentCompleter;
|
||||
import org.jline.reader.impl.completer.NullCompleter;
|
||||
import org.jline.reader.impl.completer.SystemCompleter;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.InfoCmp.Capability;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Help;
|
||||
import picocli.CommandLine.IFactory;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.Model.OptionSpec;
|
||||
|
||||
/**
|
||||
* Compiles SystemCompleter for command completion and implements a method commandDescription() that provides command descriptions
|
||||
* for JLine TailTipWidgets to be displayed in terminal status bar.
|
||||
* SystemCompleter implements the JLine 3 {@link Completer} interface. SystemCompleter generates completion
|
||||
* candidates for the specified command line based on the {@link CommandSpec} that this {@code PicocliCommands} was constructed with.
|
||||
*
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class PicocliCommands implements CommandRegistry {
|
||||
|
||||
/**
|
||||
* Command that clears the screen.
|
||||
* <p>
|
||||
* <b>WARNING:</b> This subcommand needs a JLine {@code Terminal} to clear the screen.
|
||||
* To accomplish this, construct the {@code CommandLine} with a {@code PicocliCommandsFactory},
|
||||
* and set the {@code Terminal} on that factory. For example:
|
||||
* <pre>
|
||||
* @Command(subcommands = PicocliCommands.ClearScreen.class)
|
||||
* class MyApp //...
|
||||
*
|
||||
* PicocliCommandsFactory factory = new PicocliCommandsFactory();
|
||||
* CommandLine cmd = new CommandLine(new MyApp(), factory);
|
||||
* // create terminal
|
||||
* factory.setTerminal(terminal);
|
||||
* </pre>
|
||||
*
|
||||
* @since 4.6
|
||||
*/
|
||||
@Command(name = "cls", aliases = "clear", mixinStandardHelpOptions = true,
|
||||
description = "Clears the screen", version = "1.0")
|
||||
public static class ClearScreen implements Callable<Void> {
|
||||
|
||||
private final Terminal terminal;
|
||||
|
||||
ClearScreen(Terminal terminal) { this.terminal = terminal; }
|
||||
|
||||
public Void call() throws IOException {
|
||||
if (terminal != null) { terminal.puts(Capability.clear_screen); }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command factory that is necessary for applications that want the use the {@code ClearScreen} subcommand.
|
||||
* It can be chained with other factories.
|
||||
* <p>
|
||||
* <b>WARNING:</b> If the application uses the {@code ClearScreen} subcommand, construct the {@code CommandLine}
|
||||
* with a {@code PicocliCommandsFactory}, and set the {@code Terminal} on that factory. Applications need
|
||||
* to call the {@code setTerminal} method with a {@code Terminal}; this will be passed to the {@code ClearScreen}
|
||||
* subcommand.
|
||||
*
|
||||
* For example:
|
||||
* <pre>
|
||||
* PicocliCommandsFactory factory = new PicocliCommandsFactory();
|
||||
* CommandLine cmd = new CommandLine(new MyApp(), factory);
|
||||
* // create terminal
|
||||
* factory.setTerminal(terminal);
|
||||
* </pre>
|
||||
*
|
||||
* Other factories can be chained by passing them in to the constructor like this:
|
||||
* <pre>
|
||||
* MyCustomFactory customFactory = createCustomFactory(); // your application custom factory
|
||||
* PicocliCommandsFactory factory = new PicocliCommandsFactory(customFactory); // chain the factories
|
||||
* </pre>
|
||||
*
|
||||
* @since 4.6
|
||||
*/
|
||||
public static class PicocliCommandsFactory implements CommandLine.IFactory {
|
||||
private CommandLine.IFactory nextFactory;
|
||||
private Terminal terminal;
|
||||
|
||||
public PicocliCommandsFactory() {
|
||||
// nextFactory and terminal are null
|
||||
}
|
||||
|
||||
public PicocliCommandsFactory(IFactory nextFactory) {
|
||||
this.nextFactory = nextFactory;
|
||||
// nextFactory is set (but may be null) and terminal is null
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K> K create(Class<K> clazz) throws Exception {
|
||||
if (ClearScreen.class == clazz) { return (K) new ClearScreen(terminal); }
|
||||
if (nextFactory != null) { return nextFactory.create(clazz); }
|
||||
return CommandLine.defaultFactory().create(clazz);
|
||||
}
|
||||
|
||||
public void setTerminal(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
// terminal may be null, so check before using it in ClearScreen command
|
||||
}
|
||||
}
|
||||
|
||||
private final CommandLine cmd;
|
||||
private final Set<String> commands;
|
||||
private final Map<String,String> aliasCommand = new HashMap<>();
|
||||
|
||||
public PicocliCommands(CommandLine cmd) {
|
||||
this.cmd = cmd;
|
||||
commands = cmd.getCommandSpec().subcommands().keySet();
|
||||
for (String c: commands) {
|
||||
for (String a: cmd.getSubcommands().get(c).getCommandSpec().aliases()) {
|
||||
aliasCommand.put(a, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param command
|
||||
* @return true if PicocliCommands contains command
|
||||
*/
|
||||
public boolean hasCommand(String command) {
|
||||
return commands.contains(command) || aliasCommand.containsKey(command);
|
||||
}
|
||||
|
||||
|
||||
public SystemCompleter compileCompleters() {
|
||||
SystemCompleter out = new SystemCompleter();
|
||||
List<String> all = new ArrayList<>();
|
||||
all.addAll(commands);
|
||||
all.addAll(aliasCommand.keySet());
|
||||
out.add(all, new PicocliCompleter());
|
||||
return out;
|
||||
}
|
||||
|
||||
private class PicocliCompleter extends ArgumentCompleter implements Completer {
|
||||
|
||||
public PicocliCompleter() { super(NullCompleter.INSTANCE); }
|
||||
|
||||
@Override
|
||||
public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
|
||||
assert commandLine != null;
|
||||
assert candidates != null;
|
||||
String word = commandLine.word();
|
||||
List<String> words = commandLine.words();
|
||||
CommandLine sub = findSubcommandLine(words, commandLine.wordIndex());
|
||||
if (sub == null) {
|
||||
return;
|
||||
}
|
||||
if (word.startsWith("-")) {
|
||||
String buffer = word.substring(0, commandLine.wordCursor());
|
||||
int eq = buffer.indexOf('=');
|
||||
for (OptionSpec option : sub.getCommandSpec().options()) {
|
||||
if (option.arity().max() == 0 && eq < 0) {
|
||||
addCandidates(candidates, Arrays.asList(option.names()));
|
||||
} else {
|
||||
if (eq > 0) {
|
||||
String opt = buffer.substring(0, eq);
|
||||
if (Arrays.asList(option.names()).contains(opt) && option.completionCandidates() != null) {
|
||||
addCandidates(candidates, option.completionCandidates(), buffer.substring(0, eq + 1), "", true);
|
||||
}
|
||||
} else {
|
||||
addCandidates(candidates, Arrays.asList(option.names()), "", "=", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addCandidates(candidates, sub.getSubcommands().keySet());
|
||||
for (CommandLine s : sub.getSubcommands().values()) {
|
||||
addCandidates(candidates, Arrays.asList(s.getCommandSpec().aliases()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addCandidates(List<Candidate> candidates, Iterable<String> cands) {
|
||||
addCandidates(candidates, cands, "", "", true);
|
||||
}
|
||||
|
||||
private void addCandidates(List<Candidate> candidates, Iterable<String> cands, String preFix, String postFix, boolean complete) {
|
||||
for (String s : cands) {
|
||||
candidates.add(new Candidate(AttributedString.stripAnsi(preFix + s + postFix), s, null, null, null, null, complete));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private CommandLine findSubcommandLine(List<String> args, int lastIdx) {
|
||||
CommandLine out = cmd;
|
||||
for (int i = 0; i < lastIdx; i++) {
|
||||
if (!args.get(i).startsWith("-")) {
|
||||
out = findSubcommandLine(out, args.get(i));
|
||||
if (out == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private CommandLine findSubcommandLine(CommandLine cmdline, String command) {
|
||||
for (CommandLine s : cmdline.getSubcommands().values()) {
|
||||
if (s.getCommandName().equals(command) || Arrays.asList(s.getCommandSpec().aliases()).contains(command)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args
|
||||
* @return command description for JLine TailTipWidgets to be displayed in terminal status bar.
|
||||
*/
|
||||
@Override
|
||||
public CmdDesc commandDescription(List<String> args) {
|
||||
CommandLine sub = findSubcommandLine(args, args.size());
|
||||
if (sub == null) {
|
||||
return null;
|
||||
}
|
||||
CommandSpec spec = sub.getCommandSpec();
|
||||
Help cmdhelp= new picocli.CommandLine.Help(spec);
|
||||
List<AttributedString> main = new ArrayList<>();
|
||||
Map<String, List<AttributedString>> options = new HashMap<>();
|
||||
String synopsis = AttributedString.stripAnsi(spec.usageMessage().sectionMap().get("synopsis").render(cmdhelp).toString());
|
||||
main.add(HelpException.highlightSyntax(synopsis.trim(), HelpException.defaultStyle()));
|
||||
// using JLine help highlight because the statement below does not work well...
|
||||
// main.add(new AttributedString(spec.usageMessage().sectionMap().get("synopsis").render(cmdhelp).toString()));
|
||||
for (OptionSpec o : spec.options()) {
|
||||
String key = Arrays.stream(o.names()).collect(Collectors.joining(" "));
|
||||
List<AttributedString> val = new ArrayList<>();
|
||||
for (String d: o.description()) {
|
||||
val.add(new AttributedString(d));
|
||||
}
|
||||
if (o.arity().max() > 0) {
|
||||
key += "=" + o.paramLabel();
|
||||
}
|
||||
options.put(key, val);
|
||||
}
|
||||
return new CmdDesc(main, ArgDesc.doArgNames(Arrays.asList("")), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> commandInfo(String command) {
|
||||
List<String> out = new ArrayList<>();
|
||||
CommandSpec spec = cmd.getSubcommands().get(command).getCommandSpec();
|
||||
Help cmdhelp = new picocli.CommandLine.Help(spec);
|
||||
String description = AttributedString.stripAnsi(spec.usageMessage().sectionMap().get("description").render(cmdhelp).toString());
|
||||
out.addAll(Arrays.asList(description.split("\\r?\\n")));
|
||||
return out;
|
||||
}
|
||||
|
||||
// For JLine >= 3.16.0
|
||||
@Override
|
||||
public Object invoke(CommandRegistry.CommandSession session, String command, Object[] args) throws Exception {
|
||||
List<String> arguments = new ArrayList<>();
|
||||
arguments.add( command );
|
||||
arguments.addAll( Arrays.stream( args ).map( Object::toString ).collect( Collectors.toList() ) );
|
||||
cmd.execute( arguments.toArray( new String[0] ) );
|
||||
return null;
|
||||
}
|
||||
|
||||
// @Override This method was removed in JLine 3.16.0; keep it in case this component is used with an older version of JLine
|
||||
public Object execute(CommandRegistry.CommandSession session, String command, String[] args) throws Exception {
|
||||
List<String> arguments = new ArrayList<>();
|
||||
arguments.add(command);
|
||||
arguments.addAll(Arrays.asList(args));
|
||||
cmd.execute(arguments.toArray(new String[0]));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> commandNames() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> commandAliases() {
|
||||
return aliasCommand;
|
||||
}
|
||||
|
||||
// @Override This method was removed in JLine 3.16.0; keep it in case this component is used with an older version of JLine
|
||||
public CmdDesc commandDescription(String command) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package picocli.shell.jline3;
|
||||
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.Completer;
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.ParsedLine;
|
||||
import picocli.AutoComplete;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.lang.CharSequence;
|
||||
|
||||
/**
|
||||
* Implementation of the JLine 3 {@link Completer} interface that generates completion
|
||||
* candidates for the specified command line based on the {@link CommandSpec} that
|
||||
* this {@code PicocliJLineCompleter} was constructed with.
|
||||
*
|
||||
* @since 3.9
|
||||
*/
|
||||
public class PicocliJLineCompleter implements Completer {
|
||||
private final CommandSpec spec;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code PicocliJLineCompleter} for the given command spec.
|
||||
* @param spec the command specification to generate completions for. Must be non-{@code null}.
|
||||
*/
|
||||
public PicocliJLineCompleter(CommandSpec spec) {
|
||||
if (spec == null) { throw new NullPointerException("spec"); }
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates <i>candidates</i> with a list of possible completions for the <i>command line</i>.
|
||||
*
|
||||
* The list of candidates will be sorted and filtered by the LineReader, so that
|
||||
* the list of candidates displayed to the user will usually be smaller than
|
||||
* the list given by the completer. Thus it is not necessary for the completer
|
||||
* to do any matching based on the current buffer. On the contrary, in order
|
||||
* for the typo matcher to work, all possible candidates for the word being
|
||||
* completed should be returned.
|
||||
*
|
||||
* @param reader The line reader
|
||||
* @param line The parsed command line
|
||||
* @param candidates The {@link List} of candidates to populate
|
||||
*/
|
||||
//@Override
|
||||
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
|
||||
// let picocli generate completion candidates for the token where the cursor is at
|
||||
String[] words = new String[line.words().size()];
|
||||
words = line.words().toArray(words);
|
||||
List<CharSequence> cs = new ArrayList<CharSequence>();
|
||||
AutoComplete.complete(spec,
|
||||
words,
|
||||
line.wordIndex(),
|
||||
0,
|
||||
line.cursor(),
|
||||
cs);
|
||||
for(CharSequence c: cs){
|
||||
candidates.add(new Candidate((String)c));
|
||||
}
|
||||
}
|
||||
}
|
3
src/main/java/picocli/shell/jline3/example/ChatData.java
Normal file
3
src/main/java/picocli/shell/jline3/example/ChatData.java
Normal file
|
@ -0,0 +1,3 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
public class ChatData {}
|
|
@ -0,0 +1,5 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
public enum CliInputParameterValue {
|
||||
ASK_AUTH_DATA_TYPE
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
import it.tdlight.client.InputParameter;
|
||||
import it.tdlight.client.ParameterInfo;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public record ClientInteractionRequest(TDLibInstance instance, CustomInputParameter parameter, ParameterInfo parameterInfo,
|
||||
Consumer<String> result) {
|
||||
sealed interface CustomInputParameter {
|
||||
record StandardInputParameter(InputParameter value) implements CustomInputParameter {}
|
||||
record CliInputParameter(CliInputParameterValue value) implements CustomInputParameter {}
|
||||
}
|
||||
}
|
738
src/main/java/picocli/shell/jline3/example/Example.java
Normal file
738
src/main/java/picocli/shell/jline3/example/Example.java
Normal file
|
@ -0,0 +1,738 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
import it.tdlight.client.APIToken;
|
||||
import it.tdlight.client.AuthenticationData;
|
||||
import it.tdlight.client.ParameterInfoCode;
|
||||
import it.tdlight.client.ParameterInfoNotifyLink;
|
||||
import it.tdlight.client.ParameterInfoPasswordHint;
|
||||
import it.tdlight.client.ParameterInfoTermsOfService;
|
||||
import it.tdlight.common.internal.InternalClientManager;
|
||||
import it.tdlight.common.utils.CantLoadLibrary;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.ChatList;
|
||||
import it.tdlight.jni.TdApi.ChatListArchive;
|
||||
import it.tdlight.jni.TdApi.ChatListMain;
|
||||
import it.tdlight.jni.TdApi.Chats;
|
||||
import it.tdlight.jni.TdApi.GetChat;
|
||||
import it.tdlight.jni.TdApi.LoadChats;
|
||||
import it.tdlight.jni.TdApi.Ok;
|
||||
import it.tdlight.jni.TdApi.TermsOfService;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
import org.jline.console.SystemRegistry;
|
||||
import org.jline.console.impl.Builtins;
|
||||
import org.jline.console.impl.SystemRegistryImpl;
|
||||
import org.jline.keymap.KeyMap;
|
||||
import org.jline.reader.Binding;
|
||||
import org.jline.reader.EndOfFileException;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.MaskingCallback;
|
||||
import org.jline.reader.Parser;
|
||||
import org.jline.reader.Reference;
|
||||
import org.jline.reader.UserInterruptException;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
import org.jline.widget.TailTipWidgets;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.ArgGroup;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
import picocli.CommandLine.ParentCommand;
|
||||
import picocli.shell.jline3.PicocliCommands;
|
||||
import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
|
||||
import picocli.shell.jline3.example.ClientInteractionRequest.CustomInputParameter.CliInputParameter;
|
||||
import picocli.shell.jline3.example.ClientInteractionRequest.CustomInputParameter.StandardInputParameter;
|
||||
|
||||
/**
|
||||
* Example that demonstrates how to build an interactive shell with JLine3 and picocli.
|
||||
* This example requires JLine 3.16+ and picocli 4.4+.
|
||||
* <p>
|
||||
* The built-in {@code PicocliCommands.ClearScreen} command was introduced in picocli 4.6.
|
||||
* </p>
|
||||
*/
|
||||
public class Example {
|
||||
|
||||
static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static String currentInstance = null;
|
||||
static boolean waitingLogin = false;
|
||||
static Map<String, TDLibInstance> instanceMap = new HashMap<>();
|
||||
static final LinkedBlockingDeque<ClientInteractionRequest> interactionRequests = new LinkedBlockingDeque<>();
|
||||
|
||||
private static TDLibInstance getInstance() {
|
||||
var ci = currentInstance;
|
||||
return Objects.requireNonNull(instanceMap.get(ci), "No instance is selected.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level command that just prints help.
|
||||
*/
|
||||
@Command(name = "",
|
||||
description = {
|
||||
"Example interactive shell with completion and autosuggestions. " +
|
||||
"Hit @|magenta <TAB>|@ to see available commands.",
|
||||
"Hit @|magenta ALT-S|@ to toggle tailtips.",
|
||||
""},
|
||||
footer = {"", "Press Ctrl-D to exit."},
|
||||
subcommands = {InstanceCommand.class, ChatsCommand.class,
|
||||
ChatCommand.class, PicocliCommands.ClearScreen.class,
|
||||
CommandLine.HelpCommand.class})
|
||||
static class CliCommands implements Runnable {
|
||||
PrintWriter out;
|
||||
|
||||
CliCommands() {}
|
||||
|
||||
public void setReader(LineReader reader){
|
||||
out = reader.getTerminal().writer();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
out.println(new CommandLine(this).getUsageMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "instance", mixinStandardHelpOptions = true, version = "1.0",
|
||||
description = {"Instance-related commands."},
|
||||
subcommands = {ConfigureCommand.class, StartCommand.class, StopCommand.class, CommandLine.HelpCommand.class})
|
||||
static class InstanceCommand implements Runnable {
|
||||
|
||||
private static final APIToken API_TOKEN = APIToken.example();
|
||||
@Option(names = {"-v", "--verbose"},
|
||||
description = { "Specify multiple -v options to increase verbosity.",
|
||||
"For example, `-v -v -v` or `-vvv`"})
|
||||
private boolean[] verbosity = {};
|
||||
|
||||
@ParentCommand CliCommands parent;
|
||||
|
||||
public void run() {
|
||||
if (currentInstance == null) {
|
||||
System.out.println("No instance is active.");
|
||||
return;
|
||||
}
|
||||
System.out.printf("The current instance is %s.%n", currentInstance);
|
||||
if (verbosity.length > 0) {
|
||||
System.out.println("Instances:");
|
||||
for (String instanceName : instanceMap.keySet()) {
|
||||
System.out.printf("\t- %s%n", instanceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Command(mixinStandardHelpOptions = true, description = "Add an instance.")
|
||||
public void add(@Parameters(paramLabel = "<name>", description = "Instance name.", arity = "1") String name)
|
||||
throws CantLoadLibrary {
|
||||
if (instanceMap.containsKey(name)) {
|
||||
System.err.printf("Instance %s already exists%n", name);
|
||||
return;
|
||||
}
|
||||
currentInstance = name;
|
||||
instanceMap.put(name, new TDLibInstance(API_TOKEN, name, interactionRequests));
|
||||
}
|
||||
|
||||
@Command(mixinStandardHelpOptions = true, description = "Load an instance from disk.")
|
||||
public void load(@Parameters(paramLabel = "<path>", description = "Instance path.", arity = "1") Path path)
|
||||
throws CantLoadLibrary, IOException {
|
||||
var settingsFilePath = path.resolve("settings.json");
|
||||
if (Files.notExists(settingsFilePath)) {
|
||||
System.err.printf("The following path does not exist: %s%n", settingsFilePath);
|
||||
return;
|
||||
}
|
||||
if (!Files.isRegularFile(settingsFilePath)) {
|
||||
System.err.printf("The following path is not valid: %s%n", settingsFilePath);
|
||||
return;
|
||||
}
|
||||
if (!Files.isReadable(settingsFilePath)) {
|
||||
System.err.printf("The following path is not readable: %s%n", settingsFilePath);
|
||||
return;
|
||||
}
|
||||
var settings = mapper.readValue(Files.readString(settingsFilePath, StandardCharsets.UTF_8), InstanceSettings.class);
|
||||
var name = settings.name;
|
||||
if (instanceMap.containsKey(name)) {
|
||||
System.err.printf("Instance %s already exists%n", name);
|
||||
return;
|
||||
}
|
||||
TDLibInstance instance = new TDLibInstance(API_TOKEN, settings, interactionRequests);
|
||||
currentInstance = name;
|
||||
instanceMap.put(name, instance);
|
||||
System.out.println("Client is loading...");
|
||||
waitingLogin = true;
|
||||
instance.start(settings.toAuthData());
|
||||
}
|
||||
|
||||
private void askAuthDataType(TDLibInstance instance) {
|
||||
interactionRequests.offer(new ClientInteractionRequest(instance,
|
||||
new CliInputParameter(CliInputParameterValue.ASK_AUTH_DATA_TYPE),
|
||||
null,
|
||||
result -> {
|
||||
AuthenticationData authenticationData;
|
||||
if (result.startsWith("phone ")) {
|
||||
authenticationData = AuthenticationData.user(Long.parseLong(result
|
||||
.substring("phone ".length())
|
||||
.replaceAll("[^\\d.]", "")));
|
||||
} else if (result.startsWith("token ")) {
|
||||
authenticationData = AuthenticationData.bot(result.substring("token ".length()));
|
||||
} else if (result.equals("qr")) {
|
||||
authenticationData = AuthenticationData.qrCode();
|
||||
} else {
|
||||
System.err.println("Invalid auth data type, please, enter a correctly formatted response.");
|
||||
askAuthDataType(instance);
|
||||
return;
|
||||
}
|
||||
instance.start(authenticationData);
|
||||
System.out.println("Client loaded.");
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
@Command(mixinStandardHelpOptions = true, description = "Select an instance.")
|
||||
public void select(@Parameters(paramLabel = "<name>", description = "Instance name.", arity = "1") String name) {
|
||||
if (!instanceMap.containsKey(name)) {
|
||||
System.err.printf("Instance %s does not exist%n", name);
|
||||
return;
|
||||
}
|
||||
currentInstance = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "chats", mixinStandardHelpOptions = true, version = "1.0",
|
||||
description = {"Chats-related commands."},
|
||||
subcommands = {CommandLine.HelpCommand.class})
|
||||
static class ChatsCommand implements Runnable {
|
||||
|
||||
@Option(names = {"-v", "--verbose"},
|
||||
description = { "Specify multiple -v options to increase verbosity.",
|
||||
"For example, `-v -v -v` or `-vvv`"})
|
||||
private boolean[] verbosity = {};
|
||||
|
||||
@Option(names = {"-l", "--list"},
|
||||
description = { "Chats list. Can be \"main\" or \"archive\""})
|
||||
private String chatsList = "main";
|
||||
|
||||
@Option(names = {"-s", "--skip"},
|
||||
description = { "Skip the following chats."})
|
||||
private int skipCount = 0;
|
||||
|
||||
@Option(names = {"-c", "--count"},
|
||||
description = { "Show n chats."})
|
||||
private int count = 100;
|
||||
|
||||
@ParentCommand CliCommands parent;
|
||||
|
||||
public void run() {
|
||||
if (currentInstance == null) {
|
||||
System.out.println("No instance is active.");
|
||||
return;
|
||||
}
|
||||
var i = getInstance();
|
||||
ChatList chatList;
|
||||
if (chatsList.equals("main")) {
|
||||
chatList = new ChatListMain();
|
||||
} else if (chatsList.equals("archive")) {
|
||||
chatList = new ChatListArchive();
|
||||
} else {
|
||||
System.err.println("Invalid chat list: " + chatsList);
|
||||
return;
|
||||
}
|
||||
var chats = getChatsTo(i, chatList, skipCount + count);
|
||||
System.out.printf("Chat list %s has %d chats. (shown from %d to %d)%n",
|
||||
chatsList,
|
||||
chats.totalCount,
|
||||
Math.min(skipCount, chats.totalCount),
|
||||
Math.min(skipCount + count, chats.totalCount)
|
||||
);
|
||||
long[] chatIds = chats.chatIds;
|
||||
for (int j = skipCount; j < chatIds.length; j++) {
|
||||
long chatId = chatIds[j];
|
||||
var chat = i.send(new GetChat(chatId)).join();
|
||||
if (verbosity.length == 0) {
|
||||
System.out.printf("\t- %s%n", chat.title);
|
||||
} else {
|
||||
System.out.printf("\t- [%d] %s%n", chat.id, chat.title);
|
||||
}
|
||||
}
|
||||
var more = Math.max(0, chats.totalCount - (skipCount + count));
|
||||
if (more > 0) {
|
||||
System.out.printf(" ... and %d more, use the argument --skip=%d to see them.%n", more, skipCount + count);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getIsBot(TDLibInstance i) {
|
||||
return i.getAuthenticationData().isBot();
|
||||
}
|
||||
|
||||
private Chats getChatsTo(TDLibInstance instance, ChatList chatList, int max) {
|
||||
var bot = getIsBot(instance);
|
||||
if (bot && chatList.getConstructor() != ChatListMain.CONSTRUCTOR) {
|
||||
return new Chats(0, new long[0]);
|
||||
}
|
||||
if (bot) {
|
||||
var chats = instance.getChats();
|
||||
var array = new long[chats.size()];
|
||||
var result = new Chats(chats.size(), array);
|
||||
int i = 0;
|
||||
for (Long chat : chats) {
|
||||
array[i++] = chat;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int next = 0;
|
||||
while (next < max) {
|
||||
next += 100;
|
||||
instance.send(new LoadChats(chatList, next)).exceptionallyCompose(ex -> {
|
||||
if (ex instanceof TDException ex2 && ex2.code == 404) {
|
||||
return CompletableFuture.completedFuture(new Ok());
|
||||
} else {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
}).join();
|
||||
}
|
||||
return instance.send(new TdApi.GetChats(chatList, max)).join();
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "chat", mixinStandardHelpOptions = true, version = "1.0",
|
||||
description = {"Chat-related commands."},
|
||||
subcommands = {CommandLine.HelpCommand.class})
|
||||
static class ChatCommand implements Runnable {
|
||||
|
||||
@Option(names = {"-v", "--verbose"},
|
||||
description = { "Specify multiple -v options to increase verbosity.",
|
||||
"For example, `-v -v -v` or `-vvv`"})
|
||||
private boolean[] verbosity = {};
|
||||
|
||||
@Parameters(paramLabel = "id", description = { "Chat id"}, arity = "1")
|
||||
private long id;
|
||||
|
||||
@ParentCommand CliCommands parent;
|
||||
|
||||
public void run() {
|
||||
if (currentInstance == null) {
|
||||
System.out.println("No instance is active.");
|
||||
return;
|
||||
}
|
||||
var i = getInstance();
|
||||
var chat = i.send(new TdApi.GetChat(id)).join();
|
||||
System.out.printf("Chat %s [%d]%n", chat.title, chat.id);
|
||||
var lastMessage = chat.lastMessage;
|
||||
if (lastMessage != null) {
|
||||
System.out.printf("Last message:%n");
|
||||
var sender = i.getSender(lastMessage.senderId).join();
|
||||
System.out.printf("\tSender: %s [%d]%n", sender.title, sender.id);
|
||||
var text = i.contentToString(lastMessage.content).join();
|
||||
System.out.printf("\t%s%n", text);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getIsBot(TDLibInstance i) {
|
||||
return i.getAuthenticationData().isBot();
|
||||
}
|
||||
|
||||
private Chats getChatsTo(TDLibInstance instance, ChatList chatList, int max) {
|
||||
var bot = getIsBot(instance);
|
||||
if (bot && chatList.getConstructor() != ChatListMain.CONSTRUCTOR) {
|
||||
return new Chats(0, new long[0]);
|
||||
}
|
||||
if (bot) {
|
||||
var chats = instance.getChats();
|
||||
var array = new long[chats.size()];
|
||||
var result = new Chats(chats.size(), array);
|
||||
int i = 0;
|
||||
for (Long chat : chats) {
|
||||
array[i++] = chat;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int next = 0;
|
||||
while (next < max) {
|
||||
next += 100;
|
||||
instance.send(new LoadChats(chatList, next)).exceptionallyCompose(ex -> {
|
||||
if (ex instanceof TDException ex2 && ex2.code == 404) {
|
||||
return CompletableFuture.completedFuture(new Ok());
|
||||
} else {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
}).join();
|
||||
}
|
||||
return instance.send(new TdApi.GetChats(chatList, max)).join();
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "configure", mixinStandardHelpOptions = true, version = "1.0",
|
||||
description = {"Instance-related configuration command."})
|
||||
static class ConfigureCommand implements Runnable {
|
||||
@Option(names = {"-p", "--path"},
|
||||
description = { "Specify the path."})
|
||||
private Path path;
|
||||
@Option(names = {"--use-messages-db"},
|
||||
description = { "Use the messages database."}, negatable = true)
|
||||
private Boolean useMessagesDb;
|
||||
@Option(names = {"--use-files-db"},
|
||||
description = { "Use the files database."}, negatable = true)
|
||||
private Boolean useFilesDb;
|
||||
@Option(names = {"--use-chat-info-db"},
|
||||
description = { "Use the chats database."}, negatable = true)
|
||||
private Boolean useChatInfoDb;
|
||||
@Option(names = {"-t", "--use-test-dc"},
|
||||
description = { "Use the chats database."}, negatable = true)
|
||||
private Boolean useTestDc;
|
||||
|
||||
@ParentCommand
|
||||
InstanceCommand parent;
|
||||
|
||||
public void run() {
|
||||
if (currentInstance == null) {
|
||||
System.out.println("No instance is active.");
|
||||
return;
|
||||
}
|
||||
var instance = getInstance();
|
||||
var settings = instance.getSettings();
|
||||
if (path != null) {
|
||||
instance.setPath(path);
|
||||
System.out.printf("Path set to \"%s\".%n", path);
|
||||
}
|
||||
if (useMessagesDb != null) {
|
||||
settings.setMessageDatabaseEnabled(useMessagesDb);
|
||||
System.out.printf("Messages database set to: %b.%n", useMessagesDb);
|
||||
}
|
||||
if (useFilesDb != null) {
|
||||
settings.setFileDatabaseEnabled(useFilesDb);
|
||||
System.out.printf("Files database set to: %b.%n", useFilesDb);
|
||||
}
|
||||
if (useChatInfoDb != null) {
|
||||
settings.setChatInfoDatabaseEnabled(useChatInfoDb);
|
||||
System.out.printf("Chat info database set to: %b.%n", useChatInfoDb);
|
||||
}
|
||||
if (useTestDc != null) {
|
||||
settings.setUseTestDatacenter(useTestDc);
|
||||
System.out.printf("Test datacenter set to: %b.%n", useTestDc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "start", mixinStandardHelpOptions = true, version = "1.0", description = {"Start the instance."})
|
||||
static class StartCommand implements Runnable {
|
||||
|
||||
@ParentCommand InstanceCommand parent;
|
||||
|
||||
@ArgGroup(exclusive = true, multiplicity = "1")
|
||||
private StartCommand.AuthMode authMode = new StartCommand.AuthMode();
|
||||
|
||||
static class AuthMode {
|
||||
@Option(names = {"-q", "--qr"},
|
||||
description = "QR-code login.")
|
||||
private boolean qrLogin;
|
||||
@Option(names = {"-t", "--token"},
|
||||
description = "Bot token login.")
|
||||
private String botLogin;
|
||||
@Option(names = {"-p", "--phone-number"},
|
||||
description = "User phone number login.")
|
||||
private String phoneNumberLogin;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (currentInstance == null) {
|
||||
System.out.println("No instance is active.");
|
||||
return;
|
||||
}
|
||||
var instance = getInstance();
|
||||
var path = instance.getPath();
|
||||
if (Files.exists(path)) {
|
||||
System.err.printf("Instance path already exists: %s%n", path);
|
||||
return;
|
||||
}
|
||||
AuthenticationData authenticationData;
|
||||
if (authMode.qrLogin) {
|
||||
authenticationData = AuthenticationData.qrCode();
|
||||
} else if (authMode.botLogin != null) {
|
||||
authenticationData = AuthenticationData.bot(authMode.botLogin);
|
||||
} else if (authMode.phoneNumberLogin != null) {
|
||||
var phone = Long.parseLong(authMode.phoneNumberLogin.replaceAll("[^\\d.]", ""));
|
||||
authenticationData = AuthenticationData.user(phone);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unknown choice");
|
||||
}
|
||||
waitingLogin = true;
|
||||
instance.start(authenticationData);
|
||||
System.out.println("Client started.");
|
||||
}
|
||||
}
|
||||
|
||||
@Command(name = "stop", mixinStandardHelpOptions = true, version = "1.0", description = {"Stop the instance."})
|
||||
static class StopCommand implements Runnable {
|
||||
|
||||
@ParentCommand InstanceCommand parent;
|
||||
|
||||
public void run() {
|
||||
if (currentInstance == null) {
|
||||
System.out.println("No instance is active.");
|
||||
return;
|
||||
}
|
||||
var instance = getInstance();
|
||||
System.out.println("Stopping client...");
|
||||
try {
|
||||
instance.stop();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Failed to close", e);
|
||||
}
|
||||
System.out.println("Client stopped.");
|
||||
instanceMap.remove(currentInstance);
|
||||
currentInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
AnsiConsole.systemInstall();
|
||||
try {
|
||||
Supplier<Path> workDir = () -> Paths.get(System.getProperty("user.dir"));
|
||||
// set up JLine built-in commands
|
||||
Builtins builtins = new Builtins(workDir, null, null);
|
||||
builtins.rename(Builtins.Command.TTOP, "top");
|
||||
builtins.alias("zle", "widget");
|
||||
builtins.alias("bindkey", "keymap");
|
||||
// set up picocli commands
|
||||
CliCommands commands = new CliCommands();
|
||||
|
||||
PicocliCommandsFactory factory = new PicocliCommandsFactory();
|
||||
// Or, if you have your own factory, you can chain them like this:
|
||||
// MyCustomFactory customFactory = createCustomFactory(); // your application custom factory
|
||||
// PicocliCommandsFactory factory = new PicocliCommandsFactory(customFactory); // chain the factories
|
||||
|
||||
CommandLine cmd = new CommandLine(commands, factory);
|
||||
PicocliCommands picocliCommands = new PicocliCommands(cmd);
|
||||
|
||||
Parser parser = new DefaultParser();
|
||||
try (Terminal terminal = TerminalBuilder.builder().build()) {
|
||||
SystemRegistry systemRegistry = new SystemRegistryImpl(parser, terminal, workDir, null);
|
||||
systemRegistry.setCommandRegistries(builtins, picocliCommands);
|
||||
systemRegistry.register("help", picocliCommands);
|
||||
|
||||
LineReader reader = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(systemRegistry.completer())
|
||||
.parser(parser)
|
||||
.variable(LineReader.LIST_MAX, 50) // max tab completion candidates
|
||||
.build();
|
||||
builtins.setLineReader(reader);
|
||||
commands.setReader(reader);
|
||||
factory.setTerminal(terminal);
|
||||
TailTipWidgets widgets = new TailTipWidgets(reader,
|
||||
systemRegistry::commandDescription,
|
||||
5,
|
||||
TailTipWidgets.TipType.COMPLETER
|
||||
);
|
||||
widgets.enable();
|
||||
KeyMap<Binding> keyMap = reader.getKeyMaps().get("main");
|
||||
keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s"));
|
||||
|
||||
String prompt;
|
||||
String rightPrompt = null;
|
||||
|
||||
// start the shell and process input until the user quits with Ctrl-D
|
||||
String line;
|
||||
shell:
|
||||
while (true) {
|
||||
try {
|
||||
systemRegistry.cleanUp();
|
||||
if (currentInstance != null) {
|
||||
prompt = "%s> ".formatted(currentInstance);
|
||||
} else {
|
||||
prompt = "jelegram> ";
|
||||
}
|
||||
ClientInteractionRequest lastIr;
|
||||
if (waitingLogin) {
|
||||
lastIr = interactionRequests.poll(100, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
lastIr = interactionRequests.poll();
|
||||
}
|
||||
if (lastIr != null) {
|
||||
var requestAndResponse = getInteractionRequest(lastIr);
|
||||
prompt = requestAndResponse.getKey().orElse(null);
|
||||
if (requestAndResponse.getValue().isPresent()) {
|
||||
System.out.println(prompt);
|
||||
lastIr.result().accept(requestAndResponse.getValue().get());
|
||||
} else {
|
||||
if (prompt != null) {
|
||||
prompt += "\n> ";
|
||||
}
|
||||
line = reader.readLine(prompt, null, (MaskingCallback) null, null);
|
||||
lastIr.result().accept(line);
|
||||
}
|
||||
} else if (!waitingLogin) {
|
||||
line = reader.readLine(prompt, null, (MaskingCallback) null, null);
|
||||
systemRegistry.execute(line);
|
||||
}
|
||||
} catch (UserInterruptException e) {
|
||||
// Ignore
|
||||
} catch (EndOfFileException e) {
|
||||
break shell;
|
||||
} catch (Exception e) {
|
||||
systemRegistry.trace(e);
|
||||
}
|
||||
}
|
||||
for (var entry : new HashSet<>(instanceMap.entrySet())) {
|
||||
try {
|
||||
entry.getValue().stop();
|
||||
} catch (Exception ex) {
|
||||
System.out.printf("Failed to close instance %s%n", entry.getKey());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
InternalClientManager.get("tdlight").close();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
AnsiConsole.systemUninstall();
|
||||
}
|
||||
}
|
||||
|
||||
private static Entry<Optional<String>, Optional<String>> getInteractionRequest(ClientInteractionRequest interactionRequest) {
|
||||
var customParameter = interactionRequest.parameter();
|
||||
var parameterInfo = interactionRequest.parameterInfo();
|
||||
var authenticationData = interactionRequest.instance().getAuthenticationData();
|
||||
String who;
|
||||
if (authenticationData.isQrCode()) {
|
||||
who = "QR login";
|
||||
} else if (authenticationData.isBot()) {
|
||||
who = authenticationData.getBotToken().split(":", 2)[0];
|
||||
} else {
|
||||
who = "+" + authenticationData.getUserPhoneNumber();
|
||||
}
|
||||
String question;
|
||||
boolean trim = false;
|
||||
if (customParameter instanceof StandardInputParameter inputParameter) {
|
||||
var parameter = inputParameter.value();
|
||||
switch (parameter) {
|
||||
case ASK_FIRST_NAME:
|
||||
question = "Enter first name";
|
||||
trim = true;
|
||||
break;
|
||||
case ASK_LAST_NAME:
|
||||
question = "Enter last name";
|
||||
trim = true;
|
||||
break;
|
||||
case ASK_CODE:
|
||||
question = "Enter authentication code";
|
||||
ParameterInfoCode codeInfo = ((ParameterInfoCode) parameterInfo);
|
||||
question += "\n\tPhone number: " + codeInfo.getPhoneNumber();
|
||||
question += "\n\tTimeout: " + codeInfo.getTimeout() + " seconds";
|
||||
question += "\n\tCode type: " + codeInfo.getType().getClass().getSimpleName()
|
||||
.replace("AuthenticationCodeType", "");
|
||||
if (codeInfo.getNextType() != null) {
|
||||
question += "\n\tNext code type: " + codeInfo
|
||||
.getNextType()
|
||||
.getClass()
|
||||
.getSimpleName()
|
||||
.replace("AuthenticationCodeType", "");
|
||||
}
|
||||
trim = true;
|
||||
break;
|
||||
case ASK_PASSWORD:
|
||||
question = "Enter your password";
|
||||
String passwordMessage = "Password authorization:";
|
||||
String hint = ((ParameterInfoPasswordHint) parameterInfo).getHint();
|
||||
if (hint != null && !hint.isEmpty()) {
|
||||
passwordMessage += "\n\tHint: " + hint;
|
||||
}
|
||||
boolean hasRecoveryEmailAddress = ((ParameterInfoPasswordHint) parameterInfo)
|
||||
.hasRecoveryEmailAddress();
|
||||
passwordMessage += "\n\tHas recovery email: " + hasRecoveryEmailAddress;
|
||||
String recoveryEmailAddressPattern = ((ParameterInfoPasswordHint) parameterInfo)
|
||||
.getRecoveryEmailAddressPattern();
|
||||
if (recoveryEmailAddressPattern != null && !recoveryEmailAddressPattern.isEmpty()) {
|
||||
passwordMessage += "\n\tRecovery email address pattern: " + recoveryEmailAddressPattern;
|
||||
}
|
||||
System.out.println(passwordMessage);
|
||||
break;
|
||||
case NOTIFY_LINK:
|
||||
String link = ((ParameterInfoNotifyLink) parameterInfo).getLink();
|
||||
var sb = new StringBuilder();
|
||||
sb.append("Please confirm this login link on another device: " + link + "\n");
|
||||
sb.append("\n");
|
||||
sb.append(getQr(link) + "\n");
|
||||
sb.append("\n");
|
||||
return Map.entry(Optional.of("[" + who + "] " + sb), Optional.of(""));
|
||||
case TERMS_OF_SERVICE:
|
||||
TermsOfService tos = ((ParameterInfoTermsOfService) parameterInfo).getTermsOfService();
|
||||
question = "Terms of service:\n\t" + tos.text.text;
|
||||
if (tos.minUserAge > 0) {
|
||||
question += "\n\tMinimum user age: " + tos.minUserAge;
|
||||
}
|
||||
if (tos.showPopup) {
|
||||
question += "\nPlease press enter.";
|
||||
trim = true;
|
||||
} else {
|
||||
return Map.entry(Optional.of("[" + who + "] " + question), Optional.of(""));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
question = parameter.toString();
|
||||
break;
|
||||
}
|
||||
} else if (customParameter instanceof CliInputParameter inputParameter) {
|
||||
question = switch (inputParameter.value()) {
|
||||
case ASK_AUTH_DATA_TYPE -> "Please type the preferred auth mode [qr | token <token> | phone <phone number>]";
|
||||
default -> inputParameter.value().toString();
|
||||
};
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid custom parameter type: " + customParameter.toString());
|
||||
}
|
||||
return Map.entry(Optional.of("[" + who + "] " + question), Optional.empty());
|
||||
}
|
||||
|
||||
private static String getQr(String url) {
|
||||
int width = 40;
|
||||
int height = 40;
|
||||
Hashtable<EncodeHintType, Object> qrParam = new Hashtable<>();
|
||||
qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
|
||||
qrParam.put(EncodeHintType.CHARACTER_SET, "utf-8");
|
||||
try {
|
||||
BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, width, height, qrParam);
|
||||
return toAscii(bitMatrix);
|
||||
} catch (WriterException ex) {
|
||||
throw new IllegalStateException("Can't encode QR code", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String toAscii(BitMatrix bitMatrix) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int rows = 0; rows < bitMatrix.getHeight(); rows++) {
|
||||
for (int cols = 0; cols < bitMatrix.getWidth(); cols++) {
|
||||
boolean x = bitMatrix.get(rows, cols);
|
||||
sb.append(x ? " " : "██");
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
127
src/main/java/picocli/shell/jline3/example/InstanceSettings.java
Normal file
127
src/main/java/picocli/shell/jline3/example/InstanceSettings.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import it.tdlight.client.APIToken;
|
||||
import it.tdlight.client.AuthenticationData;
|
||||
import it.tdlight.client.TDLibSettings;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class InstanceSettings {
|
||||
|
||||
public boolean useTestDatacenter;
|
||||
public String token;
|
||||
public Long phoneNumber;
|
||||
public Path path;
|
||||
public boolean fileDatabaseEnabled;
|
||||
public boolean chatInfoDatabaseEnabled;
|
||||
public boolean messageDatabaseEnabled;
|
||||
public String systemLanguageCode;
|
||||
public String deviceModel;
|
||||
public String systemVersion;
|
||||
public String applicationVersion;
|
||||
public boolean enableStorageOptimizer;
|
||||
public boolean ignoreFileNames;
|
||||
public String name;
|
||||
|
||||
public InstanceSettings(String name, Path path, TDLibSettings settings, AuthenticationData authenticationData) {
|
||||
this.name = name;
|
||||
this.useTestDatacenter = settings.isUsingTestDatacenter();
|
||||
if (authenticationData.isBot()) {
|
||||
this.token = authenticationData.getBotToken();
|
||||
} else if (!authenticationData.isQrCode()) {
|
||||
this.phoneNumber = authenticationData.getUserPhoneNumber();
|
||||
}
|
||||
this.path = path;
|
||||
this.fileDatabaseEnabled = settings.isFileDatabaseEnabled();
|
||||
this.chatInfoDatabaseEnabled = settings.isChatInfoDatabaseEnabled();
|
||||
this.messageDatabaseEnabled = settings.isMessageDatabaseEnabled();
|
||||
this.systemLanguageCode = settings.getSystemLanguageCode();
|
||||
this.deviceModel = settings.getDeviceModel();
|
||||
this.systemVersion = settings.getSystemVersion();
|
||||
this.applicationVersion = settings.getApplicationVersion();
|
||||
this.enableStorageOptimizer = settings.isStorageOptimizerEnabled();
|
||||
this.ignoreFileNames = settings.isIgnoreFileNames();
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public InstanceSettings(@JsonProperty("useTestDatacenter") boolean useTestDatacenter,
|
||||
@JsonProperty("token") String token,
|
||||
@JsonProperty("phoneNumber") Long phoneNumber,
|
||||
@JsonProperty("path") Path path,
|
||||
@JsonProperty("fileDatabaseEnabled") boolean fileDatabaseEnabled,
|
||||
@JsonProperty("chatInfoDatabaseEnabled") boolean chatInfoDatabaseEnabled,
|
||||
@JsonProperty("messageDatabaseEnabled") boolean messageDatabaseEnabled,
|
||||
@JsonProperty("systemLanguageCode") String systemLanguageCode,
|
||||
@JsonProperty("deviceModel") String deviceModel,
|
||||
@JsonProperty("systemVersion") String systemVersion,
|
||||
@JsonProperty("applicationVersion") String applicationVersion,
|
||||
@JsonProperty("enableStorageOptimizer") boolean enableStorageOptimizer,
|
||||
@JsonProperty("ignoreFileNames") boolean ignoreFileNames,
|
||||
@JsonProperty("name") String name) {
|
||||
this.name = name;
|
||||
this.useTestDatacenter = useTestDatacenter;
|
||||
this.token = token;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.path = path;
|
||||
this.fileDatabaseEnabled = fileDatabaseEnabled;
|
||||
this.chatInfoDatabaseEnabled = chatInfoDatabaseEnabled;
|
||||
this.messageDatabaseEnabled = messageDatabaseEnabled;
|
||||
this.systemLanguageCode = systemLanguageCode;
|
||||
this.deviceModel = deviceModel;
|
||||
this.systemVersion = systemVersion;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.enableStorageOptimizer = enableStorageOptimizer;
|
||||
this.ignoreFileNames = ignoreFileNames;
|
||||
}
|
||||
|
||||
public static Path getDatabaseDirectoryPath(Path basePath) {
|
||||
return basePath.resolve("data");
|
||||
}
|
||||
|
||||
public static Path getDownloadsDirectoryPath(Path basePath) {
|
||||
return basePath.resolve("downloads");
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Path getDatabaseDirectoryPath() {
|
||||
return getDatabaseDirectoryPath(path);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Path getDownloadsDirectoryPath() {
|
||||
return getDownloadsDirectoryPath(path);
|
||||
}
|
||||
|
||||
public static void setTdPaths(TDLibSettings settings, Path path) {
|
||||
settings.setDatabaseDirectoryPath(getDatabaseDirectoryPath(path));
|
||||
settings.setDownloadedFilesDirectoryPath(getDownloadsDirectoryPath(path));
|
||||
}
|
||||
|
||||
public TDLibSettings toTDLibSettings(APIToken apiToken) {
|
||||
var settings = TDLibSettings.create(apiToken);
|
||||
settings.setUseTestDatacenter(useTestDatacenter);
|
||||
setTdPaths(settings, path);
|
||||
settings.setFileDatabaseEnabled(fileDatabaseEnabled);
|
||||
settings.setChatInfoDatabaseEnabled(chatInfoDatabaseEnabled);
|
||||
settings.setMessageDatabaseEnabled(messageDatabaseEnabled);
|
||||
settings.setSystemLanguageCode(systemLanguageCode);
|
||||
settings.setDeviceModel(deviceModel);
|
||||
settings.setSystemVersion(systemVersion);
|
||||
settings.setApplicationVersion(applicationVersion);
|
||||
settings.setEnableStorageOptimizer(enableStorageOptimizer);
|
||||
settings.setIgnoreFileNames(ignoreFileNames);
|
||||
return settings;
|
||||
}
|
||||
|
||||
public AuthenticationData toAuthData() {
|
||||
if (token != null) {
|
||||
return AuthenticationData.bot(token);
|
||||
} else if (phoneNumber != null) {
|
||||
return AuthenticationData.user(phoneNumber);
|
||||
} else {
|
||||
return AuthenticationData.qrCode();
|
||||
}
|
||||
}
|
||||
}
|
13
src/main/java/picocli/shell/jline3/example/TDException.java
Normal file
13
src/main/java/picocli/shell/jline3/example/TDException.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
import it.tdlight.jni.TdApi.Error;
|
||||
|
||||
public class TDException extends Exception {
|
||||
|
||||
public final int code;
|
||||
|
||||
public TDException(Error error) {
|
||||
super(error.code + ": " + error.message);
|
||||
this.code = error.code;
|
||||
}
|
||||
}
|
170
src/main/java/picocli/shell/jline3/example/TDLibInstance.java
Normal file
170
src/main/java/picocli/shell/jline3/example/TDLibInstance.java
Normal file
|
@ -0,0 +1,170 @@
|
|||
package picocli.shell.jline3.example;
|
||||
|
||||
import it.tdlight.client.APIToken;
|
||||
import it.tdlight.client.AuthenticationData;
|
||||
import it.tdlight.client.InputParameter;
|
||||
import it.tdlight.client.ParameterInfo;
|
||||
import it.tdlight.client.SimpleTelegramClient;
|
||||
import it.tdlight.client.TDLibSettings;
|
||||
import it.tdlight.common.Init;
|
||||
import it.tdlight.common.utils.CantLoadLibrary;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateClosing;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateLoggingOut;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateReady;
|
||||
import it.tdlight.jni.TdApi.Chat;
|
||||
import it.tdlight.jni.TdApi.Function;
|
||||
import it.tdlight.jni.TdApi.MessageContent;
|
||||
import it.tdlight.jni.TdApi.MessageSender;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Collection;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import picocli.shell.jline3.example.ClientInteractionRequest.CustomInputParameter.StandardInputParameter;
|
||||
|
||||
public class TDLibInstance {
|
||||
|
||||
private final String name;
|
||||
private Path path;
|
||||
private final TDLibSettings settings;
|
||||
private final Queue<ClientInteractionRequest> interactionRequests;
|
||||
private SimpleTelegramClient client;
|
||||
private AuthenticationData authenticationData;
|
||||
private final Long2ObjectLinkedOpenHashMap<ChatData> chats = new Long2ObjectLinkedOpenHashMap<>();
|
||||
|
||||
public TDLibInstance(APIToken apiToken, String name, Queue<ClientInteractionRequest> interactionRequests)
|
||||
throws CantLoadLibrary {
|
||||
Init.start();
|
||||
|
||||
this.name = name;
|
||||
settings = TDLibSettings.create(apiToken);
|
||||
setPath(name);
|
||||
this.interactionRequests = interactionRequests;
|
||||
}
|
||||
|
||||
public TDLibInstance(APIToken apiToken, InstanceSettings settings,
|
||||
Queue<ClientInteractionRequest> interactionRequests) throws CantLoadLibrary {
|
||||
Init.start();
|
||||
|
||||
this.name = settings.name;
|
||||
this.settings = settings.toTDLibSettings(apiToken);
|
||||
this.interactionRequests = interactionRequests;
|
||||
this.path = settings.path;
|
||||
}
|
||||
|
||||
public void setPath(String name) {
|
||||
this.setPath(Path.of(".").resolve(name));
|
||||
}
|
||||
|
||||
public void setPath(Path path) {
|
||||
this.path = path;
|
||||
InstanceSettings.setTdPaths(settings, path);
|
||||
}
|
||||
|
||||
public TDLibSettings getSettings() {
|
||||
if (client != null) {
|
||||
throw new UnsupportedOperationException("Client is already started");
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void start(AuthenticationData authenticationData) {
|
||||
this.authenticationData = authenticationData;
|
||||
if (client != null) {
|
||||
throw new UnsupportedOperationException("Client is already started");
|
||||
}
|
||||
client = new SimpleTelegramClient(settings);
|
||||
client.setClientInteraction(this::onClientInteraction);
|
||||
client.addUpdateHandler(TdApi.UpdateAuthorizationState.class, updateAuthorizationState -> {
|
||||
var state = updateAuthorizationState.authorizationState.getConstructor();
|
||||
if (state == AuthorizationStateReady.CONSTRUCTOR) {
|
||||
saveSettings();
|
||||
System.out.println("Logged in");
|
||||
}
|
||||
switch (state) {
|
||||
case AuthorizationStateReady.CONSTRUCTOR,
|
||||
AuthorizationStateLoggingOut.CONSTRUCTOR,
|
||||
AuthorizationStateClosed.CONSTRUCTOR,
|
||||
AuthorizationStateClosing.CONSTRUCTOR -> Example.waitingLogin = false;
|
||||
}
|
||||
});
|
||||
client.addUpdateHandler(TdApi.UpdateNewChat.class, updateNewChat -> {
|
||||
var chat = chats.getAndMoveToFirst(updateNewChat.chat.id);
|
||||
if (chat == null) {
|
||||
chat = new ChatData();
|
||||
chats.putAndMoveToFirst(updateNewChat.chat.id, chat);
|
||||
}
|
||||
});
|
||||
client.start(authenticationData);
|
||||
}
|
||||
|
||||
private void onClientInteraction(InputParameter inputParameter,
|
||||
ParameterInfo parameterInfo,
|
||||
Consumer<String> result) {
|
||||
var customParameter = new StandardInputParameter(inputParameter);
|
||||
interactionRequests.offer(new ClientInteractionRequest(this, customParameter, parameterInfo, result));
|
||||
}
|
||||
|
||||
private void saveSettings() {
|
||||
var is = new InstanceSettings(name, path, settings, authenticationData);
|
||||
try (var os = Files.newOutputStream(path.resolve("settings.json"),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
Example.mapper.writeValue(os, is);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationData getAuthenticationData() {
|
||||
return authenticationData;
|
||||
}
|
||||
|
||||
public void stop() throws InterruptedException {
|
||||
client.closeAndWait();
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public <T extends Function<R>, R extends TdApi.Object> CompletableFuture<R> send(T f) {
|
||||
var cf = new CompletableFuture<R>();
|
||||
client.send(f, result -> {
|
||||
if (result.isError()) {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
cf.completeExceptionally(new TDException(result.error().get()));
|
||||
} else {
|
||||
cf.complete(result.get());
|
||||
}
|
||||
});
|
||||
return cf;
|
||||
}
|
||||
|
||||
public Collection<Long> getChats() {
|
||||
return chats.keySet();
|
||||
}
|
||||
|
||||
public CompletableFuture<Chat> getSender(MessageSender senderId) {
|
||||
if (senderId instanceof TdApi.MessageSenderChat senderChat) {
|
||||
return send(new TdApi.GetChat(senderChat.chatId));
|
||||
} else if (senderId instanceof TdApi.MessageSenderUser senderUser) {
|
||||
return send(new TdApi.GetChat(senderUser.userId));
|
||||
} else {
|
||||
return CompletableFuture.failedFuture(new UnsupportedOperationException("Sender not supported: " + senderId));
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<String> contentToString(MessageContent content) {
|
||||
if (content instanceof TdApi.MessageText messageText) {
|
||||
return CompletableFuture.completedFuture(messageText.text.text);
|
||||
} else {
|
||||
return CompletableFuture.completedFuture("(" + content.getClass().getSimpleName().substring("Message".length()) + ")");
|
||||
}
|
||||
}
|
||||
}
|
21
src/main/resources/log4j2.xml
Normal file
21
src/main/resources/log4j2.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration strict="true"
|
||||
xmlns="http://logging.apache.org/log4j/2.0/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://logging.apache.org/log4j/2.0/config
|
||||
https://raw.githubusercontent.com/apache/logging-log4j2/log4j-2.16.0/log4j-core/src/main/resources/Log4j-config.xsd"
|
||||
status="WARN">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout
|
||||
pattern="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, DEBUG=green bold, TRACE=blue} %style{%processId}{magenta} %style{%-20.20c{1}}{cyan} : %m%n%ex"/>
|
||||
</Console>
|
||||
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="it.tdlight.TDLight" level="ERROR" additivity="false" />
|
||||
<Root level="WARN">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
Loading…
Reference in New Issue
Block a user