purpleline.hpp 7.28 KB
#pragma once

#include <string>
#include <deque>

#include <cmds.h>
#include <debug.h>
#include <plugin.h>
#include <prpl.h>

#include "constants.hpp"
#include "thriftclient.hpp"
#include "httpclient.hpp"
#include "poller.hpp"
#include "pinverifier.hpp"

#define LINEPRPL_ID "prpl-mvirkkunen-line"

class ThriftClient;

template <typename T>
struct _blist_node_type { };

template <>
struct _blist_node_type<PurpleBuddy> {
    static const PurpleBlistNodeType type = PURPLE_BLIST_BUDDY_NODE;
    static PurpleAccount *get_account(PurpleBuddy *buddy) {
        return purple_buddy_get_account(buddy);
    }
};

template <>
struct _blist_node_type<PurpleChat> {
    static const PurpleBlistNodeType type = PURPLE_BLIST_CHAT_NODE;
    static PurpleAccount *get_account(PurpleChat *chat) {
        return purple_chat_get_account(chat);
    }
};

enum class ChatType {
    ANY = 0,
    GROUP = 1,
    ROOM = 2,
    GROUP_INVITE = 3,
};

class PurpleLine {

    struct Attachment {
        line::ContentType::type type;
        std::string id;
        std::string path;

        Attachment(line::ContentType::type type, std::string id)
            : type(type), id(id)
        {
        }
    };

    static std::map<ChatType, std::string> chat_type_to_string;

    PurpleConnection *conn;
    PurpleAccount *acct;

    boost::shared_ptr<ThriftClient> c_out;

    HTTPClient http;

    // Remove if libpurple HTTP ever gets support for binary request bodies
    LineHttpTransport os_http;

    friend class Poller;
    Poller poller;

    friend class PINVerifier;
    PINVerifier pin_verifier;

    int next_purple_id;

    std::deque<std::string> recent_messages;

    std::vector<std::string> temp_files;

    line::Profile profile;
    line::Contact profile_contact; // contains some fields from profile
    line::Contact no_contact; // empty object
    std::map<std::string, line::Group> groups;
    std::map<std::string, line::Room> rooms;
    std::map<std::string, line::Contact> contacts;

    void *pin_ui_handle;
    guint pin_timeout;

public:

    PurpleLine(PurpleConnection *conn, PurpleAccount *acct);
    ~PurpleLine();

    static void register_commands();
    static const char *list_icon(PurpleAccount *, PurpleBuddy *buddy);
    static GList *status_types(PurpleAccount *);
    static char *status_text(PurpleBuddy *buddy);
    static void tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full);
    static void login(PurpleAccount *acct);

    void close();
    int send_im(const char *who, const char *message, PurpleMessageFlags flags);
    void remove_buddy(PurpleBuddy *buddy, PurpleGroup *);

    PurpleCmdRet cmd_sticker(PurpleConversation *conv,
        const gchar *, gchar **args, gchar **error, void *);

    PurpleCmdRet cmd_history(PurpleConversation *conv,
        const gchar *, gchar **args, gchar **error, void *);

    PurpleCmdRet cmd_open(PurpleConversation *conv,
        const gchar *, gchar **args, gchar **error, void *);

private:

    static std::string markup_escape(std::string const &text);
    static std::string markup_unescape(std::string const &markup);
    static std::string url_encode(std::string const &str);

    static std::string get_sticker_id(line::Message &msg);
    static std::string get_sticker_url(line::Message &msg, bool thumb = false);

    void connect_signals();
    void disconnect_signals();

    std::string get_tmp_dir(bool create=false);

    std::string conv_attachment_add(PurpleConversation *conv,
        line::ContentType::type type, std::string id);
    Attachment *conv_attachment_get(PurpleConversation *conv, std::string token);

    void handle_message(line::Message &msg, bool replay);
    void write_message(PurpleConversation *conv, line::Message &msg,
        time_t mtime, int flags, std::string text);

    std::string get_room_display_name(line::Room &room);
    void set_chat_participants(PurpleConvChat *chat, line::Room &room);
    void set_chat_participants(PurpleConvChat *chat, line::Group &group);

    line::Contact &get_up_to_date_contact(line::Contact &c);

    int send_message(std::string to, const char *markup);
    void send_message(
        line::Message &msg,
        std::function<void(line::Message &msg)> callback=std::function<void(line::Message &)>());
    void upload_media(std::string message_id, std::string type, std::string data);
    void push_recent_message(std::string id);

    void signal_blist_node_removed(PurpleBlistNode *node);
    void signal_conversation_created(PurpleConversation *conv);
    void signal_deleting_conversation(PurpleConversation *conv);

    void fetch_conversation_history(PurpleConversation *conv, int count, bool requested);

    void notify_error(std::string msg);

    // sync

private:

    // Login process methods, executed in this this order
    void login_start();

    void get_auth_token();
    void set_auth_token(std::string auth_token);
    void get_last_op_revision();
    void get_profile();
    void get_contacts();
    void get_groups();
    void get_rooms();
    void update_rooms(line::MessageBoxWrapUpList wrap_up_list);
    void get_group_invites();

    void login_done();

    // blist

private:

    template <typename T>
    std::set<T *> blist_find(std::function<bool(T *)> predicate);

    template <typename T>
    std::set<T *> blist_find() {
        return blist_find<T>([](T *){ return true; });
    }

    PurpleGroup *blist_ensure_group(std::string group_name, bool temporary=false);
    PurpleBuddy *blist_ensure_buddy(std::string uid, bool temporary=false);
    void blist_update_buddy(std::string uid, bool temporary=false);
    PurpleBuddy *blist_update_buddy(line::Contact &contact, bool temporary=false);
    bool blist_is_buddy_in_any_conversation(std::string uid, PurpleConvChat *ignore_chat);
    void blist_remove_buddy(std::string uid,
        bool temporary_only=false, PurpleConvChat *ignore_chat=nullptr);

    std::set<PurpleChat *> blist_find_chats_by_type(ChatType type);
    PurpleChat *blist_find_chat(std::string id, ChatType type);
    PurpleChat *blist_ensure_chat(std::string id, ChatType type);
    void blist_update_chat(std::string id, ChatType type);
    PurpleChat *blist_update_chat(line::Group &group);
    PurpleChat *blist_update_chat(line::Room &room);
    void blist_remove_chat(std::string id, ChatType type);

    // chats

public:
    static char *get_chat_name(GHashTable *components);

    GList *chat_info();
    void join_chat(GHashTable *components);
    void reject_chat(GHashTable *components);
    void chat_leave(int id);
    int chat_send(int id, const char *message, PurpleMessageFlags flags);
    PurpleChat *find_blist_chat(const char *name);

private:

    static ChatType get_chat_type(const char *type_ptr);

    void join_chat_success(ChatType type, std::string id);

    void handle_group_invite(line::Group &group, line::Contact &invitee, line::Contact &inviter);
};

template <typename T>
std::set<T *> PurpleLine::blist_find(std::function<bool(T *)> predicate) {
    std::set<T *> results;

    for (PurpleBlistNode *node = purple_blist_get_root();
        node;
        node = purple_blist_node_next(node, FALSE))
    {
        if (_blist_node_type<T>::get_account((T *)node) == acct
            && purple_blist_node_get_type(node) == _blist_node_type<T>::type
            && predicate((T *)node))
        {
            results.insert((T *)node);
        }
    }

    return results;
}