From 4576ab77d7e46bc0e3fd750e23158d7204a4c6a2 Mon Sep 17 00:00:00 2001 From: codescientist703 Date: Mon, 11 Oct 2021 15:27:33 +0530 Subject: [PATCH 01/46] chore: remove chat module --- frappe/chat/__init__.py | 23 - frappe/chat/doctype/__init__.py | 0 frappe/chat/doctype/chat_message/__init__.py | 0 .../chat/doctype/chat_message/chat_message.js | 10 - .../doctype/chat_message/chat_message.json | 91 - .../chat/doctype/chat_message/chat_message.py | 215 -- .../doctype/chat_message/chat_message_list.js | 8 - frappe/chat/doctype/chat_profile/__init__.py | 0 .../chat/doctype/chat_profile/chat_profile.js | 10 - .../doctype/chat_profile/chat_profile.json | 98 - .../chat/doctype/chat_profile/chat_profile.py | 98 - .../doctype/chat_profile/chat_profile_list.js | 11 - frappe/chat/doctype/chat_room/__init__.py | 0 frappe/chat/doctype/chat_room/chat_room.js | 8 - frappe/chat/doctype/chat_room/chat_room.json | 100 - frappe/chat/doctype/chat_room/chat_room.py | 227 -- .../chat/doctype/chat_room/chat_room_list.js | 6 - .../chat/doctype/chat_room_user/__init__.py | 0 .../chat_room_user/chat_room_user.json | 40 - .../doctype/chat_room_user/chat_room_user.py | 8 - frappe/chat/doctype/chat_token/__init__.py | 0 frappe/chat/doctype/chat_token/chat_token.js | 8 - .../chat/doctype/chat_token/chat_token.json | 57 - frappe/chat/doctype/chat_token/chat_token.py | 9 - frappe/chat/util/__init__.py | 13 - frappe/chat/util/test_util.py | 35 - frappe/chat/util/util.py | 108 - frappe/chat/website/__init__.py | 42 - frappe/public/js/chat.bundle.js | 1 - frappe/public/js/frappe/chat.js | 2788 ----------------- frappe/public/less/chat.bundle.less | 461 --- frappe/public/sounds/chat-message.mp3 | Bin 9194 -> 0 bytes frappe/public/sounds/chat-notification.mp3 | Bin 28977 -> 0 bytes 33 files changed, 4475 deletions(-) delete mode 100644 frappe/chat/__init__.py delete mode 100644 frappe/chat/doctype/__init__.py delete mode 100644 frappe/chat/doctype/chat_message/__init__.py delete mode 100644 frappe/chat/doctype/chat_message/chat_message.js delete mode 100644 frappe/chat/doctype/chat_message/chat_message.json delete mode 100644 frappe/chat/doctype/chat_message/chat_message.py delete mode 100644 frappe/chat/doctype/chat_message/chat_message_list.js delete mode 100644 frappe/chat/doctype/chat_profile/__init__.py delete mode 100644 frappe/chat/doctype/chat_profile/chat_profile.js delete mode 100644 frappe/chat/doctype/chat_profile/chat_profile.json delete mode 100644 frappe/chat/doctype/chat_profile/chat_profile.py delete mode 100644 frappe/chat/doctype/chat_profile/chat_profile_list.js delete mode 100644 frappe/chat/doctype/chat_room/__init__.py delete mode 100644 frappe/chat/doctype/chat_room/chat_room.js delete mode 100644 frappe/chat/doctype/chat_room/chat_room.json delete mode 100644 frappe/chat/doctype/chat_room/chat_room.py delete mode 100644 frappe/chat/doctype/chat_room/chat_room_list.js delete mode 100644 frappe/chat/doctype/chat_room_user/__init__.py delete mode 100644 frappe/chat/doctype/chat_room_user/chat_room_user.json delete mode 100644 frappe/chat/doctype/chat_room_user/chat_room_user.py delete mode 100644 frappe/chat/doctype/chat_token/__init__.py delete mode 100644 frappe/chat/doctype/chat_token/chat_token.js delete mode 100644 frappe/chat/doctype/chat_token/chat_token.json delete mode 100644 frappe/chat/doctype/chat_token/chat_token.py delete mode 100644 frappe/chat/util/__init__.py delete mode 100644 frappe/chat/util/test_util.py delete mode 100644 frappe/chat/util/util.py delete mode 100644 frappe/chat/website/__init__.py delete mode 100644 frappe/public/js/chat.bundle.js delete mode 100644 frappe/public/js/frappe/chat.js delete mode 100644 frappe/public/less/chat.bundle.less delete mode 100644 frappe/public/sounds/chat-message.mp3 delete mode 100644 frappe/public/sounds/chat-notification.mp3 diff --git a/frappe/chat/__init__.py b/frappe/chat/__init__.py deleted file mode 100644 index 4c9b1c5db7..0000000000 --- a/frappe/chat/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ - -import frappe -from frappe import _ - -session = frappe.session - -def authenticate(user, raise_err = True): - if session.user == 'Guest': - if not frappe.db.exists('Chat Token', user): - if raise_err: - frappe.throw(_("Sorry, you're not authorized.")) - else: - return False - else: - return True - else: - if user != session.user: - if raise_err: - frappe.throw(_("Sorry, you're not authorized.")) - else: - return False - else: - return True \ No newline at end of file diff --git a/frappe/chat/doctype/__init__.py b/frappe/chat/doctype/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/chat/doctype/chat_message/__init__.py b/frappe/chat/doctype/chat_message/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/chat/doctype/chat_message/chat_message.js b/frappe/chat/doctype/chat_message/chat_message.js deleted file mode 100644 index edaad011db..0000000000 --- a/frappe/chat/doctype/chat_message/chat_message.js +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Chat Message', { - onload: function(frm) { - if(frm.doc.type == 'File') { - frm.set_df_property('content', 'read_only', 1); - } - } -}); diff --git a/frappe/chat/doctype/chat_message/chat_message.json b/frappe/chat/doctype/chat_message/chat_message.json deleted file mode 100644 index 9d2d70c5e0..0000000000 --- a/frappe/chat/doctype/chat_message/chat_message.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "beta": 1, - "creation": "2017-11-10 11:10:40.011099", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "room_type", - "type", - "user", - "room", - "content", - "mentions", - "urls" - ], - "fields": [ - { - "fieldname": "room_type", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Room Type", - "options": "Direct\nGroup\nVisitor", - "reqd": 1 - }, - { - "fieldname": "type", - "fieldtype": "Data", - "label": "Type", - "options": "Content\nFile" - }, - { - "fieldname": "user", - "fieldtype": "Link", - "hidden": 1, - "label": "User", - "options": "User", - "read_only": 1 - }, - { - "fieldname": "room", - "fieldtype": "Link", - "label": "Room", - "options": "Chat Room", - "reqd": 1 - }, - { - "fieldname": "content", - "fieldtype": "Text", - "label": "Content", - "reqd": 1 - }, - { - "fieldname": "mentions", - "fieldtype": "Code", - "hidden": 1, - "label": "Mentions" - }, - { - "fieldname": "urls", - "fieldtype": "Data", - "hidden": 1, - "label": "URLs" - } - ], - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Chat", - "name": "Chat Message", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "search_fields": "content, user", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "content", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file diff --git a/frappe/chat/doctype/chat_message/chat_message.py b/frappe/chat/doctype/chat_message/chat_message.py deleted file mode 100644 index bc470a5e9c..0000000000 --- a/frappe/chat/doctype/chat_message/chat_message.py +++ /dev/null @@ -1,215 +0,0 @@ -# imports - standard imports -import json - -# imports - third-party imports -import requests -from bs4 import BeautifulSoup as Soup - -# imports - module imports -from frappe.model.document import Document -from frappe import _, _dict -import frappe - -# imports - frappe module imports -from frappe.chat import authenticate -from frappe.chat.util import ( - get_if_empty, - check_url, - dictify, - get_emojis, - safe_json_loads, - get_user_doc, - squashify -) - -session = frappe.session - -class ChatMessage(Document): - pass - -def get_message_urls(content): - soup = Soup(content, 'html.parser') - anchors = soup.find_all('a') - urls = [ ] - - for anchor in anchors: - text = anchor.text - - if check_url(text): - urls.append(text) - - return urls - -def get_message_mentions(content): - mentions = [ ] - tokens = content.split(' ') - - for token in tokens: - if token.startswith('@'): - what = token[1:] - if frappe.db.exists('User', what): - mentions.append(what) - else: - if frappe.db.exists('User', token): - mentions.append(token) - - return mentions - -def get_message_meta(content): - ''' - Assumes content to be HTML. Sanitizes the content - into a dict of metadata values. - ''' - meta = _dict( - links = [ ], - mentions = [ ] - ) - - meta.content = content - meta.urls = get_message_urls(content) - meta.mentions = get_message_mentions(content) - - return meta - -def sanitize_message_content(content): - emojis = get_emojis() - - tokens = content.split(' ') - for token in tokens: - if token.startswith(':') and token.endswith(':'): - what = token[1:-1] - - # Expensive, I know. - for emoji in emojis: - for alias in emoji.aliases: - if what == alias: - content = content.replace(token, emoji.emoji) - - return content - -def get_new_chat_message_doc(user, room, content, type = "Content", link = True): - user = get_user_doc(user) - room = frappe.get_doc('Chat Room', room) - - meta = get_message_meta(content) - mess = frappe.new_doc('Chat Message') - mess.room = room.name - mess.room_type = room.type - mess.content = sanitize_message_content(content) - mess.type = type - mess.user = user.name - - mess.mentions = json.dumps(meta.mentions) - mess.urls = ','.join(meta.urls) - mess.save(ignore_permissions = True) - - if link: - room.update(dict( - last_message = mess.name - )) - room.save(ignore_permissions = True) - - return mess - -def get_new_chat_message(user, room, content, type = "Content"): - mess = get_new_chat_message_doc(user, room, content, type) - - resp = dict( - name = mess.name, - user = mess.user, - room = mess.room, - room_type = mess.room_type, - content = json.loads(mess.content) if mess.type in ["File"] else mess.content, - urls = mess.urls, - mentions = json.loads(mess.mentions), - creation = mess.creation, - seen = json.loads(mess._seen) if mess._seen else [ ], - ) - - return resp - -@frappe.whitelist(allow_guest = True) -def send(user, room, content, type = "Content"): - mess = get_new_chat_message(user, room, content, type) - - frappe.publish_realtime('frappe.chat.message:create', mess, room = room, - after_commit = True) - -@frappe.whitelist(allow_guest = True) -def seen(message, user = None): - authenticate(user) - - has_message = frappe.db.exists('Chat Message', message) - - if has_message: - mess = frappe.get_doc('Chat Message', message) - mess.add_seen(user) - mess.load_from_db() - room = mess.room - resp = dict(message = message, data = dict(seen = json.loads(mess._seen) if mess._seen else [])) - - frappe.publish_realtime('frappe.chat.message:update', resp, room = room, after_commit = True) - -def history(room, fields = None, limit = 10, start = None, end = None): - room = frappe.get_doc('Chat Room', room) - mess = frappe.get_all('Chat Message', - filters = [ - ('Chat Message', 'room', '=', room.name), - ('Chat Message', 'room_type', '=', room.type) - ], - fields = fields if fields else [ - 'name', 'room_type', 'room', 'content', 'type', 'user', 'mentions', 'urls', 'creation', '_seen' - ], - order_by = 'creation' - ) - - if not fields or 'seen' in fields: - for m in mess: - m['seen'] = json.loads(m._seen) if m._seen else [ ] - del m['_seen'] - if not fields or 'content' in fields: - for m in mess: - m['content'] = json.loads(m.content) if m.type in ["File"] else m.content - - frappe.enqueue('frappe.chat.doctype.chat_message.chat_message.mark_messages_as_seen', - message_names=[m.name for m in mess], user=frappe.session.user) - - return mess - -def mark_messages_as_seen(message_names, user): - ''' - Marks chat messages as seen, updates the _seen for each message - (should be run in background process) - ''' - for name in message_names: - seen = frappe.db.get_value('Chat Message', name, '_seen') or '[]' - seen = json.loads(seen) - seen.append(user) - seen = json.dumps(seen) - frappe.db.set_value('Chat Message', name, '_seen', seen, update_modified=False) - - frappe.db.commit() - - -@frappe.whitelist() -def get(name, rooms = None, fields = None): - rooms, fields = safe_json_loads(rooms, fields) - - has_message = frappe.db.exists('Chat Message', name) - - if has_message: - dmess = frappe.get_doc('Chat Message', name) - data = dict( - name = dmess.name, - user = dmess.user, - room = dmess.room, - room_type = dmess.room_type, - content = json.loads(dmess.content) if dmess.type in ["File"] else dmess.content, - type = dmess.type, - urls = dmess.urls, - mentions = dmess.mentions, - creation = dmess.creation, - seen = get_if_empty(dmess._seen, [ ]) - ) - - return data \ No newline at end of file diff --git a/frappe/chat/doctype/chat_message/chat_message_list.js b/frappe/chat/doctype/chat_message/chat_message_list.js deleted file mode 100644 index c5b717048b..0000000000 --- a/frappe/chat/doctype/chat_message/chat_message_list.js +++ /dev/null @@ -1,8 +0,0 @@ -frappe.listview_settings['Chat Message'] = { - filters: [ - ['Chat Message', 'user', '==', frappe.session.user, true] - // I need an or_filter here. - // ['Chat Room', 'owner', '==', frappe.session.user, true], - // ['Chat Room', frappe.session.user, 'in', 'users', true] - ] -}; \ No newline at end of file diff --git a/frappe/chat/doctype/chat_profile/__init__.py b/frappe/chat/doctype/chat_profile/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/chat/doctype/chat_profile/chat_profile.js b/frappe/chat/doctype/chat_profile/chat_profile.js deleted file mode 100644 index b27a98faf5..0000000000 --- a/frappe/chat/doctype/chat_profile/chat_profile.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint semi: "never" */ -frappe.ui.form.on('Chat Profile', { - refresh: function (form) { - if ( form.doc.name !== frappe.session.user ) { - form.disable_save() - form.set_read_only(true) - // There's one more that faris@frappe.io told me to add here. form.refresh_fields()? - } - } -}); diff --git a/frappe/chat/doctype/chat_profile/chat_profile.json b/frappe/chat/doctype/chat_profile/chat_profile.json deleted file mode 100644 index eb36f803fe..0000000000 --- a/frappe/chat/doctype/chat_profile/chat_profile.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "autoname": "field:user", - "beta": 1, - "creation": "2017-11-13 18:26:57.943027", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "user", - "status", - "chat_background", - "notifications", - "message_preview", - "notification_tones", - "conversation_tones", - "settings", - "enable_chat" - ], - "fields": [ - { - "fieldname": "user", - "fieldtype": "Link", - "label": "User", - "options": "User", - "reqd": 1 - }, - { - "default": "Online", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Status", - "options": "Online\nAway\nBusy\nOffline" - }, - { - "fieldname": "chat_background", - "fieldtype": "Attach Image", - "label": "Chat Background" - }, - { - "fieldname": "notifications", - "fieldtype": "Section Break", - "label": "Notifications" - }, - { - "default": "1", - "fieldname": "message_preview", - "fieldtype": "Check", - "label": "Message Preview" - }, - { - "default": "1", - "fieldname": "notification_tones", - "fieldtype": "Check", - "label": "Notification Tones" - }, - { - "default": "1", - "fieldname": "conversation_tones", - "fieldtype": "Check", - "label": "Conversation Tones" - }, - { - "fieldname": "settings", - "fieldtype": "Section Break", - "label": "Settings" - }, - { - "default": "1", - "fieldname": "enable_chat", - "fieldtype": "Check", - "label": "Enable Chat" - } - ], - "in_create": 1, - "modified": "2019-11-07 13:21:36.414961", - "modified_by": "Administrator", - "module": "Chat", - "name": "Chat Profile", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/chat/doctype/chat_profile/chat_profile.py b/frappe/chat/doctype/chat_profile/chat_profile.py deleted file mode 100644 index da10a836c4..0000000000 --- a/frappe/chat/doctype/chat_profile/chat_profile.py +++ /dev/null @@ -1,98 +0,0 @@ -# imports - module imports -from frappe.model.document import Document -from frappe import _ -import frappe - -# imports - frappe module imports -from frappe.core.doctype.version.version import get_diff -from frappe.chat.doctype.chat_room import chat_room -from frappe.chat.util import ( - safe_json_loads, - filter_dict, - dictify -) - -session = frappe.session - -class ChatProfile(Document): - def on_update(self): - if not self.is_new(): - b, a = self.get_doc_before_save(), self - diff = dictify(get_diff(a, b)) - if diff: - user = session.user - - fields = [changed[0] for changed in diff.changed] - - if 'status' in fields: - rooms = chat_room.get(user, filters = ['Chat Room', 'type', '=', 'Direct']) - update = dict(user = user, data = dict(status = self.status)) - - for room in rooms: - frappe.publish_realtime('frappe.chat.profile:update', update, room = room.name, after_commit = True) - - if 'enable_chat' in fields: - update = dict(user = user, data = dict(enable_chat = bool(self.enable_chat))) - frappe.publish_realtime('frappe.chat.profile:update', update, user = user, after_commit = True) - -def authenticate(user): - if user != session.user: - frappe.throw(_("Sorry, you're not authorized.")) - -@frappe.whitelist() -def get(user, fields = None): - duser = frappe.get_doc('User', user) - - if frappe.db.exists('Chat Profile', user): - dprof = frappe.get_doc('Chat Profile', user) - - # If you're adding something here, make sure the client recieves it. - profile = dict( - # User - name = duser.name, - email = duser.email, - first_name = duser.first_name, - last_name = duser.last_name, - username = duser.username, - avatar = duser.user_image, - bio = duser.bio, - # Chat Profile - status = dprof.status, - chat_background = dprof.chat_background, - message_preview = bool(dprof.message_preview), - notification_tones = bool(dprof.notification_tones), - conversation_tones = bool(dprof.conversation_tones), - enable_chat = bool(dprof.enable_chat) - ) - profile = filter_dict(profile, fields) - - return dictify(profile) - -@frappe.whitelist() -def create(user, exists_ok = False, fields = None): - authenticate(user) - - exists_ok, fields = safe_json_loads(exists_ok, fields) - - try: - dprof = frappe.new_doc('Chat Profile') - dprof.user = user - dprof.save(ignore_permissions = True) - except frappe.DuplicateEntryError: - frappe.clear_messages() - if not exists_ok: - frappe.throw(_('Chat Profile for User {0} exists.').format(user)) - - profile = get(user, fields = fields) - - return profile - -@frappe.whitelist() -def update(user, data): - authenticate(user) - - data = safe_json_loads(data) - - dprof = frappe.get_doc('Chat Profile', user) - dprof.update(data) - dprof.save(ignore_permissions = True) \ No newline at end of file diff --git a/frappe/chat/doctype/chat_profile/chat_profile_list.js b/frappe/chat/doctype/chat_profile/chat_profile_list.js deleted file mode 100644 index 4d97b75e65..0000000000 --- a/frappe/chat/doctype/chat_profile/chat_profile_list.js +++ /dev/null @@ -1,11 +0,0 @@ -frappe.listview_settings['Chat Profile'] = -{ - get_indicator: function (doc) - { - const status = frappe.utils.squash(frappe.chat.profile.STATUSES.filter( - s => s.name === doc.status - )); - - return [__(status.name), status.color, `status,=,${status.name}`] - } -}; \ No newline at end of file diff --git a/frappe/chat/doctype/chat_room/__init__.py b/frappe/chat/doctype/chat_room/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/chat/doctype/chat_room/chat_room.js b/frappe/chat/doctype/chat_room/chat_room.js deleted file mode 100644 index 00b9c8d8f7..0000000000 --- a/frappe/chat/doctype/chat_room/chat_room.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Chat Room', { - refresh: function (form) { - - } -}); diff --git a/frappe/chat/doctype/chat_room/chat_room.json b/frappe/chat/doctype/chat_room/chat_room.json deleted file mode 100644 index 1417306c45..0000000000 --- a/frappe/chat/doctype/chat_room/chat_room.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "autoname": "CR.#####", - "beta": 1, - "creation": "2017-11-08 15:27:21.156667", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "type", - "room_name", - "avatar", - "last_message", - "message_count", - "owner", - "user_list", - "users" - ], - "fields": [ - { - "default": "Direct", - "fieldname": "type", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Type", - "options": "Direct\nGroup\nVisitor", - "reqd": 1, - "set_only_once": 1 - }, - { - "depends_on": "eval:doc.type==\"Group\"", - "fieldname": "room_name", - "fieldtype": "Data", - "label": "Name" - }, - { - "depends_on": "eval:doc.type==\"Group\"", - "fieldname": "avatar", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Avatar" - }, - { - "fieldname": "last_message", - "fieldtype": "Data", - "hidden": 1, - "label": "Last Message" - }, - { - "fieldname": "message_count", - "fieldtype": "Int", - "hidden": 1, - "label": "Message Count" - }, - { - "fieldname": "owner", - "fieldtype": "Data", - "hidden": 1, - "label": "Owner", - "read_only": 1 - }, - { - "fieldname": "user_list", - "fieldtype": "Section Break", - "label": "Users" - }, - { - "fieldname": "users", - "fieldtype": "Table", - "label": "Users", - "options": "Chat Room User" - } - ], - "image_field": "avatar", - "modified": "2019-11-07 13:20:24.625329", - "modified_by": "Administrator", - "module": "Chat", - "name": "Chat Room", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 1, - "share": 1, - "write": 1 - } - ], - "search_fields": "room_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "room_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/chat/doctype/chat_room/chat_room.py b/frappe/chat/doctype/chat_room/chat_room.py deleted file mode 100644 index bdbee44d7a..0000000000 --- a/frappe/chat/doctype/chat_room/chat_room.py +++ /dev/null @@ -1,227 +0,0 @@ -# imports - module imports -from frappe.model.document import Document -from frappe import _ -import frappe - -# imports - frappe module imports -from frappe.chat import authenticate -from frappe.core.doctype.version.version import get_diff -from frappe.chat.doctype.chat_message import chat_message -from frappe.chat.util import ( - safe_json_loads, - dictify, - listify, - squashify, - get_if_empty -) - -session = frappe.session - - -def is_direct(owner, other, bidirectional=False): - def get_room(owner, other): - room = frappe.get_all('Chat Room', filters=[ - ['Chat Room', 'type', 'in', ('Direct', 'Visitor')], - ['Chat Room', 'owner', '=', owner], - ['Chat Room User', 'user', '=', other] - ], distinct=True) - - return room - - exists = len(get_room(owner, other)) == 1 - if bidirectional: - exists = exists or len(get_room(other, owner)) == 1 - - return exists - - -def get_chat_room_user_set(users, filter_=None): - seen, uset = set(), list() - - for u in users: - if filter_(u) and u.user not in seen: - uset.append(u) - seen.add(u.user) - - return uset - - -class ChatRoom(Document): - def validate(self): - if self.is_new(): - users = get_chat_room_user_set(self.users, filter_=lambda u: u.user != session.user) - self.update(dict( - users=users - )) - - if self.type == "Direct": - if len(self.users) != 1: - frappe.throw(_('{0} room must have atmost one user.').format(self.type)) - - other = squashify(self.users) - - if self.is_new(): - if is_direct(self.owner, other.user, bidirectional=True): - frappe.throw(_('Direct room with {0} already exists.').format(other.user)) - - if self.type == "Group" and not self.room_name: - frappe.throw(_('Group name cannot be empty.')) - - def on_update(self): - if not self.is_new(): - before = self.get_doc_before_save() - if not before: return - - after = self - diff = dictify(get_diff(before, after)) - if diff: - update = {} - for changed in diff.changed: - field, old, new = changed - - if field == 'last_message': - new = chat_message.get(new) - - update.update({field: new}) - - if diff.added or diff.removed: - update.update(dict(users=[u.user for u in self.users])) - - update = dict(room=self.name, data=update) - - frappe.publish_realtime('frappe.chat.room:update', update, room=self.name, - after_commit=True) - - -@frappe.whitelist(allow_guest=True) -def get(user=None, token=None, rooms=None, fields=None, filters=None): - # There is this horrible bug out here. - # Looks like if frappe.call sends optional arguments (not in right order), - # the argument turns to an empty string. - # I'm not even going to think searching for it. - # Hence, the hack was get_if_empty (previous assign_if_none) - # - Achilles Rasquinha achilles@frappe.io - data = user or token - authenticate(data) - - rooms, fields, filters = safe_json_loads(rooms, fields, filters) - - rooms = listify(get_if_empty(rooms, [])) - fields = listify(get_if_empty(fields, [])) - - const = [] # constraints - if rooms: - const.append(['Chat Room', 'name', 'in', rooms]) - if filters: - if isinstance(filters[0], list): - const = const + filters - else: - const.append(filters) - - default = ['name', 'type', 'room_name', 'creation', 'owner', 'avatar'] - handle = ['users', 'last_message'] - - param = [f for f in fields if f not in handle] - - rooms = frappe.get_all('Chat Room', - or_filters=[ - ['Chat Room', 'owner', '=', frappe.session.user], - ['Chat Room User', 'user', '=', frappe.session.user] - ], - filters=const, - fields=param + ['name'] if param else default, - distinct=True - ) - - if not fields or 'users' in fields: - for i, r in enumerate(rooms): - droom = frappe.get_doc('Chat Room', r.name) - rooms[i]['users'] = [] - - for duser in droom.users: - rooms[i]['users'].append(duser.user) - - if not fields or 'last_message' in fields: - for i, r in enumerate(rooms): - droom = frappe.get_doc('Chat Room', r.name) - if droom.last_message: - rooms[i]['last_message'] = chat_message.get(droom.last_message) - else: - rooms[i]['last_message'] = None - - rooms = squashify(dictify(rooms)) - - return rooms - - -@frappe.whitelist(allow_guest=True) -def create(kind, token, users=None, name=None): - authenticate(token) - - users = safe_json_loads(users) - create = True - - if kind == 'Visitor': - room = squashify(frappe.db.sql(""" - SELECT name - FROM `tabChat Room` - WHERE owner=%s - """, (frappe.session.user), as_dict=True)) - - if room: - room = frappe.get_doc('Chat Room', room.name) - create = False - - if create: - room = frappe.new_doc('Chat Room') - room.type = kind - room.owner = frappe.session.user - room.room_name = name - - dusers = [] - - if kind != 'Visitor': - if users: - users = listify(users) - for user in users: - duser = frappe.new_doc('Chat Room User') - duser.user = user - dusers.append(duser) - - room.users = dusers - else: - dsettings = frappe.get_single('Website Settings') - room.room_name = dsettings.chat_room_name - - users = [user for user in room.users] if hasattr(room, 'users') else [] - - for user in dsettings.chat_operators: - if user.user not in users: - # appending user to room.users will remove the user from chat_operators - # this is undesirable, create a new Chat Room User instead - chat_room_user = {"doctype": "Chat Room User", "user": user.user} - room.append('users', chat_room_user) - - room.save(ignore_permissions=True) - - room = get(token=token, rooms=room.name) - if room: - users = [room.owner] + [u for u in room.users] - - for user in users: - frappe.publish_realtime('frappe.chat.room:create', room, user=user, after_commit=True) - - return room - - -@frappe.whitelist(allow_guest=True) -def history(room, user, fields=None, limit=10, start=None, end=None): - if frappe.get_doc('Chat Room', room).type != 'Visitor': - authenticate(user) - - fields = safe_json_loads(fields) - - mess = chat_message.history(room, limit=limit, start=start, end=end) - mess = squashify(mess) - - return dictify(mess) diff --git a/frappe/chat/doctype/chat_room/chat_room_list.js b/frappe/chat/doctype/chat_room/chat_room_list.js deleted file mode 100644 index 70c708c7bd..0000000000 --- a/frappe/chat/doctype/chat_room/chat_room_list.js +++ /dev/null @@ -1,6 +0,0 @@ -frappe.listview_settings['Chat Room'] = { - filters: [ - ['Chat Room', 'owner', '=', frappe.session.user, true], - ['Chat Room User', 'user', '=', frappe.session.user, true] - ] -}; \ No newline at end of file diff --git a/frappe/chat/doctype/chat_room_user/__init__.py b/frappe/chat/doctype/chat_room_user/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/chat/doctype/chat_room_user/chat_room_user.json b/frappe/chat/doctype/chat_room_user/chat_room_user.json deleted file mode 100644 index f7bdf6706b..0000000000 --- a/frappe/chat/doctype/chat_room_user/chat_room_user.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "beta": 1, - "creation": "2017-11-08 15:24:21.029314", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "user", - "is_admin" - ], - "fields": [ - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "is_admin", - "fieldtype": "Check", - "label": "Admin" - } - ], - "in_create": 1, - "istable": 1, - "modified": "2019-11-07 13:21:05.297337", - "modified_by": "Administrator", - "module": "Chat", - "name": "Chat Room User", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/chat/doctype/chat_room_user/chat_room_user.py b/frappe/chat/doctype/chat_room_user/chat_room_user.py deleted file mode 100644 index f6dbdc7659..0000000000 --- a/frappe/chat/doctype/chat_room_user/chat_room_user.py +++ /dev/null @@ -1,8 +0,0 @@ -# imports - module imports -from frappe.model.document import Document -import frappe - -session = frappe.session - -class ChatRoomUser(Document): - pass \ No newline at end of file diff --git a/frappe/chat/doctype/chat_token/__init__.py b/frappe/chat/doctype/chat_token/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/chat/doctype/chat_token/chat_token.js b/frappe/chat/doctype/chat_token/chat_token.js deleted file mode 100644 index 78f03026ec..0000000000 --- a/frappe/chat/doctype/chat_token/chat_token.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Chat Token', { - refresh: function(frm) { - - } -}); diff --git a/frappe/chat/doctype/chat_token/chat_token.json b/frappe/chat/doctype/chat_token/chat_token.json deleted file mode 100644 index b73505ac2c..0000000000 --- a/frappe/chat/doctype/chat_token/chat_token.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "autoname": "field:token", - "beta": 1, - "creation": "2018-03-26 18:20:13.825652", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "token", - "ip_address", - "country" - ], - "fields": [ - { - "fieldname": "token", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Token", - "reqd": 1 - }, - { - "fieldname": "ip_address", - "fieldtype": "Data", - "label": "IP Address" - }, - { - "fieldname": "country", - "fieldtype": "Data", - "label": "Country" - } - ], - "in_create": 1, - "modified": "2019-11-07 13:21:24.514558", - "modified_by": "Administrator", - "module": "Chat", - "name": "Chat Token", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/chat/doctype/chat_token/chat_token.py b/frappe/chat/doctype/chat_token/chat_token.py deleted file mode 100644 index 0be51b6081..0000000000 --- a/frappe/chat/doctype/chat_token/chat_token.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies and contributors -# License: MIT. See LICENSE - -import frappe -from frappe.model.document import Document - -class ChatToken(Document): - pass diff --git a/frappe/chat/util/__init__.py b/frappe/chat/util/__init__.py deleted file mode 100644 index 383df581cd..0000000000 --- a/frappe/chat/util/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# imports - module imports -from frappe.chat.util.util import ( - get_user_doc, - squashify, - safe_json_loads, - filter_dict, - get_if_empty, - listify, - dictify, - check_url, - create_test_user, - get_emojis -) \ No newline at end of file diff --git a/frappe/chat/util/test_util.py b/frappe/chat/util/test_util.py deleted file mode 100644 index e2d05a4024..0000000000 --- a/frappe/chat/util/test_util.py +++ /dev/null @@ -1,35 +0,0 @@ -# imports - standard imports -import unittest - -# imports - module imports -from frappe.chat.util import ( - get_user_doc, - safe_json_loads -) -import frappe - -class TestChatUtil(unittest.TestCase): - def test_safe_json_loads(self): - number = safe_json_loads("1") - self.assertEqual(type(number), int) - - number = safe_json_loads("1.0") - self.assertEqual(type(number), float) - - string = safe_json_loads("foobar") - self.assertEqual(type(string), str) - - array = safe_json_loads('[{ "foo": "bar" }]') - self.assertEqual(type(array), list) - - objekt = safe_json_loads('{ "foo": "bar" }') - self.assertEqual(type(objekt), dict) - - true, null = safe_json_loads("true", "null") - self.assertEqual(true, True) - self.assertEqual(null, None) - - def test_get_user_doc(self): - # Needs more test cases. - user = get_user_doc() - self.assertEqual(user.name, frappe.session.user) \ No newline at end of file diff --git a/frappe/chat/util/util.py b/frappe/chat/util/util.py deleted file mode 100644 index b7e7991c2b..0000000000 --- a/frappe/chat/util/util.py +++ /dev/null @@ -1,108 +0,0 @@ -# imports - standard imports -import json -from collections.abc import MutableMapping, MutableSequence, Sequence - -# imports - third-party imports -import requests -from urllib.parse import urlparse - -# imports - module imports -import frappe -from frappe.exceptions import DuplicateEntryError -from frappe.model.document import Document - -session = frappe.session - - -def get_user_doc(user = None): - if isinstance(user, Document): - return user - - user = user or session.user - user = frappe.get_doc('User', user) - - return user - -def squashify(what): - if isinstance(what, Sequence) and len(what) == 1: - return what[0] - - return what - -def safe_json_loads(*args): - results = [] - - for arg in args: - try: - arg = json.loads(arg) - except Exception: - pass - - results.append(arg) - - return squashify(results) - -def filter_dict(what, keys, ignore = False): - copy = dict() - - if keys: - for k in keys: - if k not in what and not ignore: - raise KeyError('{key} not in dict.'.format(key = k)) - else: - copy.update({ - k: what[k] - }) - else: - copy = what.copy() - - return copy - -def get_if_empty(a, b): - if not a: - a = b - return a - -def listify(arg): - if not isinstance(arg, list): - arg = [arg] - return arg - -def dictify(arg): - if isinstance(arg, MutableSequence): - for i, a in enumerate(arg): - arg[i] = dictify(a) - elif isinstance(arg, MutableMapping): - arg = frappe._dict(arg) - - return arg - -def check_url(what, raise_err = False): - if not urlparse(what).scheme: - if raise_err: - raise ValueError('{what} not a valid URL.') - else: - return False - - return True - -def create_test_user(module): - try: - test_user = frappe.new_doc('User') - test_user.first_name = '{module}'.format(module = module) - test_user.email = 'testuser.{module}@example.com'.format(module = module) - test_user.save() - except DuplicateEntryError: - frappe.log('Test User Chat Profile exists.') - -def get_emojis(): - redis = frappe.cache() - emojis = redis.hget('frappe_emojis', 'emojis') - - if not emojis: - resp = requests.get('http://git.io/frappe-emoji') - if resp.ok: - emojis = resp.json() - redis.hset('frappe_emojis', 'emojis', emojis) - - return dictify(emojis) diff --git a/frappe/chat/website/__init__.py b/frappe/chat/website/__init__.py deleted file mode 100644 index 12affd2782..0000000000 --- a/frappe/chat/website/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ - -import frappe -from frappe.chat.util import filter_dict, safe_json_loads - -from frappe.sessions import get_geo_ip_country - -@frappe.whitelist(allow_guest = True) -def settings(fields = None): - fields = safe_json_loads(fields) - - dsettings = frappe.get_single('Website Settings') - response = dict( - socketio = dict( - port = frappe.conf.socketio_port - ), - enable = bool(dsettings.chat_enable), - enable_from = dsettings.chat_enable_from, - enable_to = dsettings.chat_enable_to, - room_name = dsettings.chat_room_name, - welcome_message = dsettings.chat_welcome_message, - operators = [ - duser.user for duser in dsettings.chat_operators - ] - ) - - if fields: - response = filter_dict(response, fields) - - return response - -@frappe.whitelist(allow_guest = True) -def token(): - dtoken = frappe.new_doc('Chat Token') - - dtoken.token = frappe.generate_hash() - dtoken.ip_address = frappe.local.request_ip - country = get_geo_ip_country(dtoken.ip_address) - if country: - dtoken.country = country['iso_code'] - dtoken.save(ignore_permissions = True) - - return dtoken.token \ No newline at end of file diff --git a/frappe/public/js/chat.bundle.js b/frappe/public/js/chat.bundle.js deleted file mode 100644 index 5f9a91ebb7..0000000000 --- a/frappe/public/js/chat.bundle.js +++ /dev/null @@ -1 +0,0 @@ -import "./frappe/chat"; diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js deleted file mode 100644 index fd440dcbbc..0000000000 --- a/frappe/public/js/frappe/chat.js +++ /dev/null @@ -1,2788 +0,0 @@ -// Frappe Chat -// Author - Achilles Rasquinha - -import Fuse from 'fuse.js' -import hyper from '../lib/hyper.min' - -import './socketio_client' - -import './ui/dialog' -import './ui/capture' - -import './utils/user' - -/* eslint semi: "never" */ -// Fuck semicolons - https://mislav.net/2010/05/semicolons - -// frappe extensions - -/** - * @description The base class for all Frappe Errors. - * - * @example - * try - * throw new frappe.Error("foobar") - * catch (e) - * console.log(e.name) - * // returns "FrappeError" - * - * @see https://stackoverflow.com/a/32749533 - * @todo Requires "transform-builtin-extend" for Babel 6 - */ -frappe.Error = Error -// class extends Error { -// constructor (message) { -// super (message) - -// this.name = 'FrappeError' - -// if ( typeof Error.captureStackTrace === 'function' ) -// Error.captureStackTrace(this, this.constructor) -// else -// this.stack = (new Error(message)).stack -// } -// } - -/** - * @description TypeError - */ -frappe.TypeError = TypeError -// class extends frappe.Error { -// constructor (message) { -// super (message) - -// this.name = this.constructor.name -// } -// } - -/** - * @description ValueError - */ -frappe.ValueError = Error -// class extends frappe.Error { -// constructor (message) { -// super (message) - -// this.name = this.constructor.name -// } -// } - -/** - * @description ImportError - */ -frappe.ImportError = Error -// class extends frappe.Error { -// constructor (message) { -// super (message) - -// this.name = this.constructor.name -// } -// } - -// frappe.datetime -frappe.provide('frappe.datetime') - -/** - * @description Frappe's datetime object. (Inspired by Python's datetime object). - * - * @example - * const datetime = new frappe.datetime.datetime() - */ -frappe.datetime.datetime = class { - /** - * @description Frappe's datetime Class's constructor. - */ - constructor (instance, format = null) { - if ( typeof moment === 'undefined' ) - throw new frappe.ImportError(`Moment.js not installed.`) - - this.moment = instance ? moment(instance, format) : moment() - } - - /** - * @description Returns a formatted string of the datetime object. - */ - format (format = null) { - const formatted = this.moment.format(format) - return formatted - } -} - -/** - * @description Frappe's daterange object. - * - * @example - * const range = new frappe.datetime.range(frappe.datetime.now(), frappe.datetime.now()) - * range.contains(frappe.datetime.now()) - */ -frappe.datetime.range = class { - constructor (start, end) { - if ( typeof moment === undefined ) - throw new frappe.ImportError(`Moment.js not installed.`) - - this.start = start - this.end = end - } - - contains (datetime) { - const contains = datetime.moment.isBetween(this.start.moment, this.end.moment) - return contains - } -} - -/** - * @description Returns the current datetime. - * - * @example - * const datetime = new frappe.datetime.now() - */ -frappe.datetime.now = () => new frappe.datetime.datetime() - -frappe.datetime.equal = (a, b, type) => { - a = a.moment - b = b.moment - - const equal = a.isSame(b, type) - - return equal -} - -/** - * @description Compares two frappe.datetime.datetime objects. - * - * @param {frappe.datetime.datetime} a - A frappe.datetime.datetime/moment object. - * @param {frappe.datetime.datetime} b - A frappe.datetime.datetime/moment object. - * - * @returns {number} 0 (if a and b are equal), 1 (if a is before b), -1 (if a is after b). - * - * @example - * frappe.datetime.compare(frappe.datetime.now(), frappe.datetime.now()) - * // returns 0 - * const then = frappe.datetime.now() - * - * frappe.datetime.compare(then, frappe.datetime.now()) - * // returns 1 - */ -frappe.datetime.compare = (a, b) => { - a = a.moment - b = b.moment - - if ( a.isBefore(b) ) - return 1 - else - if ( b.isBefore(a) ) - return -1 - else - return 0 -} - -// frappe.quick_edit -frappe.quick_edit = (doctype, docname, fn) => { - return new Promise(resolve => { - frappe.model.with_doctype(doctype, () => { - frappe.db.get_doc(doctype, docname).then(doc => { - const meta = frappe.get_meta(doctype) - const fields = meta.fields - const required = fields.filter(f => f.reqd || f.bold && !f.read_only) - - required.map(f => { - if(f.fieldname == 'content' && doc.type == 'File') { - f['read_only'] = 1; - } - }) - - const dialog = new frappe.ui.Dialog({ - title: __('Edit') + `${doctype} (${docname})`, - fields: required, - action: { - primary: { - label: __("Save"), - onsubmit: (values) => { - frappe.call('frappe.client.save', - { doc: { doctype: doctype, docname: docname, ...doc, ...values } }) - .then(r => { - if ( fn ) - fn(r.message) - - resolve(r.message) - }) - - dialog.hide() - } - }, - secondary: { - label: __("Discard") - } - } - }) - dialog.set_values(doc) - - const $element = $(dialog.body) - $element.append(` -
- -
- `) - $element.find('.qe-fp').click(() => { - dialog.hide() - frappe.set_route('Form', doctype, docname) - }) - - dialog.show() - }) - }) - }) -} - -// frappe._ -// frappe's utility namespace. -frappe.provide('frappe._') - -// String Utilities - -/** - * @description Python-inspired format extension for string objects. - * - * @param {string} string - A string with placeholders. - * @param {object} object - An object with placeholder, value pairs. - * - * @return {string} - The formatted string. - * - * @example - * frappe._.format('{foo} {bar}', { bar: 'foo', foo: 'bar' }) - * // returns "bar foo" - */ -frappe._.format = (string, object) => { - for (const key in object) - string = string.replace(`{${key}}`, object[key]) - - return string -} - -/** - * @description Fuzzy Search a given query within a dataset. - * - * @param {string} query - A query string. - * @param {array} dataset - A dataset to search within, can contain singletons or objects. - * @param {object} options - Options as per fuze.js - * - * @return {array} - The fuzzy matched index/object within the dataset. - * - * @example - * frappe._.fuzzy_search("foobar", ["foobar", "bartender"]) - * // returns [0, 1] - * - * @see http://fusejs.io - */ -frappe._.fuzzy_search = (query, dataset, options) => { - const DEFAULT = { - shouldSort: true, - threshold: 0.6, - location: 0, - distance: 100, - minMatchCharLength: 1, - maxPatternLength: 32 - } - options = { ...DEFAULT, ...options } - - const fuse = new Fuse(dataset, options) - const result = fuse.search(query) - - return result -} - -/** - * @description Pluralizes a given word. - * - * @param {string} word - The word to be pluralized. - * @param {number} count - The count. - * - * @return {string} - The pluralized string. - * - * @example - * frappe._.pluralize('member', 1) - * // returns "member" - * frappe._.pluralize('members', 0) - * // returns "members" - * - * @todo Handle more edge cases. - */ -frappe._.pluralize = (word, count = 0, suffix = 's') => `${word}${count === 1 ? '' : suffix}` - -/** - * @description Captializes a given string. - * - * @param {word} - The word to be capitalized. - * - * @return {string} - The capitalized word. - * - * @example - * frappe._.capitalize('foobar') - * // returns "Foobar" - */ -frappe._.capitalize = word => `${word.charAt(0).toUpperCase()}${word.slice(1)}` - -// Array Utilities - -/** - * @description Returns the first element of an array. - * - * @param {array} array - The array. - * - * @returns - The first element of an array, undefined elsewise. - * - * @example - * frappe._.head([1, 2, 3]) - * // returns 1 - * frappe._.head([]) - * // returns undefined - */ -frappe._.head = arr => frappe._.is_empty(arr) ? undefined : arr[0] - -/** - * @description Returns a copy of the given array (shallow). - * - * @param {array} array - The array to be copied. - * - * @returns {array} - The copied array. - * - * @example - * frappe._.copy_array(["foobar", "barfoo"]) - * // returns ["foobar", "barfoo"] - * - * @todo Add optional deep copy. - */ -frappe._.copy_array = array => { - if ( Array.isArray(array) ) - return array.slice() - else - throw frappe.TypeError(`Expected Array, recieved ${typeof array} instead.`) -} - -/** - * @description Check whether an array|string|object|jQuery is empty. - * - * @param {any} value - The value to be checked on. - * - * @returns {boolean} - Returns if the object is empty. - * - * @example - * frappe._.is_empty([]) // returns true - * frappe._.is_empty(["foo"]) // returns false - * - * frappe._.is_empty("") // returns true - * frappe._.is_empty("foo") // returns false - * - * frappe._.is_empty({ }) // returns true - * frappe._.is_empty({ foo: "bar" }) // returns false - * - * frappe._.is_empty($('.papito')) // returns false - * - * @todo Handle other cases. - */ -frappe._.is_empty = value => { - let empty = false - - if ( value === undefined || value === null ) - empty = true - else - if ( Array.isArray(value) || typeof value === 'string' || value instanceof $ ) - empty = value.length === 0 - else - if ( typeof value === 'object' ) - empty = Object.keys(value).length === 0 - - return empty -} - -/** - * @description Converts a singleton to an array, if required. - * - * @param {object} item - An object - * - * @example - * frappe._.as_array("foo") - * // returns ["foo"] - * - * frappe._.as_array(["foo"]) - * // returns ["foo"] - * - * @see https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList-T...- - */ -frappe._.as_array = item => Array.isArray(item) ? item : [item] - -/** - * @description Return a singleton if array contains a single element. - * - * @param {array} list - An array to squash. - * - * @returns {array|object} - Returns an array if there's more than 1 object else the first object itself. - * - * @example - * frappe._.squash(["foo"]) - * // returns "foo" - * - * frappe._.squash(["foo", "bar"]) - * // returns ["foo", "bar"] - */ -frappe._.squash = list => Array.isArray(list) && list.length === 1 ? list[0] : list - -/** - * @description Returns true, if the current device is a mobile device. - * - * @example - * frappe._.is_mobile() - * // returns true|false - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent - */ -frappe._.is_mobile = () => { - const regex = new RegExp("Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini", "i") - const agent = navigator.userAgent - const mobile = regex.test(agent) - - return mobile -} - -/** - * @description Removes falsey values from an array. - * - * @example - * frappe._.compact([1, 2, false, NaN, '']) - * // returns [1, 2] - */ -frappe._.compact = array => array.filter(Boolean) - -// extend utils to base. -frappe.utils = { ...frappe.utils, ...frappe._ } - -// frappe extensions - -// frappe.user extensions -/** - * @description Returns the first name of a User. - * - * @param {string} user - User - * - * @returns The first name of the user. - * - * @example - * frappe.user.first_name("Rahul Malhotra") - * // returns "Rahul" - */ -frappe.provide('frappe.user') -frappe.user.first_name = user => frappe._.head(frappe.user.full_name(user).split(" ")) - -frappe.provide('frappe.ui.keycode') -frappe.ui.keycode = { RETURN: 13 } - -/** - * @description Frappe's Store Class - */ - // frappe.stores - A registry for frappe stores. -frappe.provide('frappe.stores') -frappe.stores = [ ] -frappe.Store = class -{ - /** - * @description Frappe's Store Class's constructor. - * - * @param {string} name - Name of the logger. - */ - constructor (name) { - if ( typeof name !== 'string' ) - throw new frappe.TypeError(`Expected string for name, got ${typeof name} instead.`) - this.name = name - } - - /** - * @description Get instance of frappe.Store (return registered one if declared). - * - * @param {string} name - Name of the store. - */ - static get (name) { - if ( !(name in frappe.stores) ) - frappe.stores[name] = new frappe.Store(name) - return frappe.stores[name] - } - - set (key, value) { localStorage.setItem(`${this.name}:${key}`, value) } - get (key, value) { return localStorage.getItem(`${this.name}:${key}`) } -} - -// frappe.loggers - A registry for frappe loggers. -frappe.provide('frappe.loggers') -/** - * @description Frappe's Logger Class - * - * @example - * frappe.log = frappe.Logger.get('foobar') - * frappe.log.level = frappe.Logger.DEBUG - * - * frappe.log.info('foobar') - * // prints '[timestamp] foobar: foobar' - */ -frappe.Logger = class { - /** - * @description Frappe's Logger Class's constructor. - * - * @param {string} name - Name of the logger. - */ - constructor (name, level) { - if ( typeof name !== 'string' ) - throw new frappe.TypeError(`Expected string for name, got ${typeof name} instead.`) - - this.name = name - this.level = level - - if ( !this.level ) { - if ( frappe.boot.developer_mode ) - this.level = frappe.Logger.ERROR - else - this.level = frappe.Logger.NOTSET - } - this.format = frappe.Logger.FORMAT - } - - /** - * @description Get instance of frappe.Logger (return registered one if declared). - * - * @param {string} name - Name of the logger. - */ - static get (name, level) { - if ( !(name in frappe.loggers) ) - frappe.loggers[name] = new frappe.Logger(name, level) - return frappe.loggers[name] - } - - debug (message) { this.log(message, frappe.Logger.DEBUG) } - info (message) { this.log(message, frappe.Logger.INFO) } - warn (message) { this.log(message, frappe.Logger.WARN) } - error (message) { this.log(message, frappe.Logger.ERROR) } - - log (message, level) { - const timestamp = frappe.datetime.now() - - if ( level.value <= this.level.value ) { - const format = frappe._.format(this.format, { - time: timestamp.format('HH:mm:ss'), - name: this.name - }) - console.log(`%c ${format}:`, `color: ${level.color}`, message) - } - } -} - -frappe.Logger.DEBUG = { value: 10, color: '#616161', name: 'DEBUG' } -frappe.Logger.INFO = { value: 20, color: '#2196F3', name: 'INFO' } -frappe.Logger.WARN = { value: 30, color: '#FFC107', name: 'WARN' } -frappe.Logger.ERROR = { value: 40, color: '#F44336', name: 'ERROR' } -frappe.Logger.NOTSET = { value: 0, name: 'NOTSET' } - -frappe.Logger.FORMAT = '{time} {name}' - -// frappe.chat -frappe.provide('frappe.chat') - -frappe.log = frappe.Logger.get('frappe.chat', frappe.Logger.NOTSET) - -// frappe.chat.profile -frappe.provide('frappe.chat.profile') - -/** - * @description Create a Chat Profile. - * - * @param {string|array} fields - (Optional) fields to be retrieved after creating a Chat Profile. - * @param {function} fn - (Optional) callback with the returned Chat Profile. - * - * @returns {Promise} - * - * @example - * frappe.chat.profile.create(console.log) - * - * frappe.chat.profile.create("status").then(console.log) // { status: "Online" } - */ -frappe.chat.profile.create = (fields, fn) => { - if ( typeof fields === "function" ) { - fn = fields - fields = null - } else - if ( typeof fields === "string" ) - fields = frappe._.as_array(fields) - - return new Promise(resolve => { - frappe.call("frappe.chat.doctype.chat_profile.chat_profile.create", - { user: frappe.session.user, exists_ok: true, fields: fields }, - response => { - if ( fn ) - fn(response.message) - - resolve(response.message) - }) - }) -} - -/** - * @description Updates a Chat Profile. - * - * @param {string} user - (Optional) Chat Profile User, defaults to session user. - * @param {object} update - (Required) Updates to be dispatched. - * - * @example - * frappe.chat.profile.update(frappe.session.user, { "status": "Offline" }) - */ -frappe.chat.profile.update = (user, update, fn) => { - return new Promise(resolve => { - frappe.call("frappe.chat.doctype.chat_profile.chat_profile.update", - { user: user || frappe.session.user, data: update }, - response => { - if ( fn ) - fn(response.message) - - resolve(response.message) - }) - }) -} - -// frappe.chat.profile.on -frappe.provide('frappe.chat.profile.on') - -/** - * @description Triggers on a Chat Profile update of a user (Only if there's a one-on-one conversation). - * - * @param {function} fn - (Optional) callback with the User and the Chat Profile update. - * - * @returns {Promise} - * - * @example - * frappe.chat.profile.on.update(function (user, update) - * { - * // do stuff - * }) - */ -frappe.chat.profile.on.update = function (fn) { - frappe.realtime.on("frappe.chat.profile:update", r => fn(r.user, r.data)) -} -frappe.chat.profile.STATUSES -= -[ - { - name: "Online", - color: "green" - }, - { - name: "Away", - color: "yellow" - }, - { - name: "Busy", - color: "red" - }, - { - name: "Offline", - color: "gray" - } -] - -// frappe.chat.room -frappe.provide('frappe.chat.room') - -/** - * @description Creates a Chat Room. - * - * @param {string} kind - (Required) "Direct", "Group" or "Visitor". - * @param {string} owner - (Optional) Chat Room owner (defaults to current user). - * @param {string|array} users - (Required for "Direct" and "Visitor", Optional for "Group") User(s) within Chat Room. - * @param {string} name - Chat Room name. - * @param {function} fn - callback with created Chat Room. - * - * @returns {Promise} - * - * @example - * frappe.chat.room.create("Direct", frappe.session.user, "foo@bar.com", function (room) { - * // do stuff - * }) - * frappe.chat.room.create("Group", frappe.session.user, ["santa@gmail.com", "banta@gmail.com"], "Santa and Banta", function (room) { - * // do stuff - * }) - */ -frappe.chat.room.create = function (kind, owner, users, name, fn) { - if ( typeof name === "function" ) { - fn = name - name = null - } - - users = frappe._.as_array(users) - - return new Promise(resolve => { - frappe.call("frappe.chat.doctype.chat_room.chat_room.create", - { kind: kind, token: owner || frappe.session.user, users: users, name: name }, - r => { - let room = r.message - room = { ...room, creation: new frappe.datetime.datetime(room.creation) } - - if ( fn ) - fn(room) - - resolve(room) - }) - }) -} - -/** - * @description Returns Chat Room(s). - * - * @param {string|array} names - (Optional) Chat Room(s) to retrieve. - * @param {string|array} fields - (Optional) fields to be retrieved for each Chat Room. - * @param {function} fn - (Optional) callback with the returned Chat Room(s). - * - * @returns {Promise} - * - * @example - * frappe.chat.room.get(function (rooms) { - * // do stuff - * }) - * frappe.chat.room.get().then(function (rooms) { - * // do stuff - * }) - * - * frappe.chat.room.get(null, ["room_name", "avatar"], function (rooms) { - * // do stuff - * }) - * - * frappe.chat.room.get("CR00001", "room_name", function (room) { - * // do stuff - * }) - * - * frappe.chat.room.get(["CR00001", "CR00002"], ["room_name", "last_message"], function (rooms) { - * - * }) - */ -frappe.chat.room.get = function (names, fields, fn) { - if ( typeof names === "function" ) { - fn = names - names = null - fields = null - } - else - if ( typeof names === "string" ) { - names = frappe._.as_array(names) - - if ( typeof fields === "function" ) { - fn = fields - fields = null - } - else - if ( typeof fields === "string" ) - fields = frappe._.as_array(fields) - } - - return new Promise(resolve => { - frappe.call("frappe.chat.doctype.chat_room.chat_room.get", - { user: frappe.session.user, rooms: names, fields: fields }, - response => { - let rooms = response.message - if ( rooms ) { // frappe.api BOGZ! (emtpy arrays are falsified, not good design). - rooms = frappe._.as_array(rooms) - rooms = rooms.map(room => { - return { ...room, creation: new frappe.datetime.datetime(room.creation), - last_message: room.last_message ? { - ...room.last_message, - creation: new frappe.datetime.datetime(room.last_message.creation) - } : null - } - }) - rooms = frappe._.squash(rooms) - } - else - rooms = [ ] - - if ( fn ) - fn(rooms) - - resolve(rooms) - }) - }) -} - -/** - * @description Subscribe current user to said Chat Room(s). - * - * @param {string|array} rooms - Chat Room(s). - * - * @example - * frappe.chat.room.subscribe("CR00001") - */ -frappe.chat.room.subscribe = function (rooms) { - frappe.realtime.publish("frappe.chat.room:subscribe", rooms) -} - -/** - * @description Get Chat Room history. - * - * @param {string} name - Chat Room name - * - * @returns {Promise} - Chat Message(s) - * - * @example - * frappe.chat.room.history(function (messages) - * { - * // do stuff. - * }) - */ -frappe.chat.room.history = function (name, fn) { - return new Promise(resolve => { - frappe.call("frappe.chat.doctype.chat_room.chat_room.history", - { room: name, user: frappe.session.user }, - r => { - let messages = r.message ? frappe._.as_array(r.message) : [ ] // frappe.api BOGZ! (emtpy arrays are falsified, not good design). - messages = messages.map(m => { - return { ...m, - creation: new frappe.datetime.datetime(m.creation) - } - }) - - if ( fn ) - fn(messages) - - resolve(messages) - }) - }) -} - -/** - * @description Searches Rooms based on a query. - * - * @param {string} query - The query string. - * @param {array} rooms - A list of Chat Rooms. - * - * @returns {array} - A fuzzy searched list of rooms. - */ -frappe.chat.room.search = function (query, rooms) { - const dataset = rooms.map(r => { - if ( r.room_name ) - return r.room_name - else - if ( r.owner === frappe.session.user ) - return frappe.user.full_name(frappe._.squash(r.users)) - else - return frappe.user.full_name(r.owner) - }) - const results = frappe._.fuzzy_search(query, dataset) - rooms = results.map(i => rooms[i]) - - return rooms -} - -/** - * @description Sort Chat Room(s) based on Last Message Timestamp or Creation Date. - * - * @param {array} - A list of Chat Room(s) - * @param {compare} - (Optional) a comparision function. - */ -frappe.chat.room.sort = function (rooms, compare = null) { - compare = compare || function (a, b) { - if ( a.last_message && b.last_message ) - return frappe.datetime.compare(a.last_message.creation, b.last_message.creation) - else - if ( a.last_message ) - return frappe.datetime.compare(a.last_message.creation, b.creation) - else - if ( b.last_message ) - return frappe.datetime.compare(a.creation, b.last_message.creation) - else - return frappe.datetime.compare(a.creation, b.creation) - } - rooms.sort(compare) - - return rooms -} - -// frappe.chat.room.on -frappe.provide('frappe.chat.room.on') - -/** - * @description Triggers on Chat Room updated. - * - * @param {function} fn - callback with the Chat Room and Update. - */ -frappe.chat.room.on.update = function (fn) { - frappe.realtime.on("frappe.chat.room:update", r => { - if ( r.data.last_message ) - // creation to frappe.datetime.datetime (easier to manipulate). - r.data = { ...r.data, last_message: { ...r.data.last_message, creation: new frappe.datetime.datetime(r.data.last_message.creation) } } - - fn(r.room, r.data) - }) -} - -/** - * @description Triggers on Chat Room created. - * - * @param {function} fn - callback with the created Chat Room. - */ -frappe.chat.room.on.create = function (fn) { - frappe.realtime.on("frappe.chat.room:create", r => - fn({ ...r, creation: new frappe.datetime.datetime(r.creation) }) - ) -} - -/** - * @description Triggers when a User is typing in a Chat Room. - * - * @param {function} fn - callback with the typing User within the Chat Room. - */ -frappe.chat.room.on.typing = function (fn) { - frappe.realtime.on("frappe.chat.room:typing", r => fn(r.room, r.user)) -} - -// frappe.chat.message -frappe.provide('frappe.chat.message') - -frappe.chat.message.typing = function (room, user) { - frappe.realtime.publish("frappe.chat.message:typing", { user: user || frappe.session.user, room: room }) -} - -frappe.chat.message.send = function (room, message, type = "Content") { - frappe.call("frappe.chat.doctype.chat_message.chat_message.send", - { user: frappe.session.user, room: room, content: message, type: type }) -} - -frappe.chat.message.update = function (message, update, fn) { - return new Promise(resolve => { - frappe.call('frappe.chat.doctype.chat_message.chat_message.update', - { user: frappe.session.user, message: message, update: update }, - r => { - if ( fn ) - fn(response.message) - - resolve(response.message) - }) - }) -} - -frappe.chat.message.sort = (messages) => { - if ( !frappe._.is_empty(messages) ) - messages.sort((a, b) => frappe.datetime.compare(b.creation, a.creation)) - - return messages -} - -/** - * @description Add user to seen (defaults to session.user) - */ -frappe.chat.message.seen = (mess, user) => { - frappe.call('frappe.chat.doctype.chat_message.chat_message.seen', - { message: mess, user: user || frappe.session.user }) -} - -frappe.provide('frappe.chat.message.on') -frappe.chat.message.on.create = function (fn) { - frappe.realtime.on("frappe.chat.message:create", r => - fn({ ...r, creation: new frappe.datetime.datetime(r.creation) }) - ) -} - -frappe.chat.message.on.update = function (fn) { - frappe.realtime.on("frappe.chat.message:update", r => fn(r.message, r.data)) -} - -frappe.chat.pretty_datetime = function (date) { - const today = moment() - const instance = date.moment - - if ( today.isSame(instance, "d") ) - return instance.format("hh:mm A") - else - if ( today.isSame(instance, "week") ) - return instance.format("dddd") - else - return instance.format("DD/MM/YYYY") -} - -// frappe.chat.sound -frappe.provide('frappe.chat.sound') - -/** - * @description Plays a given registered sound. - * - * @param {value} - The name of the registered sound. - * - * @example - * frappe.chat.sound.play("message") - */ -frappe.chat.sound.play = function (name, volume = 0.1) { - // frappe._.play_sound(`chat-${name}`) - const $audio = $(`