From f24d662c3c70c11f6b8423b76148d2de31223875 Mon Sep 17 00:00:00 2001 From: Gabor Adam Toth Date: Mon, 22 Feb 2010 08:58:12 +0100 Subject: [PATCH] user channel improvements: /add, /remove & /privacy cmds, html output for multiple channels in /surf place/threads: better _notify msgs & error handling, improved html & json generation, TODO: use signatures here as well place/userthreads: added /add, /remove & /privacy cmds person: (un)subscribe to user channel when a user uses the /add & /remove commands, autojoin to #friends & #follow after friendship is established, added _channels data to qDescription user: htDescription: generate html from _channels data http: TODO: fix web examine cmd so it's not executed twice --- world/net/library/signature.c | 6 + world/net/person.c | 77 ++++++- world/net/place/threads.c | 376 ++++++++++++++++++++-------------- world/net/place/userthreads.c | 99 ++++++++- world/net/user.c | 50 ++++- world/net/usercmd.i | 17 +- world/static/examine.css | 58 +++++- 7 files changed, 500 insertions(+), 183 deletions(-) diff --git a/world/net/library/signature.c b/world/net/library/signature.c index eaa4728..091ff0b 100644 --- a/world/net/library/signature.c +++ b/world/net/library/signature.c @@ -73,6 +73,12 @@ private volatile mapping _sigs = ([ "_request_ni": ({ "_request_nick_local", 0, "_nick_local", "_INTERNAL_stuss" }), "_request_public": ({ "_request_public", 0, "_flag_public" }), "_request_pub": ({ "_request_public", 0, "_flag_public" }), +#ifdef _flag_enable_module_microblogging + "_request_add": ({ "_request_add", 0, "_person" }), + "_request_remove": ({ "_request_remove", 0, "_person" }), + "_request_priv": ({ "_request_privacy", 0, "_privacy" }), + "_request_privacy": ({ "_request_privacy", 0, "_privacy" }), +#endif #ifdef EXPERIMENTAL // stuff to play around with "_request_pset": ({ "_request_set", 0, "_key", "_value" }), diff --git a/world/net/person.c b/world/net/person.c index 591a981..4d46681 100644 --- a/world/net/person.c +++ b/world/net/person.c @@ -467,6 +467,16 @@ qDescription(source, vars, profile, itsme) { if (v("fname")) dv["_name_first"] = v("fname"); } + +#ifdef _flag_enable_module_microblogging + mapping channels = ([ ]); + foreach (string c : v("channels")) { + object p = find_place(c); + unless (objectp(p) && (p->isPublic() || (source && p->qMember(source))) /*&& p->numEntries() > 0*/) continue; + channels += ([ p->qChannel(): p->entries(10)]); + } + dv["_channels"] = make_json(channels); +#endif // PT(("sending: %O\n", dv)) return dv; } @@ -2038,10 +2048,18 @@ case "_request_friendship_implied": friends[source, FRIEND_AVAILABILITY] = vars["_degree_availability"] || AVAILABILITY_HERE; #endif - if (objectp(source)) + if (objectp(source)) insert_member(source); else insert_member(source, parse_uniform(source, 1)[URoot]); + +#ifdef _flag_enable_module_microblogging + string uni = psyc_name(ME); + sendmsg(source, "_notice_place_enter_automatic_subscription_follow", + "Following [_nick_place]", (["_nick": MYNICK, "_nick_place": uni + "#follow" ])); + sendmsg(source, "_notice_place_enter_automatic_subscription_follow", + "Following [_nick_place]", (["_nick": MYNICK, "_nick_place": uni + "#friends" ])); +#endif showFriends(); // fall thru // all other cases describe established friendships, @@ -2179,6 +2197,31 @@ case "_status_person_present": source, display, logged_on)) return logged_on && display; // what about availability? #endif // _flag_disable_module_presence +#ifdef _flag_enable_module_microblogging +case "_notice_place_enter_automatic_subscription_follow": +case "_notice_place_leave_automatic_subscription_follow": + string src = objectp(source) ? psyc_name(source) : source; + string nick = objectp(source) ? source->qNameLower() : source; + string nick_place = vars["_nick_place"]; + if (qFriend(source) || qSubscription(src, 1) || qSubscription("~"+nick, 1)) { + P3((">>> %O: %O subscribed me to %O\n", ME, source, nick_place)) + subscribe(abbrev("_notice_place_enter", mc) ? + SUBSCRIBE_TEMPORARY : SUBSCRIBE_NOT, nick_place); + } else if (member(places, find_place(nick_place)) && + (sscanf(nick_place, src+"#%s", t) || sscanf(nick_place, "~"+nick+"#%s", t))) { + placeRequest(nick_place, +#ifdef SPEC + "_request_context_leave", +#else + "_request_leave", +#endif + 1); + } else { + P3((">>> %O: got a %O from %O, but he is not a friend or not already following him.\n", + ME, mc, source, nick_place)) + } + return ""; +#endif case "_notice_mail": // on request by y0shi.. remember mail notifications // even when offline @@ -2605,7 +2648,7 @@ quit(immediate, variant) { #endif } #endif - if (immediate == 1 || (immediate && find_call_out(#'quit) != -1)) { + if (immediate == 1 || (immediate && find_call_out(#'quit) != -1)) { //' rc = save(); if (sizeof(places)) { P2(("%O stayin' alive because of places %O" @@ -2718,7 +2761,7 @@ quit(immediate, variant) { // return if there are some services/clients left if (sizeof(v("locations"))) return rc; vDel("scheme"); - call_out(#'quit, 20, 1, variant); + call_out(#'quit, 20, 1, variant); //' return rc; } } else { @@ -2921,17 +2964,31 @@ static qFriends() { } #ifdef _flag_enable_module_microblogging -qFriend(object snicker) { - P3((">> qFriend(%O)\n", snicker)) - return member(friends, snicker); +qFriend(person) { + P3((">> qFriend(%O)\n", person)) + if (IS_NEWBIE) return 0; + if (objectp(person)) person = person->qNameLower(); + return member(ppl, person) && ppl[person][PPL_NOTIFY] >= PPL_NOTIFY_FRIEND; } -qFollower(object snicker) { - P3((">> qFollower(%O)\n", snicker)) +qFollower(person) { + P3((">> qFollower(%O)\n", person)) + if (IS_NEWBIE) return 0; foreach (string c : v("channels")) { object p = find_place(c); P3((">>> c: %O, p: %O\n", c, p)) - if (p && p->qMember(snicker)) return 1; + if (p && p->qMember(person)) return 1; + } + return 0; +} + +qSubscription(target, without_channel) { + P3((">> qSubscription(%O, %O)\n", target, without_channel)) + if (IS_NEWBIE) return 0; + unless (without_channel) return member(v("subscriptions"), target); + string c; + foreach (string t : v("subscriptions")) { + if (sscanf(t, target + "#%s", c)) return 1; } return 0; } @@ -3039,7 +3096,7 @@ void reset(int again) { #if 0 } else { P3(("RESET: quitting %O\n", ME)) - if (find_call_out(#'quit) == -1) quit(); + if (find_call_out(#'quit) == -1) quit(); //' #endif } } else { // hmm.. why do we still use reset(0) for this? diff --git a/world/net/place/threads.c b/world/net/place/threads.c index 584894b..21ce44f 100644 --- a/world/net/place/threads.c +++ b/world/net/place/threads.c @@ -43,27 +43,37 @@ create() { cmd(a, args, b, source, vars) { P3((">> threads:cmd(%O, %O, %O, %O, %O)", a, args, b, source, vars)) // TODO: multiline-sachen irgendwie - mapping entry; + mapping entry; + array(mapping) entries; int i = 0; + int id; int num_entries; //unless (source) source = previous_object(); switch (a) { case "entries": num_entries = sizeof(args) >= 2 ? to_int(args[1]) : DEFAULT_BACKLOG; // _thread[<5..] - foreach( entry : _thread[= 0; i--) { + unless (entry = _thread[i]) continue; + entries = + ({ ([ + "_sep" : strlen(entry["thread"]) ? " - " : "", + "_thread" : entry["thread"], + "_text" : entry["text"], + "_author" : entry["author"], + "_date" : entry["date"], + "_comments": sizeof(entry["comments"]), + "_id" : i, + "_nick_place" : MYNICK, + ]) }) + entries; + if (sizeof(entries) == num_entries) break; } + foreach(entry : entries) + sendmsg(source, "_list_thread_entry", + "#[_id] - [_author][_sep][_thread]: [_text] ([_comments])", + entry); return 1; case "entry": unless (sizeof(args) > 1){ @@ -71,10 +81,18 @@ cmd(a, args, b, source, vars) { "Usage: /entry ", ([ ])); return 1; } - int n = to_int(args[1]); - entry = _thread[n]; - sendmsg(source, "_list_thread_item", - "#[_number] [_author][_sep][_thread]: [_text] ([_comments])", + id = to_int(args[1]); + if (id >= 0 && id < sizeof(_thread)) + entry = _thread[id]; + + unless (entry) { + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + return 1; + } + + sendmsg(source, "_list_thread_entry", + "#[_id] [_author][_sep][_thread]: [_text] ([_comments])", ([ "_sep" : strlen(entry["thread"]) ? " - " : "", "_thread" : entry["thread"], @@ -82,7 +100,7 @@ cmd(a, args, b, source, vars) { "_author" : entry["author"], "_date" : entry["date"], "_comments": sizeof(entry["comments"]), - "_number" : n, + "_id" : id, "_nick_place" : MYNICK ]) ); if (entry["comments"]) { @@ -92,6 +110,7 @@ cmd(a, args, b, source, vars) { ([ "_nick" : item["nick"], "_text" : item["text"], + "_date": item["date"], "_nick_place" : MYNICK ]) ); } } @@ -102,28 +121,38 @@ cmd(a, args, b, source, vars) { "Usage: /thread ", ([ ])); return 1; } - return setSubject(to_int(args[1]), ARGS(2)); + id = to_int(args[1]); + unless (setSubject(id, ARGS(2))) + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + + return 1; case "comment": unless (sizeof(args) >= 2) { sendmsg(source, "_warning_usage_reply", "Usage: /comment <threadid> <text>", ([ ])); return 1; } - return addComment(ARGS(2), SNICKER, to_int(args[1])); + id = to_int(args[1]); + unless (addComment(ARGS(2), SNICKER, id)) + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); + return 1; case "blog": case "submit": case "addentry": - unless (canPost(SNICKER)) return; + unless (canPost(SNICKER)) return 0; unless (sizeof(args) >= 1) { sendmsg(source, "_warning_usage_submit", "Usage: /submit <text>", ([ ])); return 1; } - return addEntry(ARGS(1), SNICKER); + addEntry(ARGS(1), SNICKER); + return 1; // TODO: append fuer multiline-sachen #if 0 case "iterator": - unless (canPost(SNICKER)) return; + unless (canPost(SNICKER)) return 0; sendmsg(source, "_notice_thread_iterator", "[_iterator] blog entries have been requested " "since creation.", ([ @@ -133,19 +162,18 @@ cmd(a, args, b, source, vars) { ]) ); return 1; #endif + case "unblog": case "deblog": case "delentry": - unless (canPost(SNICKER)) return; - // ist das ein typecheck ob args ein int is? - if (sizeof(regexp( ({ args[1] }) , "^[0-9][0-9]*$"))) { - unless (delEntry(to_int(args[1]), source, vars)) { - sendmsg(source,"_error_invalid_thread_item", - "There is no such thread item.", ([ ])); - } else { - sendmsg(source, "_notice_thread_item_removed", - "Thread item [_number] has been removed.", - ([ "_number" : ARGS(1) ]) ); - } + unless (canPost(SNICKER)) return 0; + id = to_int(args[1]); + if (delEntry(id, source, vars)) { + sendmsg(source, "_notice_thread_entry_removed", + "Entry #[_id] has been removed.", + ([ "_id" : id ]) ); + } else { + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": id])); } return 1; } @@ -185,19 +213,19 @@ listLastEntries(number) { allEntries(source) { mapping* entries; mapping ar; - int i = 0; + int i = 0; entries = _thread || ({ }); unless (sizeof(entries)) return 1; foreach (ar : entries) { - sendmsg(source, "_message_", "([_number]) \"[_topic]\", [_author]", ([ // ?? + sendmsg(source, "_message_", "([_id]) \"[_topic]\", [_author]", ([ // ?? "_topic" : ar["topic"], "_text" : ar["text"], "_author" : ar["author"], "_date" : ar["date"], - "_number" : i++, + "_id" : i++, "_nick_place" : MYNICK ]) ); } return 1; @@ -224,77 +252,80 @@ _notice_thread } #endif -setSubject(num, thread) { - mapping* entries; - - entries = _thread || ({ }); - // TODO: das hier muss sicherer - entries[num]["thread"] = thread; - _thread = entries; - save(); - return 1; +setSubject(id, thread) { + unless (_thread && id >= 0 && id <= sizeof(_thread) && _thread[id]) return 0; + _thread[id]["thread"] = thread; + save(); + return 1; } // TODO: topic uebergeben addEntry(text, unick, thread) { - mapping* entries; - mapping newentry = ([ "text" : text, - "author" : unick, - "date" : isotime(ctime(), 1), - "thread" : thread || "", - ]); - entries = _thread || ({ }); - entries += ({ newentry }); - _thread = entries; - save(); - castmsg(ME, "_notice_thread_item", - "[_nick] adds an entry in \"[_thread]\" of [_nick_place].", ([ - "_entry" : text, - "_thread" : thread, - "_nick" : unick, - ]) ); - return 1; + mapping* entries; + mapping newentry = ([ + "text" : text, + "author" : unick, + "date" : time(), + "thread" : thread || "", + ]); + entries = _thread || ({ }); + entries += ({ newentry }); + int id = sizeof(entries) - 1; + _thread = entries; + save(); + castmsg(ME, "_notice_thread_entry", + thread ? + "[_nick] adds an entry in [_nick_place] (#[_id]): \"[_thread]\":\n[_entry]" : + "[_nick] adds an entry in [_nick_place] (#[_id]):\n[_entry]", + ([ + "_entry": text, + "_id": id, + "_thread": thread, + "_nick": unick, + ])); + return 1; } -addComment(text, unick, entry_id) { - mapping entry; - - if (sizeof(_thread) > entry_id) { - entry = _thread[entry_id]; - unless (entry["comments"]) { - entry["comments"] = ({ }); - } - entry["comments"] += ({ (["text" : text, "nick" : unick ]) }); - // vSet("entries", entries); - castmsg(ME, "_notice_thread_comment", - "[_nick] adds a comment in \"[_thread]\" of [_nick_place].", ([ - "_entry" : entry["text"], - "_thread" : entry["thread"], - "_comment" : text, - "_nick" : unick, - ]) ); - save(); - return 1; - } - return -1; +addComment(text, unick, id) { + mapping entry; + unless (_thread && id >= 0 && id <= sizeof(_thread) && _thread[id]) return 0; + + entry = _thread[id]; + unless (entry["comments"]) { + entry["comments"] = ({ }); + } + int date = time(); + entry["comments"] += ({ (["text" : text, "nick" : unick, "date": date ]) }); + // vSet("entries", entries); + save(); + castmsg(ME, "_notice_thread_comment", + entry["thread"] && strlen(entry["thread"]) ? + "[_nick] adds a comment to \"[_thread]\" (entry #[_id]) of [_nick_place]:\n[_comment]" : + "[_nick] adds a comment to entry #[_id] of [_nick_place]:\n[_comment]", + ([ + "_entry" : entry["text"], + "_id" : id, + "_thread" : entry["thread"], + "_comment" : text, + "_nick" : unick, + "_date": date, + ])); + return 1; } -delEntry(int number, source, vars) { +delEntry(int id, source, vars) { + unless (_thread && id >= 0 && id <= sizeof(_thread) && _thread[id]) return 0; + array(string) entries, authors, a; string unick; - int size; - - entries = _thread || ({ }); - - unless (size = sizeof(entries)) return 0; - if (number >= size) return 0; if (canPost(unick = lower_case(SNICKER))) { - unless (lower_case(entries[number]["author"]) == unick) return 0; + unless (lower_case(_thread[id]["author"]) == unick) return 0; } - _thread = entries[0..number-1] + entries[number+1..]; - //_thread[number] = 0; + //_thread = _thread[0..id-1] + _thread[id+1..]; + // set to 0 instead so entry ids won't change + _thread[id] = 0; save(); return 1; @@ -438,15 +469,14 @@ rssExport(last) { "\t<title>"+ _thread[i]["thread"] +"\n" "\thttp://" + SERVER_HOST + ":33333" + webact + "?comments=" + i + "\n" "\t" + _thread[i]["text"] + "\n" - "\t" + _thread[i]["date"] + "\n" + "\t" + isotime(ctime(_thread[i]["date"]), 1) + "\n" "\t" + _thread[i]["author"] + "\n"); write("\n"); } - + write("\n"); } - jscriptExport(last) { mapping item; string buf = ""; @@ -460,9 +490,9 @@ jscriptExport(last) { "}\n\n" "document.blogentries = new Array(\n"); foreach (item : _thread[" + _thread[i]["thread"] + "" "" + _thread[i]["author"] + "" - "" + _thread[i]["date"] + "" + "" + isotime(ctime(_thread[i]["date"]), 1) + "" "" + _thread[i]["text"] + "" ""); @@ -492,59 +522,96 @@ displayMain(last) { } #endif -htMain(last) { - int i; - int len; - string t; - string ht = - ""; +htmlEntries(array(mapping) entries, int last, int js, string chan, string submit) { + P3((">> threads:htmlentries(%O, %O)\n", entries, last)) + string t; + string ht = ""; + if (js) ht += + "\n"; + string id_prefix = chan ? chan + "-" : ""; - len = sizeof(_thread); - if (last > len) last = len; - - // reverse order - for (i = len-1; i >= len - last; i--) { - P3((">>> _thread[%O]: %O\n", i, _thread[i])) - mapping item = _thread[i]; - t = htquote(item["text"]); - t = replace(t, "\n", "
\n"); - t = replace(t, "<", "<"); - t = replace(t, ">", ">"); + mapping item; + int n = 0; + int id; + // reverse order + for (id = sizeof(entries) - 1; id >= 0; id--) { + P3((">>> entries[%O]: %O\n", id, entries[id])) + unless (item = entries[id]) continue; - string c = ""; - if (item["comments"]) - foreach(mapping comment : item["comments"]) - c += "
" + comment["nick"] + ": " + comment["text"] + "
\n"; + t = htquote(item["text"]); + t = replace(t, "\n", "
\n"); + t = replace(t, "<", "<"); + t = replace(t, ">", ">"); - ht += "
\n" - "
\n" - "" + item["author"] + "\n" - "" + htquote(item["thread"]) + "\n" - "
\n" - "
\n" - "
" + t + "
\n" - "\n" - "
\n" - "\n" - "
\n"; - } - return "
" + ht + "
"; + string c = ""; + if (item["comments"]) + foreach(mapping comment : item["comments"]) + c += "
" + comment["nick"] + ": " + comment["text"] + "
\n"; + + ht += + "
\n" + "
\n" + "#" + id + " - \n" + "" + item["author"] + "\n" + + (item["thread"] && strlen(item["thread"]) ? " - " : "") + + "" + htquote(item["thread"]) + "\n" + "
\n" + "
\n" + "
" + t + "
\n" + "\n" + "
\n" + "\n" + "
\n"; + if (last && ++n >= last) break; + } + P3((">>> ht: %O\n", ht)) + return "
" + ht + "
"; } -htComments(data) { +htMain(int last) { + return htmlEntries(_thread, last, 1); +} + +entries(int last) { + array(mapping) entries = ({ }); + int n = 0; + int id; + // reverse order + for (id = sizeof(_thread) - 1; id >= 0; id--) { + P3((">>> _thread[%O]: %O\n", id, _thread[id])) + unless (_thread[id]) continue; + entries += ({ _thread[id] }); + if (++n >= last) break; + } + + return entries; +} + +jsonEntries(int last) { + return make_json(entries(last)); +} + +htComments(entry) { mapping item; string ht = ""; - write("" + data["author"] + ": " + data["text"] + "

