Commit 61ed212df3b90c1b44a62e835f8a7feb68c2acc8

Authored by Matti Virkkunen
1 parent 963524c5

Split purpleline.cpp up even more.

Makefile
... ... @@ -14,7 +14,8 @@ MAIN = libline.so
14 14 GEN_SRCS = thrift_line/line_constants.cpp thrift_line/line_types.cpp \
15 15 thrift_line/TalkService.cpp
16 16 REAL_SRCS = pluginmain.cpp linehttptransport.cpp thriftclient.cpp httpclient.cpp \
17   - purpleline.cpp purpleline_login.cpp purpleline_blist.cpp purpleline_chats.cpp \
  17 + purpleline.cpp purpleline_blist.cpp purpleline_chats.cpp purpleline_cmds.cpp \
  18 + purpleline_login.cpp purpleline_write.cpp \
18 19 poller.cpp pinverifier.cpp
19 20 SRCS += $(GEN_SRCS)
20 21 SRCS += $(REAL_SRCS)
... ...
poller.cpp
... ... @@ -108,11 +108,8 @@ void Poller::fetch_operations() {
108 108 parent.blist_update_chat(op.param1, ChatType::ROOM);
109 109  
110 110 case line::OpType::SEND_MESSAGE: // 25
111   - parent.handle_message(op.message, false);
112   - break;
113   -
114 111 case line::OpType::RECEIVE_MESSAGE: // 26
115   - parent.handle_message(op.message, false);
  112 + parent.write_message(op.message, false);
116 113 break;
117 114  
118 115 case line::OpType::CANCEL_INVITATION_GROUP: // 31
... ...
purpleline.cpp
... ... @@ -20,7 +20,7 @@
20 20 #include "purpleline.hpp"
21 21 #include "wrapper.hpp"
22 22  
23   -std::string PurpleLine::markup_escape(std::string const &text) {
  23 +std::string markup_escape(std::string const &text) {
24 24 gchar *escaped = purple_markup_escape_text(text.c_str(), text.size());
25 25 std::string result(escaped);
26 26 g_free(escaped);
... ... @@ -28,7 +28,7 @@ std::string PurpleLine::markup_escape(std::string const &text) {
28 28 return result;
29 29 }
30 30  
31   -std::string PurpleLine::markup_unescape(std::string const &markup) {
  31 +std::string markup_unescape(std::string const &markup) {
32 32 gchar *unescaped = purple_unescape_html(markup.c_str());
33 33 std::string result(unescaped);
34 34 g_free(unescaped);
... ... @@ -36,54 +36,10 @@ std::string PurpleLine::markup_unescape(std::string const &markup) {
36 36 return result;
37 37 }
38 38  
39   -std::string PurpleLine::url_encode(std::string const &str) {
  39 +std::string url_encode(std::string const &str) {
40 40 return purple_url_encode(str.c_str());
41 41 }
42 42  
43   -std::string PurpleLine::get_sticker_id(line::Message &msg) {
44   - std::map<std::string, std::string> &meta = msg.contentMetadata;
45   -
46   - if (meta.count("STKID") == 0 || meta.count("STKVER") == 0 || meta.count("STKPKGID") == 0)
47   - return "";
48   -
49   - std::stringstream id;
50   -
51   - id << "[LINE sticker "
52   - << meta["STKVER"] << "/"
53   - << meta["STKPKGID"] << "/"
54   - << meta["STKID"];
55   -
56   - if (meta.count("STKTXT") == 1)
57   - id << " " << meta["STKTXT"];
58   -
59   - id << "]";
60   -
61   - return id.str();
62   -}
63   -
64   -std::string PurpleLine::get_sticker_url(line::Message &msg, bool thumb) {
65   - std::map<std::string, std::string> &meta = msg.contentMetadata;
66   -
67   - int ver;
68   - std::stringstream ss(meta["STKVER"]);
69   - ss >> ver;
70   -
71   - std::stringstream url;
72   -
73   - url << LINE_STICKER_URL
74   - << (ver / 1000000) << "/" << (ver / 1000) << "/" << (ver % 1000) << "/"
75   - << meta["STKPKGID"] << "/"
76   - << "PC/stickers/"
77   - << meta["STKID"];
78   -
79   - if (thumb)
80   - url << "_key";
81   -
82   - url << ".png";
83   -
84   - return url.str();
85   -}
86   -
87 43 PurpleLine::PurpleLine(PurpleConnection *conn, PurpleAccount *acct) :
88 44 conn(conn),
89 45 acct(acct),
... ... @@ -101,39 +57,6 @@ PurpleLine::~PurpleLine() {
101 57 c_out->close();
102 58 }
103 59  
104   -void PurpleLine::register_commands() {
105   - purple_cmd_register(
106   - "sticker",
107   - "w",
108   - PURPLE_CMD_P_PRPL,
109   - (PurpleCmdFlag)(PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT),
110   - LINE_PRPL_ID,
111   - WRAPPER(PurpleLine::cmd_sticker),
112   - "Sends a sticker. The argument should be of the format VER/PKGID/ID.",
113   - nullptr);
114   -
115   - purple_cmd_register(
116   - "history",
117   - "w",
118   - PURPLE_CMD_P_PRPL,
119   - (PurpleCmdFlag)
120   - (PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS),
121   - LINE_PRPL_ID,
122   - WRAPPER(PurpleLine::cmd_history),
123   - "Shows more chat history. Optional argument specifies number of messages to show.",
124   - nullptr);
125   -
126   - purple_cmd_register(
127   - "open",
128   - "w",
129   - PURPLE_CMD_P_PRPL,
130   - (PurpleCmdFlag)(PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT),
131   - LINE_PRPL_ID,
132   - WRAPPER(PurpleLine::cmd_open),
133   - "Opens an attachment (image, audio) by number.",
134   - nullptr);
135   -}
136   -
137 60 const char *PurpleLine::list_icon(PurpleAccount *, PurpleBuddy *) {
138 61 return "line";
139 62 }
... ... @@ -325,260 +248,6 @@ PurpleLine::Attachment *PurpleLine::conv_attachment_get(PurpleConversation *conv
325 248 return (atts && index <= (int)atts->size()) ? &(*atts)[index - 1] : nullptr;
326 249 }
327 250  
328   -void PurpleLine::handle_message(line::Message &msg, bool replay) {
329   - std::string text;
330   - int flags = 0;
331   - time_t mtime = (time_t)(msg.createdTime / 1000);
332   -
333   - bool sent = (msg.from == profile.mid);
334   -
335   - if (std::find(recent_messages.cbegin(), recent_messages.cend(), msg.id)
336   - != recent_messages.cend())
337   - {
338   - // We already processed this message. User is probably talking with himself.
339   - return;
340   - }
341   -
342   - // Hack
343   - if (msg.from == msg.to)
344   - push_recent_message(msg.id);
345   -
346   - PurpleConversation *conv = purple_find_conversation_with_account(
347   - (msg.toType == line::MIDType::USER ? PURPLE_CONV_TYPE_IM : PURPLE_CONV_TYPE_CHAT),
348   - ((!sent && msg.toType == line::MIDType::USER) ? msg.from.c_str() : msg.to.c_str()),
349   - acct);
350   -
351   - // If this is a new received IM, create the conversation if it doesn't exist
352   - if (!conv && !sent && msg.toType == line::MIDType::USER)
353   - conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, msg.from.c_str());
354   -
355   - // If this is a new conversation, we're not replaying history and history hasn't been fetched
356   - // yet, queue the message instead of showing it.
357   - if (conv && !replay) {
358   - auto *queue = (std::vector<line::Message> *)
359   - purple_conversation_get_data(conv, "line-message-queue");
360   -
361   - if (queue) {
362   - queue->push_back(msg);
363   - return;
364   - }
365   - }
366   -
367   - // Replaying messages from history
368   - // Unfortunately Pidgin displays messages with this flag with odd formatting and no username.
369   - // Disable for now.
370   - //if (replay)
371   - // flags |= PURPLE_MESSAGE_NO_LOG;
372   -
373   - switch (msg.contentType) {
374   - case line::ContentType::NONE: // actually text
375   - case line::ContentType::LOCATION:
376   - if (msg.__isset.location) {
377   - line::Location &loc = msg.location;
378   -
379   - text = markup_escape(loc.title)
380   - + " | <a href=\"https://maps.google.com/?q=" + url_encode(loc.address)
381   - + "&ll=" + std::to_string(loc.latitude)
382   - + "," + std::to_string(loc.longitude)
383   - + "\">"
384   - + (loc.address.size()
385   - ? markup_escape(loc.address)
386   - : "(no address)")
387   - + "</a>";
388   - } else {
389   - text = markup_escape(msg.text);
390   - }
391   - break;
392   -
393   - case line::ContentType::STICKER:
394   - {
395   - std::string id = get_sticker_id(msg);
396   -
397   - if (id == "") {
398   - text = "<em>[Broken sticker]</em>";
399   -
400   - purple_debug_warning("line", "Got a broken sticker.\n");
401   - } else {
402   - text = id;
403   -
404   - if (conv
405   - && purple_conv_custom_smiley_add(conv, id.c_str(), "id", id.c_str(), TRUE))
406   - {
407   - http.request(get_sticker_url(msg),
408   - [this, id, conv](int status, const guchar *data, gsize len)
409   - {
410   - if (status == 200 && data && len > 0) {
411   - purple_conv_custom_smiley_write(
412   - conv,
413   - id.c_str(),
414   - data,
415   - len);
416   - } else {
417   - purple_debug_warning(
418   - "line",
419   - "Couldn't download sticker. Status: %d\n",
420   - status);
421   - }
422   -
423   - purple_conv_custom_smiley_close(conv, id.c_str());
424   - });
425   - }
426   - }
427   - }
428   - break;
429   -
430   - case line::ContentType::IMAGE:
431   - case line::ContentType::VIDEO: // Videos could really benefit from streaming...
432   - {
433   - std::string type_std = line::_ContentType_VALUES_TO_NAMES.at(msg.contentType);
434   -
435   - std::string id = "[LINE " + type_std + " " + msg.id + "]";
436   -
437   - text = id;
438   -
439   - if (conv) {
440   - text += " <font color=\"#888888\">/open "
441   - + conv_attachment_add(conv, msg.contentType, msg.id)
442   - + "</font>";
443   - }
444   -
445   - if (!conv
446   - || !purple_conv_custom_smiley_add(conv, id.c_str(), "id", id.c_str(), TRUE))
447   - {
448   - break;
449   - }
450   -
451   - if (msg.contentPreview.size() > 0) {
452   - purple_conv_custom_smiley_write(
453   - conv,
454   - id.c_str(),
455   - (const guchar *)msg.contentPreview.c_str(),
456   - msg.contentPreview.size());
457   -
458   - purple_conv_custom_smiley_close(conv, id.c_str());
459   - } else {
460   - std::string preview_url = msg.contentMetadata.count("PREVIEW_URL")
461   - ? msg.contentMetadata["PREVIEW_URL"]
462   - : std::string(LINE_OS_URL) + "os/m/" + msg.id + "/preview";
463   -
464   - http.request(preview_url, HTTPFlag::AUTH | HTTPFlag::LARGE,
465   - [this, id, conv](int status, const guchar *data, gsize len)
466   - {
467   - if (status == 200 && data && len > 0) {
468   - purple_conv_custom_smiley_write(
469   - conv,
470   - id.c_str(),
471   - data,
472   - len);
473   - } else {
474   - purple_debug_warning(
475   - "line",
476   - "Couldn't download image message. Status: %d\n",
477   - status);
478   - }
479   -
480   - purple_conv_custom_smiley_close(conv, id.c_str());
481   - });
482   - }
483   - }
484   - break;
485   -
486   - case line::ContentType::AUDIO:
487   - {
488   - text = "[Audio message";
489   -
490   - if (msg.contentMetadata.count("AUDLEN")) {
491   - int len = 0;
492   -
493   - try {
494   - len = std::stoi(msg.contentMetadata["AUDLEN"]);
495   - } catch(...) { /* ignore */ }
496   -
497   - if (len > 0) {
498   - text += " "
499   - + std::to_string(len / 1000)
500   - + "."
501   - + std::to_string((len % 1000) / 100)
502   - + "s";
503   - }
504   - }
505   -
506   - text += "]";
507   -
508   - if (conv) {
509   - text += " <font color=\"#888888\">/open "
510   - + conv_attachment_add(conv, msg.contentType, msg.id)
511   - + "</font>";
512   - }
513   - }
514   - break;
515   -
516   - // TODO: other content types
517   -
518   - default:
519   - text = "<em>[Not implemented: ";
520   - text += line::_ContentType_VALUES_TO_NAMES.at(msg.contentType);
521   - text += " message]</em>";
522   - break;
523   - }
524   -
525   - if (sent) {
526   - // Messages sent by user (sync from other devices)
527   -
528   - write_message(conv, msg, mtime, flags | PURPLE_MESSAGE_SEND, text);
529   - } else {
530   - // Messages received from other users
531   -
532   - flags |= PURPLE_MESSAGE_RECV;
533   -
534   - if (replay) {
535   - // Write replayed messages instead of serv_got_* to avoid Pidgin's IM sound
536   -
537   - write_message(conv, msg, mtime, flags, text);
538   - } else {
539   - if (msg.toType == line::MIDType::USER) {
540   - serv_got_im(
541   - conn,
542   - msg.from.c_str(),
543   - text.c_str(),
544   - (PurpleMessageFlags)flags,
545   - mtime);
546   - } else if (msg.toType == line::MIDType::GROUP || msg.toType == line::MIDType::ROOM) {
547   - serv_got_chat_in(
548   - conn,
549   - purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)),
550   - msg.from.c_str(),
551   - (PurpleMessageFlags)flags,
552   - text.c_str(),
553   - mtime);
554   - }
555   - }
556   - }
557   -}
558   -
559   -void PurpleLine::write_message(PurpleConversation *conv, line::Message &msg,
560   - time_t mtime, int flags, std::string text)
561   -{
562   - if (!conv)
563   - return;
564   -
565   - if (msg.toType == line::MIDType::USER) {
566   - purple_conv_im_write(
567   - PURPLE_CONV_IM(conv),
568   - msg.from.c_str(),
569   - text.c_str(),
570   - (PurpleMessageFlags)flags,
571   - mtime);
572   - } else if (msg.toType == line::MIDType::GROUP || msg.toType == line::MIDType::ROOM) {
573   - purple_conv_chat_write(
574   - PURPLE_CONV_CHAT(conv),
575   - msg.from.c_str(),
576   - text.c_str(),
577   - (PurpleMessageFlags)flags,
578   - mtime);
579   - }
580   -}
581   -
582 251 line::Contact &PurpleLine::get_up_to_date_contact(line::Contact &c) {
583 252 return (contacts.count(c.mid) != 0) ? contacts[c.mid] : c;
584 253 }
... ... @@ -900,7 +569,7 @@ void PurpleLine::fetch_conversation_history(PurpleConversation *conv, int count,
900 569 time(NULL));
901 570  
902 571 for (auto msgi = recent_msgs.rbegin(); msgi != recent_msgs.rend(); msgi++)
903   - handle_message(*msgi, true);
  572 + write_message(*msgi, true);
904 573  
905 574 purple_conversation_write(
906 575 conv,
... ... @@ -924,7 +593,7 @@ void PurpleLine::fetch_conversation_history(PurpleConversation *conv, int count,
924 593 // If there's a message queue, play it back now
925 594 if (queue) {
926 595 for (line::Message &msg: *queue)
927   - handle_message(msg, false);
  596 + write_message(msg, false);
928 597  
929 598 delete queue;
930 599 }
... ... @@ -962,147 +631,6 @@ void PurpleLine::signal_deleting_conversation(PurpleConversation *conv) {
962 631 }
963 632 }
964 633  
965   -static const char *sticker_fields[] = { "STKVER", "STKPKGID", "STKID" };
966   -
967   -PurpleCmdRet PurpleLine::cmd_sticker(PurpleConversation *conv,
968   - const gchar *, gchar **args, gchar **error, void *)
969   -{
970   - line::Message msg;
971   -
972   - std::stringstream ss(args[0]);
973   - std::string item;
974   -
975   - int part = 0;
976   - while (std::getline(ss, item, '/')) {
977   - if (part == 3) {
978   - *error = g_strdup("Invalid sticker.");
979   - return PURPLE_CMD_RET_FAILED;
980   - }
981   -
982   - msg.contentMetadata[sticker_fields[part]] = item;
983   -
984   - part++;
985   - }
986   -
987   - if (part != 3) {
988   - *error = g_strdup("Invalid sticker.");
989   - return PURPLE_CMD_RET_FAILED;
990   - }
991   -
992   - msg.contentType = line::ContentType::STICKER;
993   - msg.from = profile.mid;
994   - msg.to = purple_conversation_get_name(conv);
995   -
996   - handle_message(msg, false);
997   -
998   - send_message(msg);
999   -
1000   - return PURPLE_CMD_RET_OK;
1001   -}
1002   -
1003   -PurpleCmdRet PurpleLine::cmd_history(PurpleConversation *conv,
1004   - const gchar *, gchar **args, gchar **error, void *)
1005   -{
1006   - int count = 10;
1007   -
1008   - if (args[0]) {
1009   - try {
1010   - count = std::stoi(args[0]);
1011   - } catch (...) {
1012   - *error = g_strdup("Invalid message count.");
1013   - return PURPLE_CMD_RET_FAILED;
1014   - }
1015   - }
1016   -
1017   - fetch_conversation_history(conv, count, true);
1018   -
1019   - return PURPLE_CMD_RET_OK;
1020   -}
1021   -
1022   -static std::map<line::ContentType::type, std::string> attachment_extensions = {
1023   - { line::ContentType::IMAGE, ".jpg" },
1024   - { line::ContentType::VIDEO, ".mp4" },
1025   - { line::ContentType::AUDIO, ".mp3" },
1026   -};
1027   -
1028   -PurpleCmdRet PurpleLine::cmd_open(PurpleConversation *conv,
1029   - const gchar *, gchar **args, gchar **error, void *)
1030   -{
1031   - std::string token(args[0]);
1032   -
1033   - Attachment *att = conv_attachment_get(conv, token);
1034   - if (!att) {
1035   - *error = g_strdup("No such attachment.");
1036   - return PURPLE_CMD_RET_FAILED;
1037   - }
1038   -
1039   - if (att->path != "" && g_file_test(att->path.c_str(), G_FILE_TEST_EXISTS)) {
1040   - purple_notify_uri(conn, att->path.c_str());
1041   - return PURPLE_CMD_RET_OK;
1042   - }
1043   -
1044   - // Ensure there's nothing funny about the id as we're going to use it as a path element
1045   - try {
1046   - std::stoll(att->id);
1047   - } catch (...) {
1048   - *error = g_strdup("Failed to download attachment.");
1049   - return PURPLE_CMD_RET_FAILED;
1050   - }
1051   -
1052   - std::string ext = ".jpg";
1053   - if (attachment_extensions.count(att->type))
1054   - ext = attachment_extensions[att->type];
1055   -
1056   - std::string dir = get_tmp_dir(true);
1057   -
1058   - gchar *path_p = g_build_filename(
1059   - dir.c_str(),
1060   - (att->id + ext).c_str(),
1061   - nullptr);
1062   -
1063   - std::string path(path_p);
1064   -
1065   - g_free(path_p);
1066   -
1067   - purple_conversation_write(
1068   - conv,
1069   - "",
1070   - "Downloading attachment...",
1071   - (PurpleMessageFlags)PURPLE_MESSAGE_SYSTEM,
1072   - time(NULL));
1073   -
1074   - std::string url = std::string(LINE_OS_URL) + "os/m/"+ att->id;
1075   -
1076   - PurpleConversationType ctype = purple_conversation_get_type(conv);
1077   - std::string cname = std::string(purple_conversation_get_name(conv));
1078   -
1079   - http.request(url, HTTPFlag::AUTH | HTTPFlag::LARGE,
1080   - [this, path, token, ctype, cname]
1081   - (int status, const guchar *data, gsize len)
1082   - {
1083   - if (status == 200 && data && len > 0) {
1084   - g_file_set_contents(path.c_str(), (const char *)data, len, nullptr);
1085   -
1086   - temp_files.push_back(path);
1087   -
1088   - PurpleConversation *conv = purple_find_conversation_with_account(
1089   - ctype, cname.c_str(), acct);
1090   -
1091   - if (conv) {
1092   - Attachment *att = conv_attachment_get(conv, token);
1093   - if (att)
1094   - att->path = path;
1095   - }
1096   -
1097   - purple_notify_uri(conn, path.c_str());
1098   - } else {
1099   - notify_error("Failed to download attachment.");
1100   - }
1101   - });
1102   -
1103   - return PURPLE_CMD_RET_OK;
1104   -}
1105   -
1106 634 void PurpleLine::notify_error(std::string msg) {
1107 635 purple_notify_error(
1108 636 (void *)conn,
... ...
purpleline.hpp
... ... @@ -44,6 +44,12 @@ enum class ChatType {
44 44 GROUP_INVITE = 3,
45 45 };
46 46  
  47 +std::string markup_escape(std::string const &text);
  48 +
  49 +std::string markup_unescape(std::string const &markup);
  50 +
  51 +std::string url_encode(std::string const &str);
  52 +
47 53 class PurpleLine {
48 54  
49 55 struct Attachment {
... ... @@ -118,13 +124,6 @@ public:
118 124  
119 125 private:
120 126  
121   - static std::string markup_escape(std::string const &text);
122   - static std::string markup_unescape(std::string const &markup);
123   - static std::string url_encode(std::string const &str);
124   -
125   - static std::string get_sticker_id(line::Message &msg);
126   - static std::string get_sticker_url(line::Message &msg, bool thumb = false);
127   -
128 127 void connect_signals();
129 128 void disconnect_signals();
130 129  
... ... @@ -134,9 +133,9 @@ private:
134 133 line::ContentType::type type, std::string id);
135 134 Attachment *conv_attachment_get(PurpleConversation *conv, std::string token);
136 135  
137   - void handle_message(line::Message &msg, bool replay);
138   - void write_message(PurpleConversation *conv, line::Message &msg,
139   - time_t mtime, int flags, std::string text);
  136 + void write_message(line::Message &msg, bool replay);
  137 + void write_message(PurpleConversation *conv, std::string &from, std::string &text,
  138 + time_t mtime, int flags);
140 139  
141 140 std::string get_room_display_name(line::Room &room);
142 141 void set_chat_participants(PurpleConvChat *chat, line::Room &room);
... ...
purpleline_cmds.cpp 0 → 100644
  1 +#include "purpleline.hpp"
  2 +
  3 +void PurpleLine::register_commands() {
  4 + purple_cmd_register(
  5 + "sticker",
  6 + "w",
  7 + PURPLE_CMD_P_PRPL,
  8 + (PurpleCmdFlag)(PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT),
  9 + LINE_PRPL_ID,
  10 + WRAPPER(PurpleLine::cmd_sticker),
  11 + "Sends a sticker. The argument should be of the format VER/PKGID/ID.",
  12 + nullptr);
  13 +
  14 + purple_cmd_register(
  15 + "history",
  16 + "w",
  17 + PURPLE_CMD_P_PRPL,
  18 + (PurpleCmdFlag)
  19 + (PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS),
  20 + LINE_PRPL_ID,
  21 + WRAPPER(PurpleLine::cmd_history),
  22 + "Shows more chat history. Optional argument specifies number of messages to show.",
  23 + nullptr);
  24 +
  25 + purple_cmd_register(
  26 + "open",
  27 + "w",
  28 + PURPLE_CMD_P_PRPL,
  29 + (PurpleCmdFlag)(PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT),
  30 + LINE_PRPL_ID,
  31 + WRAPPER(PurpleLine::cmd_open),
  32 + "Opens an attachment (image, audio) by number.",
  33 + nullptr);
  34 +}
  35 +
  36 +PurpleCmdRet PurpleLine::cmd_sticker(PurpleConversation *conv,
  37 + const gchar *, gchar **args, gchar **error, void *)
  38 +{
  39 + static const char *sticker_fields[] = { "STKVER", "STKPKGID", "STKID" };
  40 +
  41 + line::Message msg;
  42 +
  43 + std::stringstream ss(args[0]);
  44 + std::string item;
  45 +
  46 + int part = 0;
  47 + while (std::getline(ss, item, '/')) {
  48 + if (part == 3) {
  49 + *error = g_strdup("Invalid sticker.");
  50 + return PURPLE_CMD_RET_FAILED;
  51 + }
  52 +
  53 + msg.contentMetadata[sticker_fields[part]] = item;
  54 +
  55 + part++;
  56 + }
  57 +
  58 + if (part != 3) {
  59 + *error = g_strdup("Invalid sticker.");
  60 + return PURPLE_CMD_RET_FAILED;
  61 + }
  62 +
  63 + msg.contentType = line::ContentType::STICKER;
  64 + msg.from = profile.mid;
  65 + msg.to = purple_conversation_get_name(conv);
  66 +
  67 + write_message(msg, false);
  68 +
  69 + send_message(msg);
  70 +
  71 + return PURPLE_CMD_RET_OK;
  72 +}
  73 +
  74 +PurpleCmdRet PurpleLine::cmd_history(PurpleConversation *conv,
  75 + const gchar *, gchar **args, gchar **error, void *)
  76 +{
  77 + int count = 10;
  78 +
  79 + if (args[0]) {
  80 + try {
  81 + count = std::stoi(args[0]);
  82 + } catch (...) {
  83 + *error = g_strdup("Invalid message count.");
  84 + return PURPLE_CMD_RET_FAILED;
  85 + }
  86 + }
  87 +
  88 + fetch_conversation_history(conv, count, true);
  89 +
  90 + return PURPLE_CMD_RET_OK;
  91 +}
  92 +
  93 +PurpleCmdRet PurpleLine::cmd_open(PurpleConversation *conv,
  94 + const gchar *, gchar **args, gchar **error, void *)
  95 +{
  96 + static std::map<line::ContentType::type, std::string> attachment_extensions = {
  97 + { line::ContentType::IMAGE, ".jpg" },
  98 + { line::ContentType::VIDEO, ".mp4" },
  99 + { line::ContentType::AUDIO, ".mp3" },
  100 + };
  101 +
  102 + std::string token(args[0]);
  103 +
  104 + Attachment *att = conv_attachment_get(conv, token);
  105 + if (!att) {
  106 + *error = g_strdup("No such attachment.");
  107 + return PURPLE_CMD_RET_FAILED;
  108 + }
  109 +
  110 + if (att->path != "" && g_file_test(att->path.c_str(), G_FILE_TEST_EXISTS)) {
  111 + purple_notify_uri(conn, att->path.c_str());
  112 + return PURPLE_CMD_RET_OK;
  113 + }
  114 +
  115 + // Ensure there's nothing funny about the id as we're going to use it as a path element
  116 + try {
  117 + std::stoll(att->id);
  118 + } catch (...) {
  119 + *error = g_strdup("Failed to download attachment.");
  120 + return PURPLE_CMD_RET_FAILED;
  121 + }
  122 +
  123 + std::string ext = ".jpg";
  124 + if (attachment_extensions.count(att->type))
  125 + ext = attachment_extensions[att->type];
  126 +
  127 + std::string dir = get_tmp_dir(true);
  128 +
  129 + gchar *path_p = g_build_filename(
  130 + dir.c_str(),
  131 + (att->id + ext).c_str(),
  132 + nullptr);
  133 +
  134 + std::string path(path_p);
  135 +
  136 + g_free(path_p);
  137 +
  138 + purple_conversation_write(
  139 + conv,
  140 + "",
  141 + "Downloading attachment...",
  142 + (PurpleMessageFlags)PURPLE_MESSAGE_SYSTEM,
  143 + time(NULL));
  144 +
  145 + std::string url = std::string(LINE_OS_URL) + "os/m/"+ att->id;
  146 +
  147 + PurpleConversationType ctype = purple_conversation_get_type(conv);
  148 + std::string cname = std::string(purple_conversation_get_name(conv));
  149 +
  150 + http.request(url, HTTPFlag::AUTH | HTTPFlag::LARGE,
  151 + [this, path, token, ctype, cname]
  152 + (int status, const guchar *data, gsize len)
  153 + {
  154 + if (status == 200 && data && len > 0) {
  155 + g_file_set_contents(path.c_str(), (const char *)data, len, nullptr);
  156 +
  157 + temp_files.push_back(path);
  158 +
  159 + PurpleConversation *conv = purple_find_conversation_with_account(
  160 + ctype, cname.c_str(), acct);
  161 +
  162 + if (conv) {
  163 + Attachment *att = conv_attachment_get(conv, token);
  164 + if (att)
  165 + att->path = path;
  166 + }
  167 +
  168 + purple_notify_uri(conn, path.c_str());
  169 + } else {
  170 + notify_error("Failed to download attachment.");
  171 + }
  172 + });
  173 +
  174 + return PURPLE_CMD_RET_OK;
  175 +}
... ...
purpleline_login.cpp
1   -#include <glib.h>
  1 +#include "purpleline.hpp"
2 2  
3 3 #include <core.h>
4 4  
5   -#include "purpleline.hpp"
6   -
7 5 void PurpleLine::login_start() {
8 6 purple_connection_set_state(conn, PURPLE_CONNECTING);
9 7 purple_connection_update_progress(conn, "Logging in", 0, 3);
... ...
purpleline_write.cpp 0 → 100644
  1 +#include "purpleline.hpp"
  2 +
  3 +static std::string get_sticker_id(line::Message &msg) {
  4 + std::map<std::string, std::string> &meta = msg.contentMetadata;
  5 +
  6 + if (meta.count("STKID") == 0 || meta.count("STKVER") == 0 || meta.count("STKPKGID") == 0)
  7 + return "";
  8 +
  9 + std::stringstream id;
  10 +
  11 + id << "[LINE sticker "
  12 + << meta["STKVER"] << "/"
  13 + << meta["STKPKGID"] << "/"
  14 + << meta["STKID"];
  15 +
  16 + if (meta.count("STKTXT") == 1)
  17 + id << " " << meta["STKTXT"];
  18 +
  19 + id << "]";
  20 +
  21 + return id.str();
  22 +}
  23 +
  24 +static std::string get_sticker_url(line::Message &msg, bool thumb = false) {
  25 + std::map<std::string, std::string> &meta = msg.contentMetadata;
  26 +
  27 + int ver;
  28 + std::stringstream ss(meta["STKVER"]);
  29 + ss >> ver;
  30 +
  31 + std::stringstream url;
  32 +
  33 + url << LINE_STICKER_URL
  34 + << (ver / 1000000) << "/" << (ver / 1000) << "/" << (ver % 1000) << "/"
  35 + << meta["STKPKGID"] << "/"
  36 + << "PC/stickers/"
  37 + << meta["STKID"];
  38 +
  39 + if (thumb)
  40 + url << "_key";
  41 +
  42 + url << ".png";
  43 +
  44 + return url.str();
  45 +}
  46 +
  47 +void PurpleLine::write_message(line::Message &msg, bool replay) {
  48 + std::string text;
  49 + int flags = 0;
  50 + time_t mtime = (time_t)(msg.createdTime / 1000);
  51 +
  52 + bool sent = (msg.from == profile.mid);
  53 +
  54 + if (std::find(recent_messages.cbegin(), recent_messages.cend(), msg.id)
  55 + != recent_messages.cend())
  56 + {
  57 + // We already processed this message. User is probably talking with himself.
  58 + return;
  59 + }
  60 +
  61 + // Hack
  62 + if (msg.from == msg.to)
  63 + push_recent_message(msg.id);
  64 +
  65 + PurpleConversation *conv = purple_find_conversation_with_account(
  66 + (msg.toType == line::MIDType::USER ? PURPLE_CONV_TYPE_IM : PURPLE_CONV_TYPE_CHAT),
  67 + ((!sent && msg.toType == line::MIDType::USER) ? msg.from.c_str() : msg.to.c_str()),
  68 + acct);
  69 +
  70 + // If this is a new received IM, create the conversation if it doesn't exist
  71 + if (!conv && !sent && msg.toType == line::MIDType::USER)
  72 + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, msg.from.c_str());
  73 +
  74 + // If this is a new conversation, we're not replaying history and history hasn't been fetched
  75 + // yet, queue the message instead of showing it.
  76 + if (conv && !replay) {
  77 + auto *queue = (std::vector<line::Message> *)
  78 + purple_conversation_get_data(conv, "line-message-queue");
  79 +
  80 + if (queue) {
  81 + queue->push_back(msg);
  82 + return;
  83 + }
  84 + }
  85 +
  86 + // Replaying messages from history
  87 + // Unfortunately Pidgin displays messages with this flag with odd formatting and no username.
  88 + // Disable for now.
  89 + //if (replay)
  90 + // flags |= PURPLE_MESSAGE_NO_LOG;
  91 +
  92 + switch (msg.contentType) {
  93 + case line::ContentType::NONE: // actually text
  94 + case line::ContentType::LOCATION:
  95 + if (msg.__isset.location) {
  96 + line::Location &loc = msg.location;
  97 +
  98 + text = markup_escape(loc.title)
  99 + + " | <a href=\"https://maps.google.com/?q=" + url_encode(loc.address)
  100 + + "&ll=" + std::to_string(loc.latitude)
  101 + + "," + std::to_string(loc.longitude)
  102 + + "\">"
  103 + + (loc.address.size()
  104 + ? markup_escape(loc.address)
  105 + : "(no address)")
  106 + + "</a>";
  107 + } else {
  108 + text = markup_escape(msg.text);
  109 + }
  110 + break;
  111 +
  112 + case line::ContentType::STICKER:
  113 + {
  114 + std::string id = get_sticker_id(msg);
  115 +
  116 + if (id == "") {
  117 + text = "<em>[Broken sticker]</em>";
  118 +
  119 + purple_debug_warning("line", "Got a broken sticker.\n");
  120 + } else {
  121 + text = id;
  122 +
  123 + if (conv
  124 + && purple_conv_custom_smiley_add(conv, id.c_str(), "id", id.c_str(), TRUE))
  125 + {
  126 + http.request(get_sticker_url(msg),
  127 + [this, id, conv](int status, const guchar *data, gsize len)
  128 + {
  129 + if (status == 200 && data && len > 0) {
  130 + purple_conv_custom_smiley_write(
  131 + conv,
  132 + id.c_str(),
  133 + data,
  134 + len);
  135 + } else {
  136 + purple_debug_warning(
  137 + "line",
  138 + "Couldn't download sticker. Status: %d\n",
  139 + status);
  140 + }
  141 +
  142 + purple_conv_custom_smiley_close(conv, id.c_str());
  143 + });
  144 + }
  145 + }
  146 + }
  147 + break;
  148 +
  149 + case line::ContentType::IMAGE:
  150 + case line::ContentType::VIDEO: // Videos could really benefit from streaming...
  151 + {
  152 + std::string type_std = line::_ContentType_VALUES_TO_NAMES.at(msg.contentType);
  153 +
  154 + std::string id = "[LINE " + type_std + " " + msg.id + "]";
  155 +
  156 + text = id;
  157 +
  158 + if (conv) {
  159 + text += " <font color=\"#888888\">/open "
  160 + + conv_attachment_add(conv, msg.contentType, msg.id)
  161 + + "</font>";
  162 + }
  163 +
  164 + if (!conv
  165 + || !purple_conv_custom_smiley_add(conv, id.c_str(), "id", id.c_str(), TRUE))
  166 + {
  167 + break;
  168 + }
  169 +
  170 + if (msg.contentPreview.size() > 0) {
  171 + purple_conv_custom_smiley_write(
  172 + conv,
  173 + id.c_str(),
  174 + (const guchar *)msg.contentPreview.c_str(),
  175 + msg.contentPreview.size());
  176 +
  177 + purple_conv_custom_smiley_close(conv, id.c_str());
  178 + } else {
  179 + std::string preview_url = msg.contentMetadata.count("PREVIEW_URL")
  180 + ? msg.contentMetadata["PREVIEW_URL"]
  181 + : std::string(LINE_OS_URL) + "os/m/" + msg.id + "/preview";
  182 +
  183 + http.request(preview_url, HTTPFlag::AUTH | HTTPFlag::LARGE,
  184 + [this, id, conv](int status, const guchar *data, gsize len)
  185 + {
  186 + if (status == 200 && data && len > 0) {
  187 + purple_conv_custom_smiley_write(
  188 + conv,
  189 + id.c_str(),
  190 + data,
  191 + len);
  192 + } else {
  193 + purple_debug_warning(
  194 + "line",
  195 + "Couldn't download image message. Status: %d\n",
  196 + status);
  197 + }
  198 +
  199 + purple_conv_custom_smiley_close(conv, id.c_str());
  200 + });
  201 + }
  202 + }
  203 + break;
  204 +
  205 + case line::ContentType::AUDIO:
  206 + {
  207 + text = "[Audio message";
  208 +
  209 + if (msg.contentMetadata.count("AUDLEN")) {
  210 + int len = 0;
  211 +
  212 + try {
  213 + len = std::stoi(msg.contentMetadata["AUDLEN"]);
  214 + } catch(...) { /* ignore */ }
  215 +
  216 + if (len > 0) {
  217 + text += " "
  218 + + std::to_string(len / 1000)
  219 + + "."
  220 + + std::to_string((len % 1000) / 100)
  221 + + "s";
  222 + }
  223 + }
  224 +
  225 + text += "]";
  226 +
  227 + if (conv) {
  228 + text += " <font color=\"#888888\">/open "
  229 + + conv_attachment_add(conv, msg.contentType, msg.id)
  230 + + "</font>";
  231 + }
  232 + }
  233 + break;
  234 +
  235 + // TODO: other content types
  236 +
  237 + default:
  238 + text = "<em>[Not implemented: ";
  239 + text += line::_ContentType_VALUES_TO_NAMES.at(msg.contentType);
  240 + text += " message]</em>";
  241 + break;
  242 + }
  243 +
  244 + if (sent) {
  245 + // Messages sent by user (sync from other devices)
  246 +
  247 + write_message(conv, msg.from, msg.text, mtime, flags | PURPLE_MESSAGE_SEND);
  248 + } else {
  249 + // Messages received from other users
  250 +
  251 + flags |= PURPLE_MESSAGE_RECV;
  252 +
  253 + if (replay) {
  254 + // Write replayed messages instead of serv_got_* to avoid Pidgin's IM sound
  255 +
  256 + write_message(conv, msg.from, text, mtime, flags);
  257 + } else {
  258 + if (msg.toType == line::MIDType::USER) {
  259 + serv_got_im(
  260 + conn,
  261 + msg.from.c_str(),
  262 + text.c_str(),
  263 + (PurpleMessageFlags)flags,
  264 + mtime);
  265 + } else if (msg.toType == line::MIDType::GROUP || msg.toType == line::MIDType::ROOM) {
  266 + serv_got_chat_in(
  267 + conn,
  268 + purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)),
  269 + msg.from.c_str(),
  270 + (PurpleMessageFlags)flags,
  271 + text.c_str(),
  272 + mtime);
  273 + }
  274 + }
  275 + }
  276 +}
  277 +
  278 +void PurpleLine::write_message(PurpleConversation *conv, std::string &from, std::string &text,
  279 + time_t mtime, int flags)
  280 +{
  281 + if (!conv)
  282 + return;
  283 +
  284 + PurpleConversationType type = purple_conversation_get_type(conv);
  285 +
  286 + if (type == PURPLE_CONV_TYPE_IM) {
  287 + purple_conv_im_write(
  288 + PURPLE_CONV_IM(conv),
  289 + from.c_str(),
  290 + text.c_str(),
  291 + (PurpleMessageFlags)flags,
  292 + mtime);
  293 + } else if (type == PURPLE_CONV_TYPE_CHAT) {
  294 + purple_conv_chat_write(
  295 + PURPLE_CONV_CHAT(conv),
  296 + from.c_str(),
  297 + text.c_str(),
  298 + (PurpleMessageFlags)flags,
  299 + mtime);
  300 + } else {
  301 + purple_debug_warning("line", "write_message for weird conversation type: %d\n", type);
  302 + }
  303 +}
... ...