initial
This commit is contained in:
commit
d5592d5ee3
|
@ -0,0 +1,20 @@
|
||||||
|
*.bin
|
||||||
|
*.out
|
||||||
|
build/
|
||||||
|
*.exe
|
||||||
|
.gdb_history
|
||||||
|
|
||||||
|
*.so
|
||||||
|
*.bmp
|
||||||
|
*.d
|
||||||
|
*.db
|
||||||
|
|
||||||
|
test
|
||||||
|
.vscode/settings.json
|
||||||
|
|
||||||
|
tgtui
|
||||||
|
tgtui_strip
|
||||||
|
log.txt
|
||||||
|
tdlib/
|
||||||
|
|
||||||
|
thirdparty/td/tdlib
|
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "thirdparty/td"]
|
||||||
|
path = thirdparty/td
|
||||||
|
url = https://github.com/tdlib/td
|
||||||
|
[submodule "thirdparty/Log"]
|
||||||
|
path = thirdparty/Log
|
||||||
|
url = https://git.mrbesen.de/MrBesen/Log.git
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/src/",
|
||||||
|
"${workspaceFolder}/thirdparty/Log/",
|
||||||
|
"${workspaceFolder}/src/**",
|
||||||
|
"${workspaceFolder}/inc/**",
|
||||||
|
"${workspaceFolder}/thirdparty/td/tdlib/include/**"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"compilerPath": "/usr/bin/g++",
|
||||||
|
"cStandard": "c11",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "gcc-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "test Debuggen (gdb)",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/test",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preLaunchTask": "make test",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debuggen (gdb)",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/tgtui",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preLaunchTask": "make all",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "make all",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make -j all",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": [
|
||||||
|
"$gcc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "make clean",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make clean",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "make test",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make -j test",
|
||||||
|
"problemMatcher": ["$gcc"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Author Yannis Gerlach
|
||||||
|
# Hochschule Osnabrück
|
||||||
|
# 13.11.2020
|
||||||
|
|
||||||
|
# `make clean all` nicht mit -j verwenden! -> race condition im make file
|
||||||
|
# statdessen: `make clean; make all -j` verwenden
|
||||||
|
|
||||||
|
NAME = tgtui
|
||||||
|
NAMETEST = test
|
||||||
|
CFLAGS = -std=c++17 -O2 -g -pipe -Wall -Wextra -Wno-unused-parameter -Wpedantic -rdynamic #-march=native -Wall
|
||||||
|
CXX = g++
|
||||||
|
SRCF = src/
|
||||||
|
BUILDDIR = build/
|
||||||
|
TESTF = tests/
|
||||||
|
DEPF = $(BUILDDIR)deps/
|
||||||
|
INCF = ./inc/
|
||||||
|
INCFS = $(shell find $(INCF) -type d)
|
||||||
|
|
||||||
|
LOGF = ./thirdparty/Log/
|
||||||
|
LOGO = $(LOGF)Log.o
|
||||||
|
|
||||||
|
TDLIBF = ./thirdparty/td/tdlib/
|
||||||
|
TDLIBAF = $(TDLIBF)lib/
|
||||||
|
# TDLIBA = $(shell find $(TDLIBF)lib/ -type f -name "*.a")
|
||||||
|
TDLIBA = $(TDLIBF)lib/libtdclient.a $(TDLIBF)lib/libtdcore.a $(TDLIBF)lib/libtdapi.a $(TDLIBF)lib/libtddb.a $(TDLIBF)lib/libtdactor.a $(TDLIBF)lib/libtdsqlite.a $(TDLIBF)lib/libtdnet.a $(TDLIBF)lib/libtdutils.a
|
||||||
|
|
||||||
|
INCLUDES = -I$(LOGF) $(addprefix -I, $(INCFS)) -I$(TDLIBF)include
|
||||||
|
LDFLAGS = -pthread -lz -lssl -lcrypto -lncurses
|
||||||
|
|
||||||
|
SRCFILES = $(shell find $(SRCF) -name "*.cpp")
|
||||||
|
OBJFILES = $(patsubst $(SRCF)%, $(BUILDDIR)%, $(patsubst %.cpp, %.o, $(SRCFILES))) $(LOGO)
|
||||||
|
DEPFILES = $(wildcard $(DEPF)*.d)
|
||||||
|
|
||||||
|
SOURCEDIRS = $(shell find $(SRCF) -type d -printf "%p/\n")
|
||||||
|
BUILDDIRS = $(patsubst $(SRCF)%, $(BUILDDIR)%, $(SOURCEDIRS))
|
||||||
|
|
||||||
|
OBJFILESTEST = $(filter-out $(BUILDDIR)main.o, $(OBJFILES))
|
||||||
|
|
||||||
|
INCLUDES += $(addprefix -I, $(SOURCEDIRS))
|
||||||
|
|
||||||
|
all: $(NAME) runtest
|
||||||
|
|
||||||
|
$(NAME): $(BUILDDIRS) $(DEPF) $(OBJFILES) $(TDLIBA)
|
||||||
|
@echo "Linking $@"
|
||||||
|
@$(CXX) $(CFLAGS) -o $@ $(filter %.o, $^) $(filter %.a, $^) $(LDFLAGS)
|
||||||
|
|
||||||
|
$(BUILDDIR)%.o: $(SRCF)%.cpp
|
||||||
|
@echo "Compiling: $@"
|
||||||
|
@$(CXX) $(CFLAGS) $(INCLUDES) $< -MM -MT $@ > $(DEPF)$(subst /,_,$*).d
|
||||||
|
@$(CXX) -c -o $@ $(CFLAGS) $(INCLUDES) $<
|
||||||
|
|
||||||
|
$(NAME)_strip: $(NAME)
|
||||||
|
@echo "Strip $<"
|
||||||
|
@strip -o $@ $<
|
||||||
|
|
||||||
|
%/:
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
clean-depends:
|
||||||
|
$(RM) -r $(DEPF)
|
||||||
|
|
||||||
|
$(LOGO):
|
||||||
|
$(MAKE) -C $(LOGF) all
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) -r $(NAME) $(BUILDDIR) $(NAMETEST) $(NAME)_strip
|
||||||
|
$(MAKE) -C $(LOGF) $@
|
||||||
|
|
||||||
|
$(NAMETEST): $(BUILDDIRS) $(DEPF) $(TESTF)*.cpp $(OBJFILESTEST) $(TDLIBA)
|
||||||
|
@echo "Compiling tests"
|
||||||
|
@$(CXX) -o $@ $(filter %.o, $^) $(filter %.cpp, $^) $(filter %.a, $^) $(CFLAGS) -I$(SRCF) $(INCLUDES) $(LDFLAGS)
|
||||||
|
|
||||||
|
runtest: $(NAMETEST)
|
||||||
|
@echo "Running tests"
|
||||||
|
./$<
|
||||||
|
|
||||||
|
.PHONY: clean all $(NAMETEST) clean-depends runtest
|
||||||
|
|
||||||
|
remaketdlib:
|
||||||
|
$(RM) -r ./thirdparty/td/build/ ./thirdparty/td/tdlib/
|
||||||
|
mkdir -p ./thirdparty/td/build/
|
||||||
|
cd ./thirdparty/td/build/ ; cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=../tdlib ..
|
||||||
|
$(MAKE) -C ./thirdparty/td/build/ install
|
||||||
|
|
||||||
|
include $(DEPFILES)
|
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
enum class ChatType : uint32_t {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
GROUP = 1,
|
||||||
|
PRIVATE,
|
||||||
|
SECRET,
|
||||||
|
SUPERGROUP,
|
||||||
|
CHANNEL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SupergroupInfo {
|
||||||
|
std::string username;
|
||||||
|
int64_t created;
|
||||||
|
bool signMessages;
|
||||||
|
bool verified;
|
||||||
|
std::string restriction;
|
||||||
|
bool scam;
|
||||||
|
bool fake;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Chat {
|
||||||
|
//basic information
|
||||||
|
int64_t id;
|
||||||
|
int64_t supergroupid; //diffrent than id
|
||||||
|
ChatType type;
|
||||||
|
std::string title;
|
||||||
|
bool hasPhoto;
|
||||||
|
|
||||||
|
//full information
|
||||||
|
std::string description;
|
||||||
|
uint32_t memberCount;
|
||||||
|
uint32_t adminCount;
|
||||||
|
uint32_t restrictedCount;
|
||||||
|
uint32_t bannedCount;
|
||||||
|
int64_t linkedGroupId;
|
||||||
|
uint32_t slowmode;
|
||||||
|
uint64_t slowModeExpiresTS; //timestamp, when slow mode ends
|
||||||
|
int64_t sitckerSetId; // 0 = none
|
||||||
|
|
||||||
|
std::string link = ""; //invite link, may be empty
|
||||||
|
|
||||||
|
//loacation
|
||||||
|
bool hasLocation = false;
|
||||||
|
std::string address = "";
|
||||||
|
double latitude = 0;
|
||||||
|
double longitude = 0;
|
||||||
|
double locationAccourcay = 0;
|
||||||
|
|
||||||
|
//other supergroupInforation
|
||||||
|
SupergroupInfo* moreinfo = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChatIdentifier {
|
||||||
|
int64_t chatid = 0;
|
||||||
|
std::string username = "";
|
||||||
|
|
||||||
|
bool operator<(const ChatIdentifier& rhs) const;
|
||||||
|
bool operator<(const int64_t rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool constexpr operator<(const int64_t l, const ChatIdentifier& rhs) {
|
||||||
|
return l < rhs.chatid;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
|
||||||
|
Config();
|
||||||
|
|
||||||
|
int apiid;
|
||||||
|
std::string apihash;
|
||||||
|
std::string phoneNumber;
|
||||||
|
std::string tgCloudPassword;
|
||||||
|
|
||||||
|
static Config config;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "replymarkup.h"
|
||||||
|
|
||||||
|
enum class MessageType : uint32_t {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
UNSUPPORTED = 1,
|
||||||
|
TEXT = 2,
|
||||||
|
|
||||||
|
ANIMATION,
|
||||||
|
AUDIO,
|
||||||
|
CONTACT,
|
||||||
|
DOCUMENT,
|
||||||
|
LOCATION,
|
||||||
|
PHOTO,
|
||||||
|
POLL,
|
||||||
|
STICKER,
|
||||||
|
VIDEO,
|
||||||
|
VENUE,
|
||||||
|
VOICE,
|
||||||
|
|
||||||
|
SERVICE
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string MessageTypeTypes[] {"unknown", "unsupported", "text", "animation", "audio", "contact", "document", "location", "photo", "poll", "sticker", "video", "venue", "voice", "service"};
|
||||||
|
|
||||||
|
const std::string& convertMessageType(MessageType);
|
||||||
|
MessageType convertMessageType(const std::string& in);
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
int64_t id;
|
||||||
|
int64_t chat;
|
||||||
|
int64_t sender;
|
||||||
|
MessageType type;
|
||||||
|
std::string text;
|
||||||
|
bool isDeleted;
|
||||||
|
bool isOutgoing;
|
||||||
|
bool isPinned;
|
||||||
|
bool isEditable;
|
||||||
|
bool isForwardable;
|
||||||
|
uint64_t sendDate;
|
||||||
|
uint64_t editDate;
|
||||||
|
int64_t messageThreadId;
|
||||||
|
int64_t viaBotid;
|
||||||
|
int64_t mediaAlbumId;
|
||||||
|
|
||||||
|
std::shared_ptr<ReplyMarkup> replyMarkup;
|
||||||
|
|
||||||
|
bool operator==(const Message&) const;
|
||||||
|
bool operator!=(const Message&) const;
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct MsgIdent {
|
||||||
|
int64_t chat;
|
||||||
|
int64_t msgid;
|
||||||
|
};
|
|
@ -0,0 +1,91 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
// interface
|
||||||
|
class ReplyMarkup {
|
||||||
|
public:
|
||||||
|
ReplyMarkup() {}
|
||||||
|
virtual ~ReplyMarkup() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class InlineButton {
|
||||||
|
public:
|
||||||
|
virtual ~InlineButton() {}
|
||||||
|
|
||||||
|
enum class ButtonType : uint32_t {
|
||||||
|
CALLBACK = 0,
|
||||||
|
CALLBACKPASSWORD,
|
||||||
|
LOGINURL,
|
||||||
|
SWITCHINLINE,
|
||||||
|
URL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::string ButtonTypeTypes[];
|
||||||
|
static const std::string& getButtonTypeString(ButtonType t);
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
|
||||||
|
virtual ButtonType getButtonType() const = 0;
|
||||||
|
virtual void write(std::ostream& os) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InlineCallbackButton : public InlineButton {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
|
||||||
|
virtual ButtonType getButtonType() const override;
|
||||||
|
virtual void write(std::ostream& os) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InlineCallbackPasswordButton : public InlineButton {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
|
||||||
|
virtual ButtonType getButtonType() const override;
|
||||||
|
virtual void write(std::ostream& os) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InlineLoginURLButton : public InlineButton {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string url;
|
||||||
|
int32_t id;
|
||||||
|
std::string forwardText;
|
||||||
|
|
||||||
|
virtual ButtonType getButtonType() const override;
|
||||||
|
virtual void write(std::ostream& os) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InlineSwitchInlineButton : public InlineButton {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string query;
|
||||||
|
bool inCurrentChat;
|
||||||
|
|
||||||
|
virtual ButtonType getButtonType() const override;
|
||||||
|
virtual void write(std::ostream& os) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InlineURLButton : public InlineButton {
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::string url;
|
||||||
|
|
||||||
|
virtual ButtonType getButtonType() const override;
|
||||||
|
virtual void write(std::ostream& os) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InlineReplyMarkup : public ReplyMarkup {
|
||||||
|
public:
|
||||||
|
virtual ~InlineReplyMarkup();
|
||||||
|
|
||||||
|
std::vector<InlineButton*> buttons;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct SlimChat {
|
||||||
|
int64_t chatId;
|
||||||
|
std::string name;
|
||||||
|
};
|
|
@ -0,0 +1,180 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <td/telegram/Client.h>
|
||||||
|
#include <td/telegram/td_api.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chat.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "userstatus.h"
|
||||||
|
#include "user.h"
|
||||||
|
|
||||||
|
namespace td_api = td::td_api;
|
||||||
|
using Object = td_api::object_ptr<td_api::Object>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using objptr = td_api::object_ptr<T>;
|
||||||
|
using MessageCallback = std::function<void(objptr<td_api::message>)>;
|
||||||
|
using StatusCallback = std::function<void(const td_api::updateUserStatus&)>;
|
||||||
|
using DraftCallback = std::function<void(int64_t chatid, const std::string&)>;
|
||||||
|
using ActionCallback = std::function<void(int64_t chatid, int64_t userid, ChatAction::ChatAction)>;
|
||||||
|
using DeleteCallback = std::function<void(int64_t chatid, int64_t msgid)>;
|
||||||
|
using UserUpdateCallback = std::function<void(const User&)>;
|
||||||
|
using EditMessageCallback = std::function<void(objptr<td_api::message>)>;
|
||||||
|
using IndexDoneCallback = std::function<void(int64_t)>;
|
||||||
|
using FileUpdateCallback = std::function<void(objptr<td_api::file>)>;
|
||||||
|
using NewChatCallback = std::function<void(objptr<td_api::chat>)>;
|
||||||
|
using ChatFiltersCallback = std::function<void(std::map<int32_t, std::string>)>;
|
||||||
|
|
||||||
|
class TGClient {
|
||||||
|
public:
|
||||||
|
static const uint64_t STATICHANDLERCOUNT = 128;
|
||||||
|
static void(TGClient::* STATICHANDLERS [STATICHANDLERCOUNT])(objptr<td_api::Object>);
|
||||||
|
|
||||||
|
TGClient(std::function<void()> initDoneCallback = {});
|
||||||
|
|
||||||
|
void setAuthData(std::function<std::string()> getAuthCodeCallback);
|
||||||
|
void loop();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void registerNewMessageHandler(MessageCallback mclb);
|
||||||
|
void registerStatusUpdateHandler(StatusCallback sclb);
|
||||||
|
void registerDraftHandler(DraftCallback dclb);
|
||||||
|
void registerActionHandler(ActionCallback clb);
|
||||||
|
void registerDeleteHandler(DeleteCallback dclb);
|
||||||
|
void registerUserUpdateHandler(UserUpdateCallback uuclb);
|
||||||
|
void registerEditMessageHandler(EditMessageCallback emclb);
|
||||||
|
void registerIndexDoneHandle(IndexDoneCallback idclb);
|
||||||
|
void registerFileUpdateCallback(FileUpdateCallback fuclb);
|
||||||
|
void registerNewChatCallback(NewChatCallback ncclb);
|
||||||
|
void registerChatFiltersCallback(ChatFiltersCallback cfclb);
|
||||||
|
|
||||||
|
//exposed apicalls
|
||||||
|
void deleteMessage(int64_t chatid, int64_t messageid, bool forall = true);
|
||||||
|
|
||||||
|
void screenShottaken(int64_t chatid);
|
||||||
|
|
||||||
|
void reply(const objptr<td_api::message>& rplyto, const std::string& awnser);
|
||||||
|
void reply(int64_t chat, int64_t msg, const std::string& awnser);
|
||||||
|
|
||||||
|
void editMessage(const objptr<td_api::message>& toEdit, const std::string& newText);
|
||||||
|
void editMessage(const objptr<td_api::message>& toEdit, const std::string& newText, std::vector<objptr<td_api::textEntity>>& entities);
|
||||||
|
|
||||||
|
void editMessageCaption(const objptr<td_api::message>& toEdit, const std::string& newText, std::vector<objptr<td_api::textEntity>>& entities);
|
||||||
|
|
||||||
|
void sendCallbackQuery(int64_t chat, int64_t messageid, const std::string& payload = "");
|
||||||
|
|
||||||
|
// mute for is in seconds (more than 604800 => forever)
|
||||||
|
void muteChat(int64_t chatid, int32_t mutefor = 1<<30);
|
||||||
|
|
||||||
|
void addChatToChatListFilter(int64_t chatid, int32_t chatfilterid);
|
||||||
|
|
||||||
|
//index chat (editMessageCallback is called for every Message in the chat)
|
||||||
|
void indexChat(int64_t chatid);
|
||||||
|
void getAllTextMessages(int64_t chatid, std::function<void(std::map<int64_t, std::string>)>);
|
||||||
|
|
||||||
|
void downloadFile(int32_t file_id);
|
||||||
|
|
||||||
|
void sendTextMessageToSelf(const std::string& text);
|
||||||
|
|
||||||
|
void getMessage(int64_t chatid, int64_t messageid, std::function<void(objptr<td_api::message>)> f);
|
||||||
|
|
||||||
|
//drafts
|
||||||
|
void setDraft(int64_t chat, const std::string& text = "", int64_t replyto = 0, int64_t messageThread = 0);
|
||||||
|
void clearDraft(int64_t chat, int64_t messageThread = 0);
|
||||||
|
bool hasDraft(int64_t chat) const;
|
||||||
|
|
||||||
|
const User* getCachedUser(int64_t userid);
|
||||||
|
|
||||||
|
int64_t me = 0; //my chatid
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool shouldrun = false;
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
|
std::unique_ptr<td::ClientManager> client_manager_;
|
||||||
|
std::int32_t client_id_{0};
|
||||||
|
|
||||||
|
td_api::object_ptr<td_api::AuthorizationState> authorization_state_;
|
||||||
|
bool are_authorized_{false};
|
||||||
|
bool need_restart_{false};
|
||||||
|
std::uint64_t current_query_id_ = STATICHANDLERCOUNT;
|
||||||
|
std::uint64_t authentication_query_id_{0};
|
||||||
|
|
||||||
|
std::map<std::uint64_t, std::function<void(Object)>> handlers_; //handler list maps updateid -> callback
|
||||||
|
std::map<int64_t, objptr<td_api::draftMessage>> drafts; // chatid -> draft message / list of user drafts (does not contain drafts from the client itself)
|
||||||
|
|
||||||
|
//user cache
|
||||||
|
struct CachedUser : public User {
|
||||||
|
uint64_t lastaccessed = 0;
|
||||||
|
};
|
||||||
|
std::map<int64_t, CachedUser> usercache; //list of cached users
|
||||||
|
std::multimap<uint64_t, int64_t> usercache_timeout; //list of last access -> to know when to clean up which object
|
||||||
|
static const uint32_t MAXCACHEDUSERCOUNT; //at how many users are allowed until the cache si cleand
|
||||||
|
static const uint64_t CACHEDUSERCOUNTCLEANUPTIME; //how old is a cached user allowed to get before beeing purged from the cache (in seconds)
|
||||||
|
|
||||||
|
std::function<void()> initDoneCallback;
|
||||||
|
|
||||||
|
//callbacks
|
||||||
|
std::function<std::string()> getAuthCodeCallback;
|
||||||
|
MessageCallback messageCallback;
|
||||||
|
StatusCallback statusCallback;
|
||||||
|
DraftCallback draftCallback;
|
||||||
|
ActionCallback actionCallback;
|
||||||
|
DeleteCallback deleteCallback;
|
||||||
|
UserUpdateCallback userupdCallback;
|
||||||
|
EditMessageCallback editMessageCallback;
|
||||||
|
IndexDoneCallback indexDoneCallback;
|
||||||
|
FileUpdateCallback fileUpdateCallback;
|
||||||
|
NewChatCallback newChatCallback;
|
||||||
|
ChatFiltersCallback chatFiltersCallback;
|
||||||
|
|
||||||
|
//user cache
|
||||||
|
void accessUser(std::map<int64_t, CachedUser>::iterator it); //iterator to cached user object in usercache
|
||||||
|
void addUser(CachedUser& u);
|
||||||
|
void removeUser(std::map<int64_t, CachedUser>::iterator it); //iterator to cached user object in usercache)
|
||||||
|
void checkUserCache();
|
||||||
|
|
||||||
|
//wrapped requests
|
||||||
|
void requestMessages(int64_t chatid, int64_t from_message_id = 0, std::function<void(int64_t)> onDone = {}, std::function<void(td_api::object_ptr<td_api::message>)> forMessage = {});
|
||||||
|
|
||||||
|
//helper
|
||||||
|
void iterate(const td_api::array<td_api::object_ptr<td_api::message>>& messages, std::function<void(const td_api::message&)> handler);
|
||||||
|
void setOption(const std::string& name, objptr<td_api::OptionValue> val);
|
||||||
|
void setOptions();
|
||||||
|
|
||||||
|
//load me and chat list, initDoneCallback is triggered after that
|
||||||
|
void loadInit();
|
||||||
|
void loadInitInternalCallback(Object o);
|
||||||
|
|
||||||
|
void restart();
|
||||||
|
void send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler = {});
|
||||||
|
|
||||||
|
void send_staticquery(td_api::object_ptr<td_api::Function> f, uint64_t handlerid);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void send_wrappedquery(td_api::object_ptr<td_api::Function> f, std::function<void(td::tl_object_ptr<T>)> handler = {}, std::function<void()> onError = {}, bool printError = true);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static bool catchErrors(const Object& o, std::function<void()> onError = {}, bool printError = true);
|
||||||
|
|
||||||
|
template<typename RequestType, typename ResponseType, typename... Args>
|
||||||
|
void send_inplace(Args... args, std::function<void(td::tl_object_ptr<ResponseType>)> handler = {}, bool printError = true);
|
||||||
|
|
||||||
|
void process_response(td::ClientManager::Response response);
|
||||||
|
|
||||||
|
void process_update(td_api::object_ptr<td_api::Object> update);
|
||||||
|
|
||||||
|
auto create_authentication_query_handler();
|
||||||
|
|
||||||
|
void on_authorization_state_update();
|
||||||
|
|
||||||
|
void check_authentication_error(Object object);
|
||||||
|
|
||||||
|
std::uint64_t next_query_id();
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "slimchat.h"
|
||||||
|
#include "tgclient.h"
|
||||||
|
|
||||||
|
class View;
|
||||||
|
|
||||||
|
class TgTUI {
|
||||||
|
public:
|
||||||
|
|
||||||
|
TgTUI();
|
||||||
|
~TgTUI();
|
||||||
|
|
||||||
|
void run();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
const std::vector<SlimChat>& getChats();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initDoneCB();
|
||||||
|
|
||||||
|
void threadLoop();
|
||||||
|
|
||||||
|
void handleNewChat(objptr<td_api::chat> chat);
|
||||||
|
|
||||||
|
TGClient tgclient;
|
||||||
|
|
||||||
|
View* currentView = nullptr;
|
||||||
|
std::vector<View*> views;
|
||||||
|
|
||||||
|
std::vector<SlimChat> chats;
|
||||||
|
bool shouldRun = false;
|
||||||
|
std::thread tuiThread;
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <td/telegram/td_api.h>
|
||||||
|
|
||||||
|
#include "chat.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "user.h"
|
||||||
|
|
||||||
|
namespace td_api = td::td_api;
|
||||||
|
using Object = td_api::object_ptr<td_api::Object>;
|
||||||
|
|
||||||
|
//converts between tdlib types and own types
|
||||||
|
|
||||||
|
ChatType convertChatType(const td_api::ChatType& in);
|
||||||
|
|
||||||
|
void convertUser(const td_api::user& in, User& out);
|
||||||
|
UserType convertUserType(const td_api::UserType& in);
|
||||||
|
|
||||||
|
void convertMessage(const td_api::message& in, Message& out);
|
||||||
|
MessageType convertMessageTypeandText(const td_api::MessageContent& cont, std::string& textout);
|
||||||
|
|
||||||
|
ReplyMarkup* convertReplyMarkup(const td_api::ReplyMarkup* in);
|
||||||
|
InlineButton* convertInlineButton(const td_api::inlineKeyboardButton* in);
|
||||||
|
|
||||||
|
const std::string& getMessageText(const td_api::object_ptr<td_api::message>& msg);
|
||||||
|
td_api::formattedText* getMessageFormattedText(td_api::message& msg, bool& isText);
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
enum UserType {
|
||||||
|
BOT,
|
||||||
|
DELETED,
|
||||||
|
REGULAR,
|
||||||
|
UNKNOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string UserTypeTypes[] {"bot", "deleted", "regular", "unknown"};
|
||||||
|
|
||||||
|
const std::string& convertUserType(UserType);
|
||||||
|
UserType convertUserType(const std::string& in);
|
||||||
|
|
||||||
|
// complete user infomation
|
||||||
|
struct User {
|
||||||
|
int64_t tgid;
|
||||||
|
std::string firstname;
|
||||||
|
std::string lastname;
|
||||||
|
std::string username;
|
||||||
|
std::string phonenumber;
|
||||||
|
bool isContact;
|
||||||
|
bool isMutalContact;
|
||||||
|
bool isVerified;
|
||||||
|
bool isSupport;
|
||||||
|
std::string restriction;
|
||||||
|
bool isScam;
|
||||||
|
bool haveAccess;
|
||||||
|
UserType type;
|
||||||
|
|
||||||
|
bool operator==(const User&) const;
|
||||||
|
bool operator!=(const User&) const;
|
||||||
|
};
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace UserStatus {
|
||||||
|
|
||||||
|
enum UserStatus : uint8_t {
|
||||||
|
EMPTY = 0,
|
||||||
|
ONLINE,
|
||||||
|
OFFLINE,
|
||||||
|
RECENTLY,
|
||||||
|
LASTWEEK,
|
||||||
|
LASTMONTH
|
||||||
|
};
|
||||||
|
|
||||||
|
UserStatus getStatus(int32_t status);
|
||||||
|
const std::string& statustoString(UserStatus);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ChatAction {
|
||||||
|
|
||||||
|
enum ChatAction : uint8_t {
|
||||||
|
CANCEL = 0,
|
||||||
|
CHOOSINGCONTACT,
|
||||||
|
CHOOSINGLOCATION,
|
||||||
|
RECORDINGVIDEO,
|
||||||
|
RECORDINGVIDEONOTE,
|
||||||
|
RECORDINGVOICENOTE,
|
||||||
|
PLAYGAME,
|
||||||
|
TYPING,
|
||||||
|
UPLOADINGDOCUMENT,
|
||||||
|
UPLOADINGPHOTO,
|
||||||
|
UPLODAINGVIDEO,
|
||||||
|
UPLOADINGVIDEONOTE,
|
||||||
|
UPLOADINGVOICENOTE
|
||||||
|
};
|
||||||
|
|
||||||
|
ChatAction getAction(int32_t status);
|
||||||
|
const std::string& actiontoString(ChatAction);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class TgTUI;
|
||||||
|
|
||||||
|
class View {
|
||||||
|
public:
|
||||||
|
View(TgTUI& tgtui);
|
||||||
|
virtual ~View();
|
||||||
|
|
||||||
|
virtual void open();
|
||||||
|
virtual void close();
|
||||||
|
|
||||||
|
virtual void paint() = 0;
|
||||||
|
|
||||||
|
virtual bool keyIn(int key);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TgTUI& tgtui;
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "slimchat.h"
|
||||||
|
#include "view.h"
|
||||||
|
|
||||||
|
class ViewChatList : public View {
|
||||||
|
public:
|
||||||
|
ViewChatList(TgTUI& tgtui);
|
||||||
|
|
||||||
|
virtual void open() override;
|
||||||
|
virtual void close() override;
|
||||||
|
|
||||||
|
virtual void paint() override;
|
||||||
|
|
||||||
|
virtual bool keyIn(int key) override;
|
||||||
|
|
||||||
|
int64_t getSelectedChatId();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<SlimChat> chats;
|
||||||
|
int32_t currentChatOffset = 0;
|
||||||
|
int32_t selectedChatRow = 0;
|
||||||
|
|
||||||
|
int maxRows, maxCols;
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
Config Config::config;
|
||||||
|
|
||||||
|
Config::Config() :
|
||||||
|
apiid(94575), // taken from td/example/cpp/td_example.cpp
|
||||||
|
apihash("a3406de8d171bb422bb6ddf3bbd800e2"),
|
||||||
|
phoneNumber(""),
|
||||||
|
tgCloudPassword("")
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "tgtui.h"
|
||||||
|
|
||||||
|
static TgTUI* tui = nullptr;
|
||||||
|
|
||||||
|
void sig_handler(int sig_num) {
|
||||||
|
Log::info << "signalHandler triggered";
|
||||||
|
if(tui)
|
||||||
|
tui->stop();
|
||||||
|
(void) sig_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char** argv) {
|
||||||
|
Log::Deleter log;
|
||||||
|
Log::setConsoleLogLevel(Log::Level::off);
|
||||||
|
Log::addLogfile("log.txt", Log::Level::trace, true);
|
||||||
|
|
||||||
|
Log::info << "Hello, World!";
|
||||||
|
|
||||||
|
TgTUI tgtui;
|
||||||
|
tui = &tgtui;
|
||||||
|
|
||||||
|
//register signal handler
|
||||||
|
signal(SIGINT, sig_handler);
|
||||||
|
|
||||||
|
tgtui.run();
|
||||||
|
|
||||||
|
tui = nullptr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include "replymarkup.h"
|
||||||
|
|
||||||
|
const std::string InlineButton::ButtonTypeTypes[] = {"Callback", "CallbackPassword", "LoginURL", "SwitchInline", "URL"};
|
||||||
|
|
||||||
|
const std::string& InlineButton::getButtonTypeString(ButtonType t) {
|
||||||
|
if(t <= ButtonType::URL)
|
||||||
|
return ButtonTypeTypes[(uint32_t) t];
|
||||||
|
return ButtonTypeTypes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineButton::ButtonType InlineCallbackButton::getButtonType() const {
|
||||||
|
return ButtonType::CALLBACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineCallbackButton::write(std::ostream& os) const {
|
||||||
|
os << data;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineButton::ButtonType InlineCallbackPasswordButton::getButtonType() const {
|
||||||
|
return ButtonType::CALLBACKPASSWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineCallbackPasswordButton::write(std::ostream& os) const {
|
||||||
|
os << data;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineButton::ButtonType InlineLoginURLButton::getButtonType() const {
|
||||||
|
return ButtonType::LOGINURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineLoginURLButton::write(std::ostream& os) const {
|
||||||
|
os << "url: " << url << " id: " << id << " fwdText: " << forwardText;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineButton::ButtonType InlineSwitchInlineButton::getButtonType() const {
|
||||||
|
return ButtonType::SWITCHINLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineSwitchInlineButton::write(std::ostream& os) const {
|
||||||
|
os << "query: " << query << " inCurrentChat: " << inCurrentChat;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineButton::ButtonType InlineURLButton::getButtonType() const {
|
||||||
|
return ButtonType::URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineURLButton::write(std::ostream& os) const {
|
||||||
|
os << url;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineReplyMarkup::~InlineReplyMarkup() {
|
||||||
|
for(InlineButton* btn : buttons) {
|
||||||
|
delete btn;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,761 @@
|
||||||
|
#include "tgclient.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <td/telegram/td_api.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "typeconverter.h"
|
||||||
|
|
||||||
|
namespace pl = std::placeholders;
|
||||||
|
|
||||||
|
// overloaded
|
||||||
|
namespace detail {
|
||||||
|
template <class... Fs>
|
||||||
|
struct overload;
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
struct overload<F> : public F {
|
||||||
|
explicit overload(F f) : F(f) { }
|
||||||
|
};
|
||||||
|
template <class F, class... Fs>
|
||||||
|
struct overload<F, Fs...>
|
||||||
|
: public overload<F>
|
||||||
|
, overload<Fs...> {
|
||||||
|
overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) { }
|
||||||
|
using overload<F>::operator();
|
||||||
|
using overload<Fs...>::operator();
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <class... F>
|
||||||
|
auto overloaded(F... f) {
|
||||||
|
return detail::overload<F...>(f...);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Placeholder : public td_api::Object {
|
||||||
|
public:
|
||||||
|
static const std::int32_t ID = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint32_t TGClient::MAXCACHEDUSERCOUNT = 1024;
|
||||||
|
const uint64_t TGClient::CACHEDUSERCOUNTCLEANUPTIME = 86400;
|
||||||
|
|
||||||
|
void(TGClient::* TGClient::STATICHANDLERS [])(objptr<td_api::Object>) = {
|
||||||
|
nullptr, // ids with 0 are handled with the authorisation handler
|
||||||
|
nullptr, // zero handle
|
||||||
|
&TGClient::loadInitInternalCallback,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint64_t HANDLER_NULL = 1;
|
||||||
|
|
||||||
|
TGClient::TGClient(std::function<void()> initDoneCallback) : initDoneCallback(initDoneCallback) {
|
||||||
|
td::ClientManager::execute(td_api::make_object<td_api::setLogVerbosityLevel>(1));
|
||||||
|
client_manager_ = std::make_unique<td::ClientManager>();
|
||||||
|
client_id_ = client_manager_->create_client_id();
|
||||||
|
send_query(td_api::make_object<td_api::getOption>("version"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::setAuthData(std::function<std::string()> getAuthCodeCallback_) {
|
||||||
|
getAuthCodeCallback = getAuthCodeCallback_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::loop() {
|
||||||
|
shouldrun = true;
|
||||||
|
while (shouldrun) {
|
||||||
|
if (need_restart_) {
|
||||||
|
restart();
|
||||||
|
} else if (!are_authorized_) {
|
||||||
|
Log::warn << "not authorized";
|
||||||
|
while(!are_authorized_) {
|
||||||
|
process_response(client_manager_->receive(1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//process updates
|
||||||
|
while(true) {
|
||||||
|
auto response = client_manager_->receive(0.1);
|
||||||
|
if (response.object)
|
||||||
|
process_response(std::move(response));
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::note << "Client Loop terminated";
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::stop() {
|
||||||
|
shouldrun = false;
|
||||||
|
|
||||||
|
send_query(td_api::make_object<td_api::close>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerNewMessageHandler(MessageCallback mclb) {
|
||||||
|
messageCallback = mclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerStatusUpdateHandler(StatusCallback sclb) {
|
||||||
|
statusCallback = sclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerDraftHandler(DraftCallback dclb) {
|
||||||
|
draftCallback = dclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerActionHandler(ActionCallback clb) {
|
||||||
|
actionCallback = clb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerDeleteHandler(DeleteCallback dclb) {
|
||||||
|
deleteCallback = dclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerUserUpdateHandler(UserUpdateCallback uuclb) {
|
||||||
|
userupdCallback = uuclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerEditMessageHandler(EditMessageCallback emclb) {
|
||||||
|
editMessageCallback = emclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerIndexDoneHandle(IndexDoneCallback idclb) {
|
||||||
|
indexDoneCallback = idclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerFileUpdateCallback(FileUpdateCallback fuclb) {
|
||||||
|
fileUpdateCallback = fuclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerNewChatCallback(NewChatCallback ncclb) {
|
||||||
|
newChatCallback = ncclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::registerChatFiltersCallback(ChatFiltersCallback cfclb) {
|
||||||
|
chatFiltersCallback = cfclb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto createFormattedText(const std::string& in, std::vector<objptr<td_api::textEntity>>& entities) {
|
||||||
|
auto formattedtext = td::make_tl_object<td_api::formattedText>();
|
||||||
|
formattedtext->text_ = in;
|
||||||
|
formattedtext->entities_.reserve(entities.size());
|
||||||
|
for(uint32_t i = 0; i < entities.size(); ++i) {
|
||||||
|
auto& ref = entities.at(i);
|
||||||
|
formattedtext->entities_.push_back(td::make_tl_object<td_api::textEntity>(ref->offset_, ref->length_, std::move(ref->type_)));
|
||||||
|
}
|
||||||
|
return formattedtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto createMessageInputTextEntities(const std::string& in, std::vector<objptr<td_api::textEntity>>& entities) {
|
||||||
|
auto formattedText = createFormattedText(in, entities);
|
||||||
|
return td::make_tl_object<td_api::inputMessageText>(std::move(formattedText), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto createMessageInputText(const std::string& in) {
|
||||||
|
std::vector<objptr<td_api::textEntity>> entities;
|
||||||
|
return createMessageInputTextEntities(in, entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::deleteMessage(int64_t chatid, int64_t messageid, bool forall) {
|
||||||
|
send_staticquery(td::make_tl_object<td_api::deleteMessages>(chatid, td_api::array<int64_t>(1, messageid), forall), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::screenShottaken(int64_t chatid) {
|
||||||
|
td_api::array<td_api::int53> messageIds;
|
||||||
|
send_staticquery(td::make_tl_object<td_api::viewMessages>(chatid, std::move(messageIds), td::make_tl_object<td_api::messageSourceScreenshot>(), false), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::reply(const objptr<td_api::message>& rplyto, const std::string& awnser) {
|
||||||
|
reply(rplyto->chat_id_, rplyto->id_, awnser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::reply(int64_t chat, int64_t msg, const std::string& awnser) {
|
||||||
|
auto inputmsgCont = createMessageInputText(awnser);
|
||||||
|
auto sendMsg = td::make_tl_object<td_api::sendMessage>();
|
||||||
|
sendMsg->chat_id_ = chat;
|
||||||
|
sendMsg->reply_to_ = td_api::make_object<td_api::messageReplyToMessage>(chat, msg);
|
||||||
|
sendMsg->input_message_content_ = std::move(inputmsgCont);
|
||||||
|
send_staticquery(std::move(sendMsg), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::editMessage(const objptr<td_api::message>& toEdit, const std::string& newText) {
|
||||||
|
std::vector<objptr<td_api::textEntity>> entities;
|
||||||
|
editMessage(toEdit, newText, entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::editMessage(const objptr<td_api::message>& toEdit, const std::string& newText, std::vector<objptr<td_api::textEntity>>& entities) {
|
||||||
|
auto editMsg = td::make_tl_object<td_api::editMessageText>();
|
||||||
|
editMsg->chat_id_ = toEdit->chat_id_;
|
||||||
|
editMsg->message_id_ = toEdit->id_;
|
||||||
|
editMsg->input_message_content_ = createMessageInputTextEntities(newText, entities);
|
||||||
|
send_staticquery(std::move(editMsg), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::editMessageCaption(const objptr<td_api::message>& toEdit, const std::string& newText, std::vector<objptr<td_api::textEntity>>& entities ) {
|
||||||
|
auto editMsg = td::make_tl_object<td_api::editMessageCaption>();
|
||||||
|
editMsg->chat_id_ = toEdit->chat_id_;
|
||||||
|
editMsg->message_id_ = toEdit->id_;
|
||||||
|
editMsg->caption_ = createFormattedText(newText, entities);
|
||||||
|
send_staticquery(std::move(editMsg), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::sendCallbackQuery(int64_t chat, int64_t messageid, const std::string& payload) {
|
||||||
|
Log::info << "sendCallbackQuery: " << chat << " " << messageid << " " << payload;
|
||||||
|
auto payloadobj = td::make_tl_object<td_api::callbackQueryPayloadData>();
|
||||||
|
payloadobj->data_ = payload;
|
||||||
|
auto sendCallback = td::make_tl_object<td_api::getCallbackQueryAnswer>();
|
||||||
|
sendCallback->chat_id_ = chat;
|
||||||
|
sendCallback->message_id_ = messageid;
|
||||||
|
sendCallback->payload_ = std::move(payloadobj);
|
||||||
|
send_wrappedquery<td_api::callbackQueryAnswer>(std::move(sendCallback), [](objptr<td_api::callbackQueryAnswer> clb) {
|
||||||
|
Log::info << "CallbackAwnser - text: " << clb->text_ << " showAlert: " << clb->show_alert_ << " url: " << clb->url_;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::muteChat(int64_t chatid, int32_t mutefor) {
|
||||||
|
auto settings = td::make_tl_object<td_api::chatNotificationSettings>();
|
||||||
|
settings->use_default_mute_for_ = false;
|
||||||
|
settings->mute_for_ = mutefor;
|
||||||
|
settings->use_default_sound_ = true;
|
||||||
|
settings->use_default_show_preview_ = true;
|
||||||
|
settings->use_default_disable_pinned_message_notifications_ = true;
|
||||||
|
settings->use_default_disable_mention_notifications_ = true;
|
||||||
|
send_staticquery(td::make_tl_object<td_api::setChatNotificationSettings>(chatid, std::move(settings)), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::addChatToChatListFilter(int64_t chatid, int32_t chatfilterid) {
|
||||||
|
send_staticquery(td::make_tl_object<td_api::addChatToList>(chatid, td::make_tl_object<td_api::chatListFolder>(chatfilterid)), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::indexChat(int64_t chatid) {
|
||||||
|
requestMessages(chatid, 0, indexDoneCallback, editMessageCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::getAllTextMessages(int64_t chatid, std::function<void(std::map<int64_t, std::string>)> f) {
|
||||||
|
if(!f) return;
|
||||||
|
|
||||||
|
// only works without multithreading!
|
||||||
|
// with multithreading onDone could be processed before the last update -> segfault
|
||||||
|
auto map = new std::map<int64_t, std::string>();
|
||||||
|
requestMessages(chatid, 0, [this, map, f](int64_t chat) {
|
||||||
|
f(*map);
|
||||||
|
delete map;
|
||||||
|
}, [this, map](objptr<td_api::message> msg) {
|
||||||
|
// try to get text
|
||||||
|
bool isText = false;
|
||||||
|
// text does not need to be freed, because it has a owning pointer in msg
|
||||||
|
td_api::formattedText* text = getMessageFormattedText(*msg, isText);
|
||||||
|
if(msg->sender_id_->get_id() == td_api::messageSenderUser::ID) {
|
||||||
|
auto sender = (td_api::messageSenderUser*) (msg->sender_id_).get();
|
||||||
|
if(sender->user_id_ != me) {
|
||||||
|
Log::warn << "ignore non-me-config-message from: " << sender->user_id_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(text) {
|
||||||
|
(*map)[msg->id_] = text->text_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::downloadFile(int32_t file_id) {
|
||||||
|
Log::info << "start download for file: " << file_id;
|
||||||
|
send_staticquery(td::make_tl_object<td_api::downloadFile>(file_id, /* prio */ 15, 0, 0, false), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::sendTextMessageToSelf(const std::string& text) {
|
||||||
|
auto sendmsg = td::make_tl_object<td_api::sendMessage>();
|
||||||
|
sendmsg->chat_id_ = me;
|
||||||
|
sendmsg->message_thread_id_ = 0;
|
||||||
|
sendmsg->reply_to_ = nullptr;
|
||||||
|
sendmsg->options_ = nullptr;
|
||||||
|
sendmsg->reply_markup_ = nullptr;
|
||||||
|
std::vector<objptr<td_api::textEntity>> entities;
|
||||||
|
sendmsg->input_message_content_ = createMessageInputTextEntities(text, entities);
|
||||||
|
send_staticquery(std::move(sendmsg), HANDLER_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::getMessage(int64_t chatid, int64_t messageid, std::function<void(objptr<td_api::message>)> f) {
|
||||||
|
send_wrappedquery<td_api::message>(td::make_tl_object<td_api::getMessage>(chatid, messageid), f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::setDraft(int64_t chat, const std::string& text, int64_t replyto, int64_t messageThread) {
|
||||||
|
auto itext = createMessageInputText(text);
|
||||||
|
objptr<td_api::draftMessage> dmsg = td_api::make_object<td_api::draftMessage>(replyto, time(nullptr), std::move(itext));
|
||||||
|
send_staticquery(td_api::make_object<td_api::setChatDraftMessage>(chat, messageThread, std::move(dmsg)), HANDLER_NULL);
|
||||||
|
drafts.erase(chat); //if there was a user draft, its now gone
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::clearDraft(int64_t chat, int64_t messageThread) {
|
||||||
|
objptr<td_api::draftMessage> dmsg;
|
||||||
|
send_staticquery(td_api::make_object<td_api::setChatDraftMessage>(chat, messageThread, std::move(dmsg)), HANDLER_NULL);
|
||||||
|
drafts.erase(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TGClient::hasDraft(int64_t chat) const {
|
||||||
|
return drafts.find(chat) != drafts.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const User* TGClient::getCachedUser(int64_t userid) {
|
||||||
|
auto it = usercache.find(userid);
|
||||||
|
if(it == usercache.end()) {
|
||||||
|
// no user found
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//update accesstimes
|
||||||
|
accessUser(it);
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::accessUser(std::map<int64_t, CachedUser>::iterator it) {
|
||||||
|
//change last access time
|
||||||
|
uint64_t lastacc = it->second.lastaccessed;
|
||||||
|
|
||||||
|
//remove old entry
|
||||||
|
auto it2 = usercache_timeout.lower_bound(lastacc);
|
||||||
|
auto it2end = usercache_timeout.upper_bound(lastacc);
|
||||||
|
for( ;it2 != it2end; ++it2) {
|
||||||
|
if(it2->second == it->first) {
|
||||||
|
usercache_timeout.erase(it2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t newacc = time(0);
|
||||||
|
|
||||||
|
//insert new value
|
||||||
|
usercache_timeout.insert({newacc, it->first});
|
||||||
|
it->second.lastaccessed = newacc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::addUser(CachedUser& u) {
|
||||||
|
//check for existing user
|
||||||
|
auto it = usercache.find(u.tgid);
|
||||||
|
if(it != usercache.end()) {
|
||||||
|
//remove old user
|
||||||
|
removeUser(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
//add user
|
||||||
|
uint64_t accesstime = time(0);
|
||||||
|
u.lastaccessed = accesstime;
|
||||||
|
usercache.insert({u.tgid, u});
|
||||||
|
|
||||||
|
//add timing information
|
||||||
|
usercache_timeout.insert({accesstime, u.tgid});
|
||||||
|
|
||||||
|
//remove old entrys
|
||||||
|
checkUserCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::removeUser(std::map<int64_t, CachedUser>::iterator it) {
|
||||||
|
if(it == usercache.end()) return;
|
||||||
|
|
||||||
|
uint64_t accesstime = it->second.lastaccessed;
|
||||||
|
int64_t id = it->second.tgid;
|
||||||
|
|
||||||
|
//remove from timeout cache
|
||||||
|
auto it2 = usercache_timeout.lower_bound(accesstime);
|
||||||
|
auto it2end = usercache_timeout.upper_bound(accesstime);
|
||||||
|
for( ;it2 != it2end; ++it2) {
|
||||||
|
if(it2->second == id) {
|
||||||
|
usercache_timeout.erase(it2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove from real cache
|
||||||
|
usercache.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::checkUserCache() {
|
||||||
|
if(usercache.size() > MAXCACHEDUSERCOUNT) {
|
||||||
|
uint64_t lastallowed = time(0) - CACHEDUSERCOUNTCLEANUPTIME;
|
||||||
|
auto it = usercache_timeout.begin();
|
||||||
|
auto itend = usercache_timeout.upper_bound(lastallowed);
|
||||||
|
while(it != itend) {
|
||||||
|
if(it->first == (uint64_t) me) {
|
||||||
|
++it;
|
||||||
|
continue; // never remove self from cache
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itcopy = it;
|
||||||
|
++itcopy;
|
||||||
|
auto todeleteit = usercache.find(it->second);
|
||||||
|
removeUser(todeleteit);
|
||||||
|
it = itcopy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::requestMessages(int64_t chatid, int64_t from_message_id, std::function<void(int64_t)> onDone, std::function<void(td_api::object_ptr<td_api::message>)> forMessage) {
|
||||||
|
Log::debug << "requestMessages " << chatid << " from: " << from_message_id;
|
||||||
|
send_wrappedquery<td_api::messages>(td_api::make_object<td_api::getChatHistory>(chatid, from_message_id, 0, 100, false), [this, chatid, onDone, forMessage](objptr<td_api::messages> m) {
|
||||||
|
//request next chunk
|
||||||
|
td_api::array<td_api::object_ptr<td_api::message>>& arr = m->messages_;
|
||||||
|
|
||||||
|
Log::trace << "got " << arr.size() << " messages";
|
||||||
|
|
||||||
|
if(arr.size() == 0) {
|
||||||
|
// indexing done
|
||||||
|
Log::info << "Chatindex done";
|
||||||
|
if(onDone) {
|
||||||
|
onDone(chatid);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//issue next request bevore processing -> requires reverse iteration to find the last message id
|
||||||
|
int64_t smallestid = std::numeric_limits<int64_t>::max();
|
||||||
|
for(td_api::array<td_api::object_ptr<td_api::message>>::const_reverse_iterator it = arr.rbegin(); it != arr.rend(); ++it) {
|
||||||
|
const td_api::object_ptr<td_api::message>& obj = *it;
|
||||||
|
if(obj) {
|
||||||
|
int64_t id = obj->id_;
|
||||||
|
if(id < smallestid) {
|
||||||
|
smallestid = id;
|
||||||
|
break; //the first element should be good, because they should be ordered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//start next request
|
||||||
|
if(shouldrun && smallestid != std::numeric_limits<int64_t>::max()) {
|
||||||
|
requestMessages(chatid, smallestid, onDone, forMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
//process the messages
|
||||||
|
if(forMessage) {
|
||||||
|
for(auto it = arr.begin(); it != arr.end(); ++it) {
|
||||||
|
if(*it) {
|
||||||
|
forMessage(std::move(*it));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::setOption(const std::string& name, objptr<td_api::OptionValue> val) {
|
||||||
|
client_manager_->execute(td_api::make_object<td_api::setOption>(name, std::move(val)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::setOptions() {
|
||||||
|
setOption("disable_persistent_network_statistics", td_api::make_object<td_api::optionValueBoolean>(true));
|
||||||
|
setOption("disable_sent_scheduled_message_notifications", td_api::make_object<td_api::optionValueBoolean>(true));
|
||||||
|
setOption("disable_time_adjustment_protection", td_api::make_object<td_api::optionValueBoolean>(true));
|
||||||
|
setOption("disable_top_chats", td_api::make_object<td_api::optionValueBoolean>(true));
|
||||||
|
setOption("ignore_background_updates", td_api::make_object<td_api::optionValueBoolean>(false));
|
||||||
|
setOption("ignore_inline_thumbnails", td_api::make_object<td_api::optionValueBoolean>(true));
|
||||||
|
setOption("ignore_platform_restrictions", td_api::make_object<td_api::optionValueBoolean>(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::loadInit() {
|
||||||
|
send_wrappedquery<td_api::user>(td_api::make_object<td_api::getMe>(), [this](objptr<td_api::user> meu) {
|
||||||
|
CachedUser cachedme;
|
||||||
|
convertUser(*meu, cachedme);
|
||||||
|
me = cachedme.tgid;
|
||||||
|
addUser(cachedme);
|
||||||
|
|
||||||
|
//load chat list
|
||||||
|
td_api::object_ptr<td_api::ChatList> mainlist = td_api::make_object<td_api::chatListMain>();
|
||||||
|
send_staticquery(td_api::make_object<td_api::getChats>(std::move(mainlist), std::numeric_limits<int32_t>::max()), 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::loadInitInternalCallback(Object o) {
|
||||||
|
(void) o;
|
||||||
|
Log::note << "init done, chats loaded";
|
||||||
|
if(initDoneCallback)
|
||||||
|
initDoneCallback();
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::restart() {
|
||||||
|
client_manager_.reset();
|
||||||
|
authorization_state_.reset();
|
||||||
|
|
||||||
|
(this)->~TGClient();
|
||||||
|
new (this) TGClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler) {
|
||||||
|
auto query_id = next_query_id();
|
||||||
|
if (handler) {
|
||||||
|
handlers_.emplace(query_id, std::move(handler));
|
||||||
|
}
|
||||||
|
client_manager_->send(client_id_, query_id, std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::send_staticquery(td_api::object_ptr<td_api::Function> f, uint64_t handlerid) {
|
||||||
|
assert(handlerid < STATICHANDLERCOUNT);
|
||||||
|
client_manager_->send(client_id_, handlerid, std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void TGClient::send_wrappedquery(td_api::object_ptr<td_api::Function> f, std::function<void(td::tl_object_ptr<T>)> handler, std::function<void()> onError, bool printError) {
|
||||||
|
send_query(std::move(f), [handler, onError, printError](Object o) {
|
||||||
|
if(catchErrors<T>(o, onError, printError) && handler) {
|
||||||
|
auto casted = td::move_tl_object_as<T>(o);
|
||||||
|
handler(std::move(casted));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool TGClient::catchErrors(const Object& o, std::function<void()> onError, bool printError) {
|
||||||
|
if(!o) return false;
|
||||||
|
if(o->get_id() == td_api::error::ID) {
|
||||||
|
if(printError) {
|
||||||
|
const objptr<td_api::error>& err = (const objptr<td_api::error>&) o;
|
||||||
|
Log::warn << "error: " << err->code_ << " " << err->message_;
|
||||||
|
}
|
||||||
|
if(onError)
|
||||||
|
onError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(T::ID != Placeholder::ID && o->get_id() != T::ID) {
|
||||||
|
if(printError)
|
||||||
|
Log::warn << "function did not return required type: returned_id: " << o->get_id() << " required_id: " << T::ID;
|
||||||
|
if(onError)
|
||||||
|
onError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename RequestType, typename ResponseType, typename... Args>
|
||||||
|
void TGClient::send_inplace(Args... args, std::function<void(td::tl_object_ptr<ResponseType>)> handler, bool printError) {
|
||||||
|
send_wrappedquery<ResponseType>(td_api::make_object<RequestType>(std::forward<Args>(args)...), handler, printError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::process_response(td::ClientManager::Response response) {
|
||||||
|
if (!response.object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
if (response.request_id == 0) {
|
||||||
|
return process_update(std::move(response.object));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.request_id < STATICHANDLERCOUNT) {
|
||||||
|
auto handler = STATICHANDLERS[response.request_id];
|
||||||
|
if(!catchErrors<Placeholder>(response.object) || !handler) return; // nullptr handler
|
||||||
|
|
||||||
|
(this->*handler)(std::move(response.object));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = handlers_.find(response.request_id);
|
||||||
|
if (it != handlers_.end()) {
|
||||||
|
it->second(std::move(response.object));
|
||||||
|
handlers_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::process_update(td_api::object_ptr<td_api::Object> update) {
|
||||||
|
td_api::downcast_call(
|
||||||
|
*update, overloaded(
|
||||||
|
[this](td_api::updateAuthorizationState& update_authorization_state) {
|
||||||
|
authorization_state_ = std::move(update_authorization_state.authorization_state_);
|
||||||
|
on_authorization_state_update();
|
||||||
|
},
|
||||||
|
[this](td_api::updateNewMessage& update_new_message) {
|
||||||
|
if(messageCallback) {
|
||||||
|
messageCallback(std::move(update_new_message.message_));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateUserStatus& updateStatus) {
|
||||||
|
if(statusCallback)
|
||||||
|
statusCallback(updateStatus);
|
||||||
|
},
|
||||||
|
[this](td_api::updateChatDraftMessage& draft) {
|
||||||
|
if(draftCallback) {
|
||||||
|
const objptr<td_api::draftMessage>& draftmsg = draft.draft_message_;
|
||||||
|
std::string text;
|
||||||
|
if(draftmsg) {
|
||||||
|
const objptr<td_api::InputMessageContent>& imc = draftmsg->input_message_text_;
|
||||||
|
if(imc->get_id() == td_api::inputMessageText::ID) {
|
||||||
|
text = ((const td_api::inputMessageText&) *imc).text_->text_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draftCallback(draft.chat_id_, text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateDeleteMessages& dele) {
|
||||||
|
if(deleteCallback && dele.is_permanent_ && !dele.from_cache_) {
|
||||||
|
int64_t chatid = dele.chat_id_;
|
||||||
|
const std::vector<int64_t>& list = dele.message_ids_;
|
||||||
|
|
||||||
|
//trigger callback
|
||||||
|
for(int64_t msgid : list) {
|
||||||
|
deleteCallback(chatid, msgid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateMessageEdited& edit) {
|
||||||
|
if(editMessageCallback) {
|
||||||
|
//request full message
|
||||||
|
send_wrappedquery<td_api::message>(td_api::make_object<td_api::getMessage>(edit.chat_id_, edit.message_id_), editMessageCallback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateChatAction& action) {
|
||||||
|
if(actionCallback) {
|
||||||
|
int64_t chatid = action.chat_id_, userid = action.chat_id_;
|
||||||
|
ChatAction::ChatAction act = ChatAction::getAction(action.action_->get_id());
|
||||||
|
actionCallback(chatid, userid, act);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateUser& user) {
|
||||||
|
if(userupdCallback) {
|
||||||
|
const td_api::user& ou = *user.user_;
|
||||||
|
CachedUser u;
|
||||||
|
convertUser(ou, u);
|
||||||
|
|
||||||
|
//cache user
|
||||||
|
addUser(u);
|
||||||
|
|
||||||
|
//trigger event
|
||||||
|
userupdCallback(u);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateFile& fileupd) {
|
||||||
|
if(fileUpdateCallback) {
|
||||||
|
fileUpdateCallback(std::move(fileupd.file_));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateNewChat& newChat) {
|
||||||
|
if(newChatCallback) {
|
||||||
|
newChatCallback(std::move(newChat.chat_));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[this](td_api::updateChatFolders& chatFilters) {
|
||||||
|
if(chatFiltersCallback) {
|
||||||
|
std::map<int32_t, std::string> out;
|
||||||
|
auto& arr = chatFilters.chat_folders_;
|
||||||
|
for(auto& it : arr) {
|
||||||
|
out[it->id_] = it->title_;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatFiltersCallback(out);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[](auto& u) {})); //default
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
auto TGClient::create_authentication_query_handler() {
|
||||||
|
return [this, id = authentication_query_id_](Object object) {
|
||||||
|
if (id == authentication_query_id_) {
|
||||||
|
check_authentication_error(std::move(object));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::on_authorization_state_update() {
|
||||||
|
Log::trace << "update auth state " << authorization_state_->get_id();
|
||||||
|
authentication_query_id_++;
|
||||||
|
td_api::downcast_call(
|
||||||
|
*authorization_state_,
|
||||||
|
overloaded(
|
||||||
|
[this](td_api::authorizationStateReady&) {
|
||||||
|
are_authorized_ = true;
|
||||||
|
Log::note << "Got authorization";
|
||||||
|
loadInit();
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateLoggingOut&) {
|
||||||
|
are_authorized_ = false;
|
||||||
|
std::cout << "Logging out" << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateClosing&) { std::cout << "Closing" << std::endl; },
|
||||||
|
[this](td_api::authorizationStateClosed&) {
|
||||||
|
are_authorized_ = false;
|
||||||
|
need_restart_ = true;
|
||||||
|
std::cout << "Terminated" << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitCode&) {
|
||||||
|
|
||||||
|
if(!getAuthCodeCallback) {
|
||||||
|
Log::error << "no authCode Callback registered";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string code = getAuthCodeCallback();
|
||||||
|
send_query(td_api::make_object<td_api::checkAuthenticationCode>(code),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitRegistration&) {
|
||||||
|
std::string first_name;
|
||||||
|
std::string last_name;
|
||||||
|
std::cout << "Enter your first name: " << std::flush;
|
||||||
|
std::cin >> first_name;
|
||||||
|
std::cout << "Enter your last name: " << std::flush;
|
||||||
|
std::cin >> last_name;
|
||||||
|
send_query(td_api::make_object<td_api::registerUser>(first_name, last_name),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitPassword&) {
|
||||||
|
send_query(td_api::make_object<td_api::checkAuthenticationPassword>(Config::config.tgCloudPassword),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitOtherDeviceConfirmation& state) {
|
||||||
|
std::cout << "Confirm this login link on another device: " << state.link_ << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitPhoneNumber&) {
|
||||||
|
if(Config::config.phoneNumber.empty()) {
|
||||||
|
Log::fatal << "empty phone number";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_query(td_api::make_object<td_api::setAuthenticationPhoneNumber>(Config::config.phoneNumber, nullptr),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitEmailAddress&) {
|
||||||
|
send_query(td_api::make_object<td_api::setAuthenticationEmailAddress>(""),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitEmailCode&) {
|
||||||
|
send_query(td_api::make_object<td_api::checkAuthenticationCode>(""),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitTdlibParameters&) {
|
||||||
|
auto parameters = td_api::make_object<td_api::setTdlibParameters>();
|
||||||
|
parameters->database_directory_ = "tdlib";
|
||||||
|
parameters->use_test_dc_ = false;
|
||||||
|
|
||||||
|
parameters->use_file_database_ = true;
|
||||||
|
parameters->use_chat_info_database_ = true;
|
||||||
|
parameters->use_message_database_ = true;
|
||||||
|
|
||||||
|
parameters->use_secret_chats_ = false;
|
||||||
|
parameters->ignore_file_names_ = false;
|
||||||
|
|
||||||
|
parameters->api_id_ = Config::config.apiid;
|
||||||
|
parameters->api_hash_ = Config::config.apihash;
|
||||||
|
Log::info << "using api: " << Config::config.apiid << " " << Config::config.apihash;
|
||||||
|
parameters->system_language_code_ = "en";
|
||||||
|
parameters->device_model_ = "Desktop";
|
||||||
|
parameters->application_version_ = "1.0";
|
||||||
|
parameters->enable_storage_optimizer_ = true;
|
||||||
|
send_query(std::move(parameters),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
setOptions();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TGClient::check_authentication_error(Object object) {
|
||||||
|
if (object->get_id() == td_api::error::ID) {
|
||||||
|
auto error = td::move_tl_object_as<td_api::error>(object);
|
||||||
|
std::cout << "Error: " << to_string(error) << std::flush;
|
||||||
|
on_authorization_state_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t TGClient::next_query_id() {
|
||||||
|
return ++current_query_id_;
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
#include "tgtui.h"
|
||||||
|
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
|
#include <curses.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "view.h"
|
||||||
|
#include "viewchatlist.h"
|
||||||
|
|
||||||
|
namespace pl = std::placeholders;
|
||||||
|
|
||||||
|
TgTUI::TgTUI() : tgclient(std::bind(&TgTUI::initDoneCB, this)) {
|
||||||
|
views.push_back(new ViewChatList(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
TgTUI::~TgTUI() {
|
||||||
|
for(View* v : views) {
|
||||||
|
delete v;
|
||||||
|
}
|
||||||
|
views.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TgTUI::run() {
|
||||||
|
|
||||||
|
//apply config
|
||||||
|
tgclient.setAuthData( [](){
|
||||||
|
/*
|
||||||
|
std::cout << "Auth Code: " << std::flush;
|
||||||
|
std::string code;
|
||||||
|
std::cin >> code; // TODO: take input different
|
||||||
|
return code;
|
||||||
|
*/
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
//register callbacks
|
||||||
|
/*
|
||||||
|
tgclient.registerDraftHandler(std::bind(&UserBot::handleDraft, this, pl::_1, pl::_2));
|
||||||
|
tgclient.registerActionHandler(std::bind(&UserBot::handleAction, this, pl::_1, pl::_2, pl::_3));
|
||||||
|
tgclient.registerDeleteHandler(std::bind(&UserBot::handleDelete, this, pl::_1, pl::_2));
|
||||||
|
tgclient.registerStatusUpdateHandler(std::bind(&UserBot::handleStatus, this, pl::_1));
|
||||||
|
tgclient.registerEditMessageHandler(std::bind(&UserBot::handleEditMessage, this, pl::_1));
|
||||||
|
tgclient.registerNewMessageHandler(std::bind(&UserBot::handleMessage, this, pl::_1));
|
||||||
|
tgclient.registerUserUpdateHandler(std::bind(&Store::logUser, store, pl::_1));
|
||||||
|
tgclient.registerIndexDoneHandle(std::bind(&UserBot::handleIndexDone, this, pl::_1));
|
||||||
|
tgclient.registerFileUpdateCallback(std::bind(&UserBot::handleFileUpdate, this, pl::_1));
|
||||||
|
tgclient.registerChatFiltersCallback(std::bind(&UserBot::handleChatFilters, this, pl::_1));
|
||||||
|
*/
|
||||||
|
|
||||||
|
tgclient.registerNewChatCallback(std::bind(&TgTUI::handleNewChat, this, pl::_1));
|
||||||
|
|
||||||
|
//run client
|
||||||
|
tgclient.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TgTUI::stop() {
|
||||||
|
shouldRun = false;
|
||||||
|
|
||||||
|
tgclient.stop();
|
||||||
|
|
||||||
|
if(tuiThread.joinable()) {
|
||||||
|
tuiThread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<SlimChat>& TgTUI::getChats() {
|
||||||
|
return chats;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TgTUI::initDoneCB() {
|
||||||
|
shouldRun = true;
|
||||||
|
currentView = views.at(0);
|
||||||
|
tuiThread = std::thread(&TgTUI::threadLoop, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TgTUI::threadLoop() {
|
||||||
|
// init ncurses
|
||||||
|
::initscr();
|
||||||
|
// ::start_color();
|
||||||
|
::cbreak(); // Disable line buffering
|
||||||
|
::keypad(stdscr, TRUE); // Enable special keys like arrow keys
|
||||||
|
::noecho(); // Don't print characters to the screen
|
||||||
|
|
||||||
|
currentView->open();
|
||||||
|
|
||||||
|
bool keep = true;
|
||||||
|
while(shouldRun && keep) {
|
||||||
|
::clear();
|
||||||
|
|
||||||
|
currentView->paint();
|
||||||
|
|
||||||
|
::refresh();
|
||||||
|
|
||||||
|
int ch = getch();
|
||||||
|
|
||||||
|
keep = currentView->keyIn(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentView->close();
|
||||||
|
|
||||||
|
// deinit ncurses
|
||||||
|
::nocbreak();
|
||||||
|
::echo();
|
||||||
|
::endwin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TgTUI::handleNewChat(objptr<td_api::chat> chat) {
|
||||||
|
chats.push_back({chat->id_, chat->title_});
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
#include "typeconverter.h"
|
||||||
|
#include "message.h"
|
||||||
|
|
||||||
|
//converts between tdlib types and own types
|
||||||
|
|
||||||
|
ChatType convertChatType(const td_api::ChatType& in) {
|
||||||
|
switch(in.get_id()) {
|
||||||
|
case td_api::chatTypeBasicGroup::ID:
|
||||||
|
return ChatType::GROUP;
|
||||||
|
case td_api::chatTypePrivate::ID:
|
||||||
|
return ChatType::PRIVATE;
|
||||||
|
case td_api::chatTypeSecret::ID:
|
||||||
|
return ChatType::SECRET;
|
||||||
|
case td_api::chatTypeSupergroup::ID: {
|
||||||
|
if(((const td_api::chatTypeSupergroup&) in).is_channel_)
|
||||||
|
return ChatType::CHANNEL;
|
||||||
|
return ChatType::SUPERGROUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ChatType::UNKNOWN; //unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
void convertUser(const td_api::user& in, User& out) {
|
||||||
|
if(in.usernames_ && in.usernames_->active_usernames_.empty()) {
|
||||||
|
out.username = in.usernames_->active_usernames_.at(0);
|
||||||
|
} else {
|
||||||
|
out.username = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.tgid = in.id_;
|
||||||
|
out.firstname = in.first_name_;
|
||||||
|
out.lastname = in.last_name_;
|
||||||
|
out.phonenumber = in.phone_number_;
|
||||||
|
out.isContact = in.is_contact_;
|
||||||
|
out.isMutalContact = in.is_mutual_contact_;
|
||||||
|
out.isVerified = in.is_verified_;
|
||||||
|
out.isSupport = in.is_support_;
|
||||||
|
out.restriction = in.restriction_reason_;
|
||||||
|
out.isScam = in.is_scam_;
|
||||||
|
out.haveAccess = in.have_access_;
|
||||||
|
out.type = convertUserType(*in.type_);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserType convertUserType(const td_api::UserType& in) {
|
||||||
|
switch(in.get_id()) {
|
||||||
|
case td_api::userTypeBot::ID:
|
||||||
|
return UserType::BOT;
|
||||||
|
case td_api::userTypeDeleted::ID:
|
||||||
|
return UserType::DELETED;
|
||||||
|
case td_api::userTypeRegular::ID:
|
||||||
|
return UserType::REGULAR;
|
||||||
|
}
|
||||||
|
return UserType::UNKNOWN; //unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& convertUserType(UserType type) {
|
||||||
|
switch(type) {
|
||||||
|
case UserType::BOT:
|
||||||
|
return UserTypeTypes[0];
|
||||||
|
case UserType::DELETED:
|
||||||
|
return UserTypeTypes[1];
|
||||||
|
case UserType::REGULAR:
|
||||||
|
return UserTypeTypes[2];
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return UserTypeTypes[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
UserType convertUserType(const std::string& in) {
|
||||||
|
if(in == UserTypeTypes[0])
|
||||||
|
return UserType::BOT;
|
||||||
|
if(in == UserTypeTypes[1])
|
||||||
|
return UserType::DELETED;
|
||||||
|
if(in == UserTypeTypes[2])
|
||||||
|
return UserType::REGULAR;
|
||||||
|
return UserType::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& convertMessageType(MessageType type) {
|
||||||
|
uint32_t i = (uint32_t) type;
|
||||||
|
if(i > 14) return MessageTypeTypes[0]; //unknown
|
||||||
|
return MessageTypeTypes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageType convertMessageType(const std::string& in) {
|
||||||
|
for(int32_t i = 0; i < 14; ++i) {
|
||||||
|
if(MessageTypeTypes[i] == in) {
|
||||||
|
return (MessageType) i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MessageType::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t getSender(const td_api::MessageSender& s) {
|
||||||
|
if(s.get_id() == td_api::messageSenderUser::ID) {
|
||||||
|
return static_cast<const td_api::messageSenderUser&>(s).user_id_;
|
||||||
|
}
|
||||||
|
if(s.get_id() == td_api::messageSenderChat::ID) {
|
||||||
|
return static_cast<const td_api::messageSenderChat&>(s).chat_id_;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convertMessage(const td_api::message& in, Message& out) {
|
||||||
|
out.id = in.id_;
|
||||||
|
out.chat = in.chat_id_;
|
||||||
|
out.sender = getSender(*in.sender_id_);
|
||||||
|
out.text = "";
|
||||||
|
out.type = convertMessageTypeandText(*in.content_, out.text);
|
||||||
|
out.isDeleted = false;
|
||||||
|
out.isOutgoing = in.is_outgoing_;
|
||||||
|
out.isPinned = in.is_pinned_;
|
||||||
|
out.isEditable = in.can_be_edited_;
|
||||||
|
out.isForwardable = in.can_be_forwarded_;
|
||||||
|
out.sendDate = in.date_;
|
||||||
|
out.editDate = in.edit_date_;
|
||||||
|
out.messageThreadId = in.message_thread_id_;
|
||||||
|
out.viaBotid = in.via_bot_user_id_;
|
||||||
|
out.mediaAlbumId = in.media_album_id_;
|
||||||
|
|
||||||
|
ReplyMarkup* markup = convertReplyMarkup(in.reply_markup_.get());
|
||||||
|
if(markup) {
|
||||||
|
out.replyMarkup = std::shared_ptr<ReplyMarkup>(markup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define CHECKCAST(TYPE, MTYPE, ACCESS) case td_api::TYPE::ID: \
|
||||||
|
textout = static_cast<const td_api::TYPE&>(cont).ACCESS; \
|
||||||
|
return MessageType::MTYPE
|
||||||
|
#define ECHECKCAST(TYPE, MTYPE) case td_api::TYPE::ID: \
|
||||||
|
return MessageType::MTYPE
|
||||||
|
|
||||||
|
MessageType convertMessageTypeandText(const td_api::MessageContent& cont, std::string& textout) {
|
||||||
|
switch(cont.get_id()) {
|
||||||
|
CHECKCAST(messageText, TEXT, text_->text_);
|
||||||
|
|
||||||
|
CHECKCAST(messageAnimation, ANIMATION, caption_->text_);
|
||||||
|
CHECKCAST(messageAudio, AUDIO, caption_->text_);
|
||||||
|
CHECKCAST(messageContact, CONTACT, contact_->first_name_);
|
||||||
|
CHECKCAST(messageDocument, DOCUMENT, caption_->text_);
|
||||||
|
ECHECKCAST(messageLocation, LOCATION);
|
||||||
|
CHECKCAST(messagePhoto, PHOTO, caption_->text_);
|
||||||
|
ECHECKCAST(messageExpiredPhoto, PHOTO);
|
||||||
|
CHECKCAST(messagePoll, POLL, poll_->question_);
|
||||||
|
ECHECKCAST(messageSticker, STICKER);
|
||||||
|
ECHECKCAST(messageDice, STICKER);
|
||||||
|
CHECKCAST(messageVideo, VIDEO, caption_->text_);
|
||||||
|
ECHECKCAST(messageExpiredVideo, VIDEO);
|
||||||
|
ECHECKCAST(messageVideoNote, VIDEO);
|
||||||
|
CHECKCAST(messageVenue, VENUE, venue_->title_);
|
||||||
|
CHECKCAST(messageVoiceNote, VOICE, caption_->text_);
|
||||||
|
ECHECKCAST(messageUnsupported, UNSUPPORTED);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return MessageType::SERVICE;
|
||||||
|
|
||||||
|
}
|
||||||
|
// unreachable
|
||||||
|
return MessageType::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CHECKCAST
|
||||||
|
#undef ECHECKCAST
|
||||||
|
|
||||||
|
ReplyMarkup* convertReplyMarkup(const td_api::ReplyMarkup* in) {
|
||||||
|
if(!in)
|
||||||
|
return nullptr;
|
||||||
|
if(in->get_id() == td_api::replyMarkupInlineKeyboard::ID) {
|
||||||
|
const td_api::replyMarkupInlineKeyboard* kyb = (const td_api::replyMarkupInlineKeyboard*) in;
|
||||||
|
InlineReplyMarkup* out = new InlineReplyMarkup();
|
||||||
|
|
||||||
|
// convertbuttons
|
||||||
|
for(uint32_t i = 0; i < kyb->rows_.size(); ++i) {
|
||||||
|
const std::vector<td_api::object_ptr<td_api::inlineKeyboardButton>>& row = kyb->rows_.at(i);
|
||||||
|
for(uint32_t j = 0; j < row.size(); ++j) {
|
||||||
|
InlineButton* btn = convertInlineButton(row.at(j).get());
|
||||||
|
if(btn)
|
||||||
|
out->buttons.push_back(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineButton* convertInlineButton(const td_api::inlineKeyboardButton* in) {
|
||||||
|
//construct button
|
||||||
|
InlineButton* btn = nullptr;
|
||||||
|
const td_api::InlineKeyboardButtonType* type = in->type_.get();
|
||||||
|
if(type->get_id() == td_api::inlineKeyboardButtonTypeCallback::ID) {
|
||||||
|
btn = new InlineCallbackButton();
|
||||||
|
((InlineCallbackButton*) btn)->data = ((const td_api::inlineKeyboardButtonTypeCallback*) type)->data_;
|
||||||
|
} else if(type->get_id() == td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID) {
|
||||||
|
btn = new InlineCallbackPasswordButton();
|
||||||
|
((InlineCallbackPasswordButton*) btn)->data = ((const td_api::inlineKeyboardButtonTypeCallbackWithPassword*) type)->data_;
|
||||||
|
} else if(type->get_id() == td_api::inlineKeyboardButtonTypeLoginUrl::ID) {
|
||||||
|
InlineLoginURLButton* lurlbtn = new InlineLoginURLButton();
|
||||||
|
const td_api::inlineKeyboardButtonTypeLoginUrl* casted = (const td_api::inlineKeyboardButtonTypeLoginUrl*) type;
|
||||||
|
lurlbtn->url = casted->url_;
|
||||||
|
lurlbtn->id = casted->id_;
|
||||||
|
lurlbtn->forwardText = casted->forward_text_;
|
||||||
|
btn = lurlbtn;
|
||||||
|
} else if(type->get_id() == td_api::inlineKeyboardButtonTypeSwitchInline::ID) {
|
||||||
|
InlineSwitchInlineButton* lsbtn = new InlineSwitchInlineButton();
|
||||||
|
const td_api::inlineKeyboardButtonTypeSwitchInline* casted = (const td_api::inlineKeyboardButtonTypeSwitchInline*) type;
|
||||||
|
lsbtn->query = casted->query_;
|
||||||
|
lsbtn->inCurrentChat = (casted->target_chat_->get_id() == td_api::targetChatCurrent::ID);
|
||||||
|
btn = lsbtn;
|
||||||
|
} else if(type->get_id() == td_api::inlineKeyboardButtonTypeUrl::ID) {
|
||||||
|
btn = new InlineURLButton();
|
||||||
|
((InlineURLButton*) btn)->url = ((const td_api::inlineKeyboardButtonTypeUrl*) type)->url_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(btn) {
|
||||||
|
btn->text = in->text_;
|
||||||
|
}
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool User::operator==(const User& u) const {
|
||||||
|
return (tgid == u.tgid && firstname == u.firstname && lastname == u.lastname && username == u.username && phonenumber == u.phonenumber && isContact == u.isContact && isMutalContact == u.isMutalContact && isVerified == u.isVerified && isSupport == u.isSupport && restriction == u.restriction && isScam == u.isScam && haveAccess == u.haveAccess && type == u.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool User::operator!=(const User& u) const {
|
||||||
|
return !operator==(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Message::operator==(const Message& m) const {
|
||||||
|
return (id == m.id && chat == m.chat && sender == m.sender && type == m.type && text == m.text && isDeleted == m.isDeleted && isOutgoing == m.isOutgoing && isPinned == m.isPinned && isForwardable == m.isForwardable && sendDate == m.sendDate && editDate == m.sendDate && viaBotid == m.viaBotid && mediaAlbumId == m.mediaAlbumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Message::operator!=(const Message& m) const {
|
||||||
|
return !operator==(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& getMessageText(const td_api::object_ptr<td_api::message>& msg) {
|
||||||
|
if(msg->content_->get_id() == td_api::messageText::ID) {
|
||||||
|
const std::string& text = ((const td_api::messageText&) *msg->content_).text_->text_;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
static const std::string EMPTY = "";
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define CHECKCAST(TYPE) case td_api::TYPE::ID: \
|
||||||
|
return &*static_cast<td_api::TYPE&>(cont).caption_;
|
||||||
|
td_api::formattedText* getMessageFormattedText(td_api::message& msg, bool& isText) {
|
||||||
|
if(!msg.content_) return nullptr;
|
||||||
|
|
||||||
|
td_api::MessageContent& cont = *msg.content_;
|
||||||
|
isText = false;
|
||||||
|
switch(cont.get_id()) {
|
||||||
|
CHECKCAST(messageAnimation);
|
||||||
|
CHECKCAST(messageAudio);
|
||||||
|
CHECKCAST(messageDocument);
|
||||||
|
CHECKCAST(messagePhoto);
|
||||||
|
CHECKCAST(messageVideo);
|
||||||
|
CHECKCAST(messageVoiceNote);
|
||||||
|
case td_api::messageText::ID:
|
||||||
|
isText = true;
|
||||||
|
return &*static_cast<td_api::messageText&>(cont).text_;
|
||||||
|
}
|
||||||
|
|
||||||
|
//default
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#undef CHECKCAST
|
|
@ -0,0 +1,49 @@
|
||||||
|
#include "userstatus.h"
|
||||||
|
|
||||||
|
#include <td/telegram/td_api.h>
|
||||||
|
namespace td_api = td::td_api;
|
||||||
|
|
||||||
|
#define CASERETURN(TGTYPE, ENUMTYPE) case td_api::TGTYPE::ID: return UserStatus::ENUMTYPE
|
||||||
|
UserStatus::UserStatus UserStatus::getStatus(int32_t status) {
|
||||||
|
switch(status) {
|
||||||
|
CASERETURN(userStatusEmpty , EMPTY);
|
||||||
|
CASERETURN(userStatusOnline , ONLINE);
|
||||||
|
CASERETURN(userStatusOffline , OFFLINE);
|
||||||
|
CASERETURN(userStatusRecently , RECENTLY);
|
||||||
|
CASERETURN(userStatusLastWeek , LASTWEEK);
|
||||||
|
CASERETURN(userStatusLastMonth, LASTMONTH);
|
||||||
|
}
|
||||||
|
return UserStatus::EMPTY;
|
||||||
|
}
|
||||||
|
#undef CASERETURN
|
||||||
|
|
||||||
|
const std::string& UserStatus::statustoString(UserStatus status) {
|
||||||
|
static const std::string names[] {"empty", "online", "offline", "recently", "lastweek", "lastmonth"};
|
||||||
|
return names[(uint8_t) status];
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CASERETURN(TGTYPE, ENUMTYPE) case td_api::TGTYPE::ID: return ChatAction::ENUMTYPE
|
||||||
|
ChatAction::ChatAction ChatAction::getAction(int32_t status) {
|
||||||
|
switch(status) {
|
||||||
|
CASERETURN(chatActionCancel , CANCEL);
|
||||||
|
CASERETURN(chatActionChoosingContact , CHOOSINGCONTACT);
|
||||||
|
CASERETURN(chatActionChoosingLocation , CHOOSINGLOCATION);
|
||||||
|
CASERETURN(chatActionRecordingVideo , RECORDINGVIDEO);
|
||||||
|
CASERETURN(chatActionRecordingVideoNote, RECORDINGVIDEONOTE);
|
||||||
|
CASERETURN(chatActionRecordingVoiceNote, RECORDINGVOICENOTE);
|
||||||
|
CASERETURN(chatActionStartPlayingGame , PLAYGAME);
|
||||||
|
CASERETURN(chatActionTyping , TYPING);
|
||||||
|
CASERETURN(chatActionUploadingDocument , UPLOADINGDOCUMENT);
|
||||||
|
CASERETURN(chatActionUploadingPhoto , UPLOADINGPHOTO);
|
||||||
|
CASERETURN(chatActionUploadingVideo , UPLODAINGVIDEO);
|
||||||
|
CASERETURN(chatActionUploadingVideoNote, UPLOADINGVIDEONOTE);
|
||||||
|
CASERETURN(chatActionUploadingVoiceNote, UPLOADINGVOICENOTE);
|
||||||
|
}
|
||||||
|
return ChatAction::CANCEL;
|
||||||
|
}
|
||||||
|
#undef CASERETURN
|
||||||
|
|
||||||
|
const std::string& ChatAction::actiontoString(ChatAction action) {
|
||||||
|
static const std::string names[] {"CANCEL", "CHOOSINGCONTACT ", "CHOOSINGLOCATION ", "RECORDINGVIDEO ", "RECORDINGVIDEONOTE ", "RECORDINGVOICENOTE ", "PLAYGAME ", "TYPING ", "UPLOADINGDOCUMENT ", "UPLOADINGPHOTO ", "UPLODAINGVIDEO ", "UPLOADINGVIDEONOTE ", "UPLOADINGVOICENOTE"};
|
||||||
|
return names[action];
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#include "view.h"
|
||||||
|
|
||||||
|
View::View(TgTUI& tgtui) : tgtui(tgtui) {}
|
||||||
|
|
||||||
|
View::~View() {}
|
||||||
|
|
||||||
|
void View::open() {}
|
||||||
|
|
||||||
|
void View::close() {}
|
||||||
|
|
||||||
|
bool View::keyIn(int) {
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
#include "viewchatlist.h"
|
||||||
|
|
||||||
|
#include <curses.h>
|
||||||
|
|
||||||
|
#include "tgtui.h"
|
||||||
|
|
||||||
|
ViewChatList::ViewChatList(TgTUI& tgtui) : View(tgtui) {}
|
||||||
|
|
||||||
|
void ViewChatList::open() {
|
||||||
|
chats = tgtui.getChats();
|
||||||
|
currentChatOffset = 0;
|
||||||
|
selectedChatRow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewChatList::close() {
|
||||||
|
chats.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewChatList::paint() {
|
||||||
|
::printw("Chats:\n");
|
||||||
|
|
||||||
|
getmaxyx(stdscr, maxRows, maxCols);
|
||||||
|
for(int row = 1; row < maxRows-1 && currentChatOffset + row < chats.size(); ++row) {
|
||||||
|
int selection = ' ';
|
||||||
|
const bool currentRowSelected = (row == selectedChatRow + currentChatOffset +1);
|
||||||
|
if(currentRowSelected) {
|
||||||
|
selection = '#';
|
||||||
|
attron(A_REVERSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
::mvprintw(row, 0, "%c %s\n", selection, chats.at(currentChatOffset + row -1).name.c_str());
|
||||||
|
|
||||||
|
if(currentRowSelected) {
|
||||||
|
attroff(A_REVERSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewChatList::keyIn(int key) {
|
||||||
|
switch (key) {
|
||||||
|
case KEY_UP:
|
||||||
|
if (selectedChatRow > 0) {
|
||||||
|
selectedChatRow--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_DOWN:
|
||||||
|
if (selectedChatRow < maxRows - 2) {
|
||||||
|
selectedChatRow++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_RIGHT:
|
||||||
|
case KEY_ENTER:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t ViewChatList::getSelectedChatId() {
|
||||||
|
return chats.at(selectedChatRow + currentChatOffset).chatId;
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define RED "\033[1;91m"
|
||||||
|
#define GREEN "\033[1;92m"
|
||||||
|
#define YELLOW "\033[1;93m"
|
||||||
|
#define AQUA "\033[1;36m"
|
||||||
|
#define GRAY "\033[1;38;5;244m"
|
||||||
|
#define RESET "\033[;1m"
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
const std::chrono::time_point<std::chrono::high_resolution_clock> start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
testdef* startit = &__start_testlist, *endit = &__stop_testlist;
|
||||||
|
int failcount = 0;
|
||||||
|
int skipcount = 0;
|
||||||
|
int testcount = endit-startit;
|
||||||
|
int testnumber = 0;
|
||||||
|
|
||||||
|
// get the maximum length of a test name
|
||||||
|
int testNameMaxLen = 0;
|
||||||
|
for(testdef* it = startit; it != endit; ++it) {
|
||||||
|
testNameMaxLen = std::max<int>(testNameMaxLen, std::string(it->name).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// go through back -> front (tests are inserted in reverse order)
|
||||||
|
for(testdef* it = startit + testcount-1; it >= startit; --it) {
|
||||||
|
const std::string testName(it->name);
|
||||||
|
const std::string namePadding((int) (testNameMaxLen - testName.size()) + 1, ' ');
|
||||||
|
std::cout << RESET "Running test:" << std::setfill(' ') << std::setw(std::log10(testcount)+2) << ++testnumber << '/' << testcount << " " AQUA << testName << RESET << namePadding;
|
||||||
|
|
||||||
|
// run test
|
||||||
|
int result = TESTFAILED;
|
||||||
|
const std::chrono::time_point<std::chrono::high_resolution_clock> testStart = std::chrono::high_resolution_clock::now();
|
||||||
|
try {
|
||||||
|
result = (it->testf)();
|
||||||
|
} catch(std::exception& e) {
|
||||||
|
std::cout << "catched exception: \"" << e.what() << "\" " << std::flush;
|
||||||
|
} catch(...) {}
|
||||||
|
const std::chrono::time_point<std::chrono::high_resolution_clock> testEnd = std::chrono::high_resolution_clock::now();
|
||||||
|
const std::chrono::duration<double, std::milli> testDuration(testEnd-testStart);
|
||||||
|
|
||||||
|
std::cout << std::fixed << std::setprecision(1);
|
||||||
|
if(result == TESTGOOD) {
|
||||||
|
std::cout << GREEN "succeeded" RESET "! " GRAY << testDuration.count() << "ms" RESET << std::endl;
|
||||||
|
} else if(result == TESTSKIPPED) {
|
||||||
|
std::cout << YELLOW " skipped" RESET "! " GRAY << testDuration.count() << "ms" RESET << std::endl;
|
||||||
|
skipcount++;
|
||||||
|
} else {
|
||||||
|
std::cout << RED " failed" RESET "! " GRAY << testDuration.count() << "ms" RESET << std::endl;
|
||||||
|
failcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* color = (failcount > 0 ? RED : GREEN); // red or green
|
||||||
|
std::cout << color << failcount << RESET "/" << testcount << " failed (" YELLOW << skipcount << RESET " skipped)" << std::endl;
|
||||||
|
|
||||||
|
const std::chrono::time_point<std::chrono::high_resolution_clock> end = std::chrono::high_resolution_clock::now();
|
||||||
|
const std::chrono::duration<double, std::milli> t = end - start;
|
||||||
|
std::cout << "Testing took: " << t.count() << "ms" << std::endl;
|
||||||
|
|
||||||
|
return failcount > 0;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
|
// tests are executed top to bottom
|
||||||
|
|
||||||
|
TEST(ABC) {
|
||||||
|
CMPASSERT(1, true);
|
||||||
|
|
||||||
|
} TESTEND
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
#define TESTFAILED 0
|
||||||
|
#define TESTGOOD 1
|
||||||
|
#define TESTSKIPPED -1
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define TESTDATA "./tests/data/"
|
||||||
|
|
||||||
|
// very helpfull: https://mgalgs.io/2013/05/10/hacking-your-ELF-for-fun-and-profit.html
|
||||||
|
|
||||||
|
#define TESTNAME(NAME) test_##NAME
|
||||||
|
#define TESTFUNC(NAME) int TESTNAME(NAME)()
|
||||||
|
|
||||||
|
#define REGISTERTEST(NAME) static const testdef __test_ ## NAME \
|
||||||
|
__attribute((__section__("testlist"))) \
|
||||||
|
__attribute((__used__)) = { \
|
||||||
|
TESTNAME(NAME), \
|
||||||
|
#NAME, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TEST(NAME) static TESTFUNC(NAME); \
|
||||||
|
REGISTERTEST(NAME); \
|
||||||
|
TESTFUNC(NAME) {
|
||||||
|
|
||||||
|
#define TESTEND return TESTGOOD; } \
|
||||||
|
|
||||||
|
|
||||||
|
#define ASSERT(BED, ERR) if(!(BED)) { std::cout << __FILE__ << ":" << __LINE__ << " " << ERR << ' ' << std::flush; return TESTFAILED; }
|
||||||
|
#define CMPASSERTE(IS, SHOULD, ERR) if( !((IS) == (SHOULD))) { std::cout << __FILE__ << ":" << __LINE__ << " is: \"" << (IS) << "\" should: \"" << (SHOULD) << "\" "<< std::flush; return TESTFAILED; }
|
||||||
|
#define CMPASSERT(IS, SHOULD) CMPASSERTE(IS, SHOULD, "")
|
||||||
|
|
||||||
|
#define SKIPTEST return TESTSKIPPED
|
||||||
|
|
||||||
|
typedef int (*test_t)();
|
||||||
|
|
||||||
|
struct testdef {
|
||||||
|
test_t testf;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// linker generates this <3
|
||||||
|
extern struct testdef __start_testlist;
|
||||||
|
extern struct testdef __stop_testlist;
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c5274a56b8a306fd184e80bd023824e623c56c27
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 00258ccb4cef19e4d6b4392082c68755887d9c2f
|
Loading…
Reference in New Issue