\n"); - if (data["comments"]) { - foreach(item : data["comments"]) { + write("" + entry["author"] + ": " + entry["text"] + "

\n"); + if (entry["comments"]) { + foreach(item : entry["comments"]) { ht += "" + item["nick"] + ": " + item["text"] + "
\n"; } } else { @@ -557,8 +624,8 @@ displayMain(last) { write(htMain(last)); } -displayComments(data) { - write(htComments(data)); +displayComments(entry) { + write(htComments(entry)); } nntpget(cmd, args) { @@ -573,13 +640,14 @@ case "ARTICLE": i = to_int(args) - 1; P2(("i is: %d\n", i)) P2(("entries: %O\n", _thread)) + unless (_thread && i >= 0 && i <= sizeof(_thread) && _thread[i]) break; item = _thread[i]; write(S("220 %d <%s%d@%s> article\n", i + 1, MYNICK, i + 1, SERVER_HOST)); write(S("From: %s\n", item["author"])); write(S("Newsgroups: %s\n", MYNICK)); write(S("Subject: %s\n", item["thread"])); - write(S("Date: %s\n", item["date"])); + write(S("Date: %s\n", isotime(ctime(item["date"]), 1))); write(S("Xref: %s %s:%d\n", SERVER_HOST, MYNICK, i + 1)); write(S("Message-ID: <%s$%d@%s>\n", MYNICK, i+1, SERVER_HOST)); write("\n"); @@ -592,11 +660,11 @@ case "GROUP": break; case "XOVER": for (i = 0; i < sizeof(_thread); i++) { - item = _thread[i]; + unless(item = _thread[i]) continue; P2(("item: %O\n", item)) write(S("%d\t%s\t%s\t%s <%s%d@%s>\t1609\t22\tXref: news.t-online.com\t%s:%d\n", i+1, item["thread"], - item["author"], item["date"], + item["author"], isotime(ctime(item["date"]), 1), MYNICK, i+1, SERVER_HOST, MYNICK, i+1)); } @@ -633,3 +701,11 @@ displayFooter() { canPost(snicker) { return qAide(snicker); } + +mayLog(mc) { + return abbrev("_notice_thread", mc) || abbrev("_message", mc); +} + +numEntries() { + return sizeof(_thread); +} diff --git a/world/net/place/userthreads.c b/world/net/place/userthreads.c index 7a6e99b..e4aab38 100644 --- a/world/net/place/userthreads.c +++ b/world/net/place/userthreads.c @@ -4,6 +4,11 @@ #define BLAME "!configuration" #define DONT_REWRITE_NICKS +#define PLACE_HISTORY +#define PLACE_OWNED +#define HISTORY_GLIMPSE 12 + +#include inherit NET_PATH "place/threads"; @@ -17,6 +22,7 @@ load(name, keep) { sscanf(name, "~%s#%s", owner, channel); vSet("owners", ([ owner: 0 ])); + vSet("privacy", "private"); vSet("_restrict_invitation", BLAME); vSet("_filter_conversation", BLAME); @@ -27,9 +33,11 @@ load(name, keep) { enter(source, mc, data, vars) { P3((">> userthreads:enter(%O, %O, %O, %O)\n", source, mc, data, vars)) object p = summon_person(owner, NET_PATH "user"); - string src = psyc_name(source); + string src = objectp(source) ? psyc_name(source) : source; - unless (p && (p == source || qAide(src) || p->qFriend(source) || p->qFollower(source))) { + unless (v("privacy") == "public" || + (p && (p == source || qAide(src) || (objectp(source) && qAide(source->qNameLower())) + || p->qFriend(source) || p->qFollower(source)))) { sendmsg(source, "_error_place_enter_necessary_invitation", "[_nick_place] can only be entered upon invitation.", ([ "_nick_place" : qName() ]) ); @@ -49,17 +57,90 @@ enter(source, mc, data, vars) { return ::enter(source, mc, data, vars); } -cmd(a, args, b, source, vars) { - P3((">> threads:cmd(%O, %O, %O, %O, %O)", a, args, b, source, vars)) - - switch (a) { - case "add": // or similar - // add follower to this channel, TODO +_request_add(source, mc, data, vars, b) { + P3((">> userthreads:_request_add(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (vars["_person"]) { + sendmsg(source, "_warning_usage_add", "Usage: /add ", ([ ])); + return 1; } - return ::cmd(a, args, b, source, vars); + object p = summon_person(owner, NET_PATH "user"); + object target = summon_person(vars["_person"], NET_PATH "user"); + string ni; + if (objectp(target)) { + ni = target->qName(); + } else { + target = vars["_person"]; + mixed *u = parse_uniform(target); + if (u) ni = u[UNick]; + } + + unless (ni && p && (p->qFriend(target) || p->qFollower(target))) { + sendmsg(source, "_error_add", + "Error: [_person] is not a friend or follower.", ([ "_person": vars["_person"]])); + return 1; + } + + string _mc = "_notice_place_enter_automatic_subscription_follow"; + sendmsg(target, _mc, "You're now following [_nick_place].", (["_nick_place" : qName()]), source); + //insert_member(target, mc, 0, (["_nick": ni]), ni); + + return 1; +} + +_request_remove(source, mc, data, vars, b) { + P3((">> userthreads:_request_remove(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (vars["_person"]) { + sendmsg(source, "_warning_usage_remove", "Usage: /remove ", ([ ])); + return 1; + } + + object p = summon_person(owner, NET_PATH "user"); + object target = summon_person(vars["_person"], NET_PATH "user") || vars["_person"]; + + unless (p && (qMember(target) || p->qFriend(target) || p->qFollower(target))) { + sendmsg(source, "_error_add", + "Can't remove: [_person] is not a friend or follower.", ([ "_person": vars["_person"]])); + return 1; + } + + string _mc = "_notice_place_leave_automatic_subscription_follow"; + sendmsg(target, _mc, "You're no longer following [_nick_place].", (["_nick_place" : qName()]), source); + remove_member(target); + + return 1; +} + +// set privacy: private or public +// - private: only friends & invited people can enter (default) +// - public: anyone can enter +_request_privacy(source, mc, data, vars, b) { + P3((">> userthreads:_request_privace(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + string p = vars["_privacy"]; + if (p == "public" || p == "private") { + vSet("privacy", p); + save(); + } + sendmsg(source, "_status_privacy", "Privacy is: [_privacy].", (["_privacy": v("privacy")])); + return 1; +} + +htMain(int last) { + return htmlEntries(_thread, last, 1, channel); } canPost(snicker) { return qOwner(snicker); } + +isPublic() { + return vQuery("privacy") == "public"; +} + +qChannel() { + return channel; +} + +qHistoryGlimpse() { + return HISTORY_GLIMPSE; +} diff --git a/world/net/user.c b/world/net/user.c index dc42941..5e928cd 100644 --- a/world/net/user.c +++ b/world/net/user.c @@ -158,7 +158,7 @@ htDescription(anonymous, query, headers, qs, variant, vars) { #else int doTalk = 0; - P3(("htDescription for %O showing %O or %O\n", qs, vars, descvars)) + P3(("htDescription in %O for %O showing %O or %O\n", ME, qs, vars, descvars)) if (query) sTextPath(query["layout"] || v("layout"), query["lang"] || v("language"), "html"); else sTextPath(v("layout"), v("language"), "html"); @@ -285,13 +285,53 @@ htDescription(anonymous, query, headers, qs, variant, vars) { // # ifdef _flag_enable_module_microblogging - object u = find_place("~" + nick + "#follow"); //TODO - string updates = objectp(u) ? u->htMain(10) : ""; + mapping channels; + string htchannels; + if (vars["_channels"] && (channels = parse_json(vars["_channels"]))) { + P3((">>> channels: %O\n", channels)) + htchannels = + "\n"; + + string tabs = ""; + string contents = ""; + int first = 1; + object threads = clone_object(NET_PATH "place/threads"); + + foreach (string channel, array(mapping) entries : channels) { + unless (sizeof(entries) > 0) continue; + tabs += + "#" + channel + "\n"; + contents += + "
" + threads->htmlEntries(entries, 0, first, channel, anonymous ? "" : vars["_identification"] + "#" + channel) + "
\n"; + first = 0; + } + + if (first) htchannels = ""; + else htchannels += + "
\n" + tabs + "
\n" + "
\n" + contents + "
\n"; + } # endif return psyctext(page, vars + ([ "_FORM_start" : "\
\n\ -\n", +\n\ +\n", "_QUERY_STRING" : qs || "", "_HTML_photo" : foto, "_nick_me" : MYNICK, @@ -299,7 +339,7 @@ htDescription(anonymous, query, headers, qs, variant, vars) { ]) ) # ifdef _flag_enable_module_microblogging - + updates + + htchannels # endif ; } diff --git a/world/net/usercmd.i b/world/net/usercmd.i index d479ea3..29461ba 100644 --- a/world/net/usercmd.i +++ b/world/net/usercmd.i @@ -2430,6 +2430,14 @@ friend(rm, entity, ni, trustee) { insert_member(entity); else insert_member(entity, parse_uniform(entity, 1)[URoot]); + +#ifdef _flag_enable_module_microblogging + string uni = psyc_name(ME); + sendmsg(entity, "_notice_place_enter_automatic_subscription_follow", + "Following [_nick_place]", (["_nick": MYNICK, "_nick_place": uni + "#follow" ])); + sendmsg(entity, "_notice_place_enter_automatic_subscription_follow", + "Following [_nick_place]", (["_nick": MYNICK, "_nick_place": uni + "#friends" ])); +#endif // this used to imply a symmetric request for // friendship, but we prefer to make it an // informational message instead. the protocol @@ -3377,14 +3385,17 @@ subscribe(how, arg, quiet) { // be like implementing HTTPs cookies.. // if (arg = placeRequest(arg, "_request_enter_subscribe")) // sups += ([ objectp(arg) ? arg->qName() : arg ]); - if (placeRequest(arg, + if (find_place(arg)) { + // don't join to temporarily subscribed places when not online + if (ONLINE || how == SUBSCRIBE_PERMANENT) + placeRequest(arg, #ifdef SPEC "_request_context_enter_subscribe", #else "_request_enter_subscribe", #endif - 0, quiet)) { - sups += ([ arg : how ]); + 0, quiet); + sups += ([ arg : how ]); } else return 1; } if (size != sizeof(sups)) { diff --git a/world/static/examine.css b/world/static/examine.css index c912fed..6d2238b 100644 --- a/world/static/examine.css +++ b/world/static/examine.css @@ -19,6 +19,7 @@ body.threads, padding: 4; border: 1px solid #f33; } +.comment-submit textarea, .comment-submit input, .Pe form input,select,textarea { background: black; color: #f33; @@ -79,12 +80,17 @@ body.threads, .entry .body { background: black; - padding: 5px; + padding: 0.4em 0.4em 0 0.4em; +} + +.entry .body .text, +.entry .body .comment-submit { + padding-bottom: 0.4em; } .entry .body .comment { - margin: 0.5em 1em 0; - padding: 2px; + margin: 0.2em 1em; + padding: 0.2em; border-top: 1px solid #933; } @@ -92,9 +98,19 @@ body.threads, font-weight: bold; } -.entry .title { - display: none; +.entry .body .comment-submit { + margin: 0 1em; } + +.entry .body .comment-submit input, +.entry .body .comment-submit textarea { + font-size: 12px; +} +.entry .body .comment-submit textarea { + width: 100%; +} + +.entry .title {} .entry .title .author {} .entry .title .subject {} @@ -114,6 +130,36 @@ body.threads, float: right; } +.tabs { + width: 574px; + margin: 22px 44px; + padding-bottom: 2px; + border-bottom: 1px solid #933; +} + +.tabs a { + padding: 2px; + background: #333; + border: 1px solid #933; + cursor: pointer; +} + +.tabs a:hover { + background: #222; +} + +.tabs > a.selected { + background: #000; +} + + +.tab-contents > div { + display: none; +} +.tab-contents > div.selected { + display: block; +} + .hidden { display: none; -} \ No newline at end of file +}