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