mirror of https://github.com/CIRCL/AIL-framework
				
				
				
			chg: [chats] add messages threads
							parent
							
								
									f766cbebda
								
							
						
					
					
						commit
						93ef541862
					
				|  | @ -20,6 +20,7 @@ sys.path.append(os.environ['AIL_BIN']) | |||
| from importer.feeders.Default import DefaultFeeder | ||||
| from lib.objects.Chats import Chat | ||||
| from lib.objects import ChatSubChannels | ||||
| from lib.objects import ChatThreads | ||||
| from lib.objects import Images | ||||
| from lib.objects import Messages | ||||
| from lib.objects import FilesNames | ||||
|  | @ -74,13 +75,13 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|         return self.json_data['meta']['chat']['id'] | ||||
| 
 | ||||
|     def get_subchannel_id(self): | ||||
|         pass | ||||
|         return self.json_data['meta']['chat'].get('subchannel', {}).get('id') | ||||
| 
 | ||||
|     def get_subchannels(self): | ||||
|         pass | ||||
| 
 | ||||
|     def get_thread_id(self): | ||||
|         pass | ||||
|         return self.json_data['meta'].get('thread', {}).get('id') | ||||
| 
 | ||||
|     def get_message_id(self): | ||||
|         return self.json_data['meta']['id'] | ||||
|  | @ -112,7 +113,7 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|         return self.json_data['meta'].get('reply_to')  # TODO change to reply ??? | ||||
| 
 | ||||
|     def get_message_reply_id(self): | ||||
|         return self.json_data['meta'].get('reply_to', None) | ||||
|         return self.json_data['meta'].get('reply_to', {}).get('message_id') | ||||
| 
 | ||||
|     def get_message_content(self): | ||||
|         decoded = base64.standard_b64decode(self.json_data['data']) | ||||
|  | @ -125,6 +126,7 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|         #### Create Object ID #### | ||||
|         chat_id = self.get_chat_id() | ||||
|         message_id = self.get_message_id() | ||||
|         thread_id = self.get_thread_id() | ||||
|         # channel id | ||||
|         # thread id | ||||
| 
 | ||||
|  | @ -135,11 +137,11 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|             self.obj = Images.Image(self.json_data['data-sha256']) | ||||
| 
 | ||||
|         else: | ||||
|             obj_id = Messages.create_obj_id(self.get_chat_instance_uuid(), chat_id, message_id, timestamp) | ||||
|             obj_id = Messages.create_obj_id(self.get_chat_instance_uuid(), chat_id, message_id, timestamp, thread_id=thread_id) | ||||
|             self.obj = Messages.Message(obj_id) | ||||
|         return self.obj | ||||
| 
 | ||||
|     def process_chat(self, new_objs, obj, date, timestamp, reply_id=None):  # TODO threads | ||||
|     def process_chat(self, new_objs, obj, date, timestamp, reply_id=None): | ||||
|         meta = self.json_data['meta']['chat'] # todo replace me by function | ||||
|         chat = Chat(self.get_chat_id(), self.get_chat_instance_uuid()) | ||||
| 
 | ||||
|  | @ -170,7 +172,10 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|             chat.add_children(obj_global_id=subchannel.get_global_id()) | ||||
|         else: | ||||
|             if obj.type == 'message': | ||||
|                 chat.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id) | ||||
|                 if self.get_thread_id(): | ||||
|                     self.process_thread(obj, chat, date, timestamp, reply_id=reply_id) | ||||
|                 else: | ||||
|                     chat.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id) | ||||
| 
 | ||||
| 
 | ||||
|         # if meta.get('subchannels'): # TODO Update icon + names | ||||
|  | @ -198,9 +203,32 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|             subchannel.set_info(meta['info']) | ||||
| 
 | ||||
|         if obj.type == 'message': | ||||
|             subchannel.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id) | ||||
|             if self.get_thread_id(): | ||||
|                 self.process_thread(obj, subchannel, date, timestamp, reply_id=reply_id) | ||||
|             else: | ||||
|                 subchannel.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id) | ||||
|         return subchannel | ||||
| 
 | ||||
|     def process_thread(self, obj, obj_chat, date, timestamp, reply_id=None): | ||||
|         meta = self.json_data['meta']['thread'] | ||||
|         thread_id = self.get_thread_id() | ||||
|         p_chat_id = meta['parent'].get('chat') | ||||
|         p_subchannel_id = meta['parent'].get('subchannel') | ||||
|         p_message_id = meta['parent'].get('message') | ||||
| 
 | ||||
|         if p_chat_id == self.get_chat_id() and p_subchannel_id == self.get_subchannel_id(): | ||||
|             thread = ChatThreads.create(thread_id, self.get_chat_instance_uuid(), p_chat_id, p_subchannel_id, p_message_id, obj_chat) | ||||
|             thread.add(date, obj) | ||||
|             thread.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id) | ||||
|             # TODO OTHERS CORRELATIONS TO ADD | ||||
| 
 | ||||
|             return thread | ||||
| 
 | ||||
|         # TODO | ||||
|         # else: | ||||
|         #   # ADD NEW MESSAGE REF (used by discord) | ||||
| 
 | ||||
| 
 | ||||
|     def process_sender(self, new_objs, obj, date, timestamp): | ||||
|         meta = self.json_data['meta']['sender'] | ||||
|         user_account = UsersAccount.UserAccount(meta['id'], self.get_chat_instance_uuid()) | ||||
|  | @ -298,6 +326,9 @@ class AbstractChatFeeder(DefaultFeeder, ABC): | |||
|                 if media_name: | ||||
|                     FilesNames.FilesNames().create(media_name, date, message, file_obj=obj) | ||||
| 
 | ||||
|                 for reaction in self.get_reactions(): | ||||
|                     message.add_reaction(reaction['reaction'], int(reaction['count'])) | ||||
| 
 | ||||
|         for obj in objs:  # TODO PERF avoid parsing metas multiple times | ||||
| 
 | ||||
|             # CHAT | ||||
|  |  | |||
|  | @ -16,8 +16,8 @@ r_serv_db = config_loader.get_db_conn("Kvrocks_DB") | |||
| r_object = config_loader.get_db_conn("Kvrocks_Objects") | ||||
| config_loader = None | ||||
| 
 | ||||
| AIL_OBJECTS = sorted({'chat', 'cookie-name', 'cve', 'cryptocurrency', 'decoded', 'domain', 'etag', 'favicon', | ||||
|                       'file-name', 'hhhash', | ||||
| AIL_OBJECTS = sorted({'chat', 'chat-subchannel', 'chat-thread', 'cookie-name', 'cve', 'cryptocurrency', 'decoded', | ||||
|                       'domain', 'etag', 'favicon', 'file-name', 'hhhash', | ||||
|                       'item', 'image', 'message', 'pgp', 'screenshot', 'title', 'user-account', 'username'}) | ||||
| 
 | ||||
| def get_ail_uuid(): | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ sys.path.append(os.environ['AIL_BIN']) | |||
| from lib.ConfigLoader import ConfigLoader | ||||
| from lib.objects import Chats | ||||
| from lib.objects import ChatSubChannels | ||||
| from lib.objects import ChatThreads | ||||
| from lib.objects import Messages | ||||
| from lib.objects import Usernames | ||||
| 
 | ||||
|  | @ -324,7 +325,7 @@ def api_get_nb_message_by_week(chat_id, chat_instance_uuid): | |||
| def api_get_subchannel(chat_id, chat_instance_uuid): | ||||
|     subchannel = ChatSubChannels.ChatSubChannel(chat_id, chat_instance_uuid) | ||||
|     if not subchannel.exists(): | ||||
|         return {"status": "error", "reason": "Unknown chat"}, 404 | ||||
|         return {"status": "error", "reason": "Unknown subchannel"}, 404 | ||||
|     meta = subchannel.get_meta({'chat', 'created_at', 'icon', 'nb_messages'}) | ||||
|     if meta['chat']: | ||||
|         meta['chat'] = get_chat_meta_from_global_id(meta['chat']) | ||||
|  | @ -333,6 +334,16 @@ def api_get_subchannel(chat_id, chat_instance_uuid): | |||
|     meta['messages'], meta['tags_messages'] = subchannel.get_messages() | ||||
|     return meta, 200 | ||||
| 
 | ||||
| def api_get_thread(thread_id, thread_instance_uuid): | ||||
|     thread = ChatThreads.ChatThread(thread_id, thread_instance_uuid) | ||||
|     if not thread.exists(): | ||||
|         return {"status": "error", "reason": "Unknown thread"}, 404 | ||||
|     meta = thread.get_meta({'chat', 'nb_messages'}) | ||||
|     # if meta['chat']: | ||||
|     #     meta['chat'] = get_chat_meta_from_global_id(meta['chat']) | ||||
|     meta['messages'], meta['tags_messages'] = thread.get_messages() | ||||
|     return meta, 200 | ||||
| 
 | ||||
| def api_get_message(message_id): | ||||
|     message = Messages.Message(message_id) | ||||
|     if not message.exists(): | ||||
|  |  | |||
|  | @ -41,7 +41,9 @@ config_loader = None | |||
| ################################## | ||||
| 
 | ||||
| CORRELATION_TYPES_BY_OBJ = { | ||||
|     "chat": ["image", "user-account"],  # message or direct correlation like cve, bitcoin, ... ??? | ||||
|     "chat": ["chat-subchannel", "chat-thread", "image", "user-account"],  # message or direct correlation like cve, bitcoin, ... ??? | ||||
|     "chat-subchannel": ["chat", "chat-thread", "image", "message", "user-account"], | ||||
|     "chat-thread": ["chat", "chat-subchannel", "image", "message", "user-account"], # TODO user account | ||||
|     "cookie-name": ["domain"], | ||||
|     "cryptocurrency": ["domain", "item", "message"], | ||||
|     "cve": ["domain", "item", "message"], | ||||
|  | @ -53,11 +55,11 @@ CORRELATION_TYPES_BY_OBJ = { | |||
|     "hhhash": ["domain"], | ||||
|     "image": ["chat", "message", "user-account"], | ||||
|     "item": ["cve", "cryptocurrency", "decoded", "domain", "favicon", "pgp", "screenshot", "title", "username"],  # chat ??? | ||||
|     "message": ["cve", "cryptocurrency", "decoded", "file-name", "image", "pgp", "user-account"],  # chat ?? | ||||
|     "message": ["chat", "chat-subchannel", "chat-thread", "cve", "cryptocurrency", "decoded", "file-name", "image", "pgp", "user-account"],  # chat ?? | ||||
|     "pgp": ["domain", "item", "message"], | ||||
|     "screenshot": ["domain", "item"], | ||||
|     "title": ["domain", "item"], | ||||
|     "user-account": ["chat", "message"], | ||||
|     "user-account": ["chat", "chat-subchannel", "chat-thread", "message"], | ||||
|     "username": ["domain", "item", "message"],  # TODO chat-user/account | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -149,7 +149,7 @@ class ChatSubChannel(AbstractChatObject): | |||
| 
 | ||||
| class ChatSubChannels(AbstractChatObjects): | ||||
|     def __init__(self): | ||||
|         super().__init__('chat-subchannels') | ||||
|         super().__init__('chat-subchannel') | ||||
| 
 | ||||
| # if __name__ == '__main__': | ||||
| #     chat = Chat('test', 'telegram') | ||||
|  |  | |||
|  | @ -15,12 +15,8 @@ sys.path.append(os.environ['AIL_BIN']) | |||
| ################################## | ||||
| from lib import ail_core | ||||
| from lib.ConfigLoader import ConfigLoader | ||||
| from lib.objects.abstract_subtype_object import AbstractSubtypeObject, get_all_id | ||||
| from lib.data_retention_engine import update_obj_date | ||||
| from lib.objects import ail_objects | ||||
| from lib.timeline_engine import Timeline | ||||
| from lib.objects.abstract_chat_object import AbstractChatObject, AbstractChatObjects | ||||
| 
 | ||||
| from lib.correlations_engine import get_correlation_by_correl_type | ||||
| 
 | ||||
| config_loader = ConfigLoader() | ||||
| baseurl = config_loader.get_config_str("Notifications", "ail_domain") | ||||
|  | @ -33,13 +29,13 @@ config_loader = None | |||
| ################################################################################ | ||||
| ################################################################################ | ||||
| 
 | ||||
| class Chat(AbstractSubtypeObject):  # TODO # ID == username ????? | ||||
| class ChatThread(AbstractChatObject): | ||||
|     """ | ||||
|     AIL Chat Object. (strings) | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, id, subtype): | ||||
|         super(Chat, self).__init__('chat-thread', id, subtype) | ||||
|         super().__init__('chat-thread', id, subtype) | ||||
| 
 | ||||
|     # def get_ail_2_ail_payload(self): | ||||
|     #     payload = {'raw': self.get_gzip_content(b64=True), | ||||
|  | @ -69,7 +65,7 @@ class Chat(AbstractSubtypeObject):  # TODO # ID == username ????? | |||
|         #     style = 'fas' | ||||
|         #     icon = '\uf007' | ||||
|         style = 'fas' | ||||
|         icon = '\uf086' | ||||
|         icon = '\uf7a4' | ||||
|         return {'style': style, 'icon': icon, 'color': '#4dffff', 'radius': 5} | ||||
| 
 | ||||
|     def get_meta(self, options=set()): | ||||
|  | @ -77,27 +73,42 @@ class Chat(AbstractSubtypeObject):  # TODO # ID == username ????? | |||
|         meta['id'] = self.id | ||||
|         meta['subtype'] = self.subtype | ||||
|         meta['tags'] = self.get_tags(r_list=True) | ||||
|         if 'username': | ||||
|             meta['username'] = self.get_username() | ||||
|         if 'nb_messages': | ||||
|             meta['nb_messages'] = self.get_nb_messages() | ||||
|         # created_at ??? | ||||
|         return meta | ||||
| 
 | ||||
|     def get_misp_object(self): | ||||
|         return | ||||
| 
 | ||||
|     ############################################################################ | ||||
|     ############################################################################ | ||||
|     def create(self, container_obj, message_id): | ||||
|         if message_id: | ||||
|             parent_message = container_obj.get_obj_by_message_id(message_id) | ||||
|             if parent_message:  # TODO EXCEPTION IF DON'T EXISTS | ||||
|                 self.set_parent(obj_global_id=parent_message) | ||||
|                 _, _, parent_id = parent_message.split(':', 2) | ||||
|                 self.add_correlation('message', '', parent_id) | ||||
|         else: | ||||
|             self.set_parent(obj_global_id=container_obj.get_global_id()) | ||||
|             self.add_correlation(container_obj.get_type(), container_obj.get_subtype(r_str=True), container_obj.get_id()) | ||||
| 
 | ||||
|     # others optional metas, ... -> # TODO ALL meta in hset | ||||
| def create(thread_id, chat_instance, chat_id, subchannel_id, message_id, container_obj): | ||||
|     if container_obj.get_type() == 'chat': | ||||
|         new_thread_id = f'{chat_id}/{thread_id}' | ||||
|     # sub-channel | ||||
|     else: | ||||
|         new_thread_id = f'{chat_id}/{subchannel_id}/{thread_id}' | ||||
| 
 | ||||
|     #### Messages #### TODO set parents | ||||
|     thread = ChatThread(new_thread_id, chat_instance) | ||||
|     if not thread.exists(): | ||||
|         thread.create(container_obj, message_id) | ||||
|     return thread | ||||
| 
 | ||||
|     # def get_last_message_id(self): | ||||
|     # | ||||
|     #     return r_object.hget(f'meta:{self.type}:{self.subtype}:{self.id}', 'last:message:id') | ||||
| class ChatThreads(AbstractChatObjects): | ||||
|     def __init__(self): | ||||
|         super().__init__('chat-thread') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     chat = Chat('test', 'telegram') | ||||
|     r = chat.get_messages() | ||||
|     print(r) | ||||
| # if __name__ == '__main__': | ||||
| #     chat = Chat('test', 'telegram') | ||||
| #     r = chat.get_messages() | ||||
| #     print(r) | ||||
|  |  | |||
|  | @ -100,6 +100,13 @@ class Message(AbstractObject): | |||
|         chat_id = self.get_basename().rsplit('_', 1)[0] | ||||
|         return chat_id | ||||
| 
 | ||||
|     def get_thread(self): | ||||
|         for child in self.get_childrens(): | ||||
|             obj_type, obj_subtype, obj_id = child.split(':', 2) | ||||
|             if obj_type == 'chat-thread': | ||||
|                 nb_messages = r_object.zcard(f'messages:{obj_type}:{obj_subtype}:{obj_id}') | ||||
|                 return {'type': obj_type, 'subtype': obj_subtype, 'id': obj_id, 'nb': nb_messages} | ||||
| 
 | ||||
|     # TODO get Instance ID | ||||
|     # TODO get channel ID | ||||
|     # TODO get thread  ID | ||||
|  | @ -245,6 +252,10 @@ class Message(AbstractObject): | |||
|                 meta['user-account'] = {'id': 'UNKNOWN'} | ||||
|         if 'chat' in options: | ||||
|             meta['chat'] = self.get_chat_id() | ||||
|         if 'thread' in options: | ||||
|             thread = self.get_thread() | ||||
|             if thread: | ||||
|                 meta['thread'] = thread | ||||
|         if 'images' in options: | ||||
|             meta['images'] = self.get_images() | ||||
|         if 'files-names' in options: | ||||
|  | @ -318,10 +329,10 @@ class Message(AbstractObject): | |||
|     def delete(self): | ||||
|         pass | ||||
| 
 | ||||
| def create_obj_id(chat_instance, chat_id, message_id, timestamp, channel_id=None, thread_id=None): | ||||
| def create_obj_id(chat_instance, chat_id, message_id, timestamp, channel_id=None, thread_id=None): # TODO CHECK COLLISIONS | ||||
|     timestamp = int(timestamp) | ||||
|     if channel_id and thread_id: | ||||
|         return f'{chat_instance}/{timestamp}/{chat_id}/{chat_id}/{message_id}'  # TODO add thread ID ????? | ||||
|         return f'{chat_instance}/{timestamp}/{chat_id}/{thread_id}/{message_id}' | ||||
|     elif channel_id: | ||||
|         return f'{chat_instance}/{timestamp}/{channel_id}/{chat_id}/{message_id}' | ||||
|     elif thread_id: | ||||
|  | @ -329,6 +340,10 @@ def create_obj_id(chat_instance, chat_id, message_id, timestamp, channel_id=None | |||
|     else: | ||||
|         return f'{chat_instance}/{timestamp}/{chat_id}/{message_id}' | ||||
| 
 | ||||
|     # thread id of message | ||||
|     # thread id of chat | ||||
|     # thread id of subchannel | ||||
| 
 | ||||
| # TODO Check if already exists | ||||
| # def create(source, chat_id, message_id, timestamp, content, tags=[]): | ||||
| def create(obj_id, content, translation=None, tags=[]): | ||||
|  |  | |||
|  | @ -181,7 +181,7 @@ class AbstractChatObject(AbstractSubtypeObject, ABC): | |||
| 
 | ||||
|     def get_message_meta(self, message, timestamp=None):  # TODO handle file message | ||||
|         message = Messages.Message(message[9:]) | ||||
|         meta = message.get_meta(options={'content', 'files-names', 'images', 'link', 'parent', 'parent_meta', 'reactions', 'user-account'}, timestamp=timestamp) | ||||
|         meta = message.get_meta(options={'content', 'files-names', 'images', 'link', 'parent', 'parent_meta', 'reactions', 'thread', 'user-account'}, timestamp=timestamp) | ||||
|         return meta | ||||
| 
 | ||||
|     def get_messages(self, start=0, page=1, nb=500, unread=False):  # threads ???? # TODO ADD last/first message timestamp + return page | ||||
|  | @ -189,7 +189,7 @@ class AbstractChatObject(AbstractSubtypeObject, ABC): | |||
|         tags = {} | ||||
|         messages = {} | ||||
|         curr_date = None | ||||
|         for message in self._get_messages(nb=50, page=1): | ||||
|         for message in self._get_messages(nb=2000, page=1): | ||||
|             timestamp = message[1] | ||||
|             date_day = datetime.fromtimestamp(timestamp).strftime('%Y/%m/%d') | ||||
|             if date_day != curr_date: | ||||
|  |  | |||
|  | @ -180,9 +180,9 @@ class AbstractSubtypeObject(AbstractObject, ABC): | |||
|                     self.add_correlation('domain', '', domain) | ||||
| 
 | ||||
|     # TODO:ADD objects + Stats | ||||
|     def create(self, first_seen, last_seen): | ||||
|         self.set_first_seen(first_seen) | ||||
|         self.set_last_seen(last_seen) | ||||
|     # def create(self, first_seen, last_seen): | ||||
|     #     self.set_first_seen(first_seen) | ||||
|     #     self.set_last_seen(last_seen) | ||||
| 
 | ||||
|     def _delete(self): | ||||
|         pass | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ from lib import btc_ail | |||
| from lib import Tag | ||||
| 
 | ||||
| from lib.objects import Chats | ||||
| from lib.objects import ChatSubChannels | ||||
| from lib.objects import ChatThreads | ||||
| from lib.objects import CryptoCurrencies | ||||
| from lib.objects import CookiesNames | ||||
| from lib.objects.Cves import Cve | ||||
|  | @ -62,6 +64,10 @@ def get_object(obj_type, subtype, obj_id): | |||
|         return Decoded(obj_id) | ||||
|     elif obj_type == 'chat': | ||||
|         return Chats.Chat(obj_id, subtype) | ||||
|     elif obj_type == 'chat-subchannel': | ||||
|         return ChatSubChannels.ChatSubChannel(obj_id, subtype) | ||||
|     elif obj_type == 'chat-thread': | ||||
|         return ChatThreads.ChatThread(obj_id, subtype) | ||||
|     elif obj_type == 'cookie-name': | ||||
|         return CookiesNames.CookieName(obj_id) | ||||
|     elif obj_type == 'cve': | ||||
|  |  | |||
|  | @ -112,6 +112,18 @@ def objects_subchannel_messages(): | |||
|         subchannel = subchannel[0] | ||||
|         return render_template('SubChannelMessages.html', subchannel=subchannel, bootstrap_label=bootstrap_label) | ||||
| 
 | ||||
| @chats_explorer.route("/chats/explorer/thread", methods=['GET']) | ||||
| @login_required | ||||
| @login_read_only | ||||
| def objects_thread_messages(): | ||||
|     thread_id = request.args.get('id') | ||||
|     instance_uuid = request.args.get('uuid') | ||||
|     thread = chats_viewer.api_get_thread(thread_id, instance_uuid) | ||||
|     if thread[1] != 200: | ||||
|         return create_json_response(thread[0], thread[1]) | ||||
|     else: | ||||
|         meta = thread[0] | ||||
|         return render_template('ThreadMessages.html', meta=meta, bootstrap_label=bootstrap_label) | ||||
| 
 | ||||
| @chats_explorer.route("/objects/message", methods=['GET']) | ||||
| @login_required | ||||
|  |  | |||
|  | @ -0,0 +1,191 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| 
 | ||||
| <head> | ||||
|   <title>Thread Messages - AIL</title> | ||||
| 	<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png') }}"> | ||||
| 
 | ||||
|   <!-- Core CSS --> | ||||
| 	<link href="{{ url_for('static', filename='css/bootstrap4.min.css') }}" rel="stylesheet"> | ||||
| 	<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet"> | ||||
| 	<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet"> | ||||
| {#	<link href="{{ url_for('static', filename='css/daterangepicker.min.css') }}" rel="stylesheet">#} | ||||
| 
 | ||||
|   <!-- JS --> | ||||
| 	<script src="{{ url_for('static', filename='js/jquery.js')}}"></script> | ||||
| 	<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script> | ||||
| 	<script src="{{ url_for('static', filename='js/bootstrap4.min.js')}}"></script> | ||||
| 	<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script> | ||||
| 	<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script> | ||||
| {#	<script src="{{ url_for('static', filename='js/moment.min.js') }}"></script> | ||||
| 	<script src="{{ url_for('static', filename='js/jquery.daterangepicker.min.js') }}"></script>#} | ||||
| 	<script src="{{ url_for('static', filename='js/d3.min.js') }}"></script> | ||||
|     <script src="{{ url_for('static', filename='js/d3/sparklines.js')}}"></script> | ||||
| 
 | ||||
| 	<style> | ||||
|         .chat-message-left, | ||||
|         .chat-message-right { | ||||
|             display: flex; | ||||
|             flex-shrink: 0; | ||||
|         } | ||||
|         .chat-message-right { | ||||
|             flex-direction: row-reverse; | ||||
|             margin-left: auto | ||||
|         } | ||||
|         .divider:after, | ||||
|         .divider:before { | ||||
|             content: ""; | ||||
|             flex: 1; | ||||
|             height: 2px; | ||||
|             background: #eee; | ||||
|         } | ||||
| 	</style> | ||||
| </head> | ||||
| <body> | ||||
| 
 | ||||
| 	{% include 'nav_bar.html' %} | ||||
| 
 | ||||
| 	<div class="container-fluid"> | ||||
| 		<div class="row"> | ||||
| 
 | ||||
| 			{% include 'sidebars/sidebar_objects.html' %} | ||||
| 
 | ||||
| 			<div class="col-12 col-lg-10" id="core_content"> | ||||
| 
 | ||||
|                 <div class="card my-3"> | ||||
|                     <div class="card-header" style="background-color:#d9edf7;font-size: 15px"> | ||||
|                         {{ meta["id"] }} | ||||
|                         <ul class="list-group mb-2"> | ||||
|                             <li class="list-group-item py-0"> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-md-10"> | ||||
|                                         <table class="table"> | ||||
|                                             <thead> | ||||
|                                                 <tr> | ||||
|                                                     <th>ID</th> | ||||
|                                                     <th>Parent</th> | ||||
|                                                     <th>First seen</th> | ||||
|                                                     <th>Last seen</th> | ||||
|                                                     <th>Nb Messages</th> | ||||
|                                                 </tr> | ||||
|                                             </thead> | ||||
|                                             <tbody> | ||||
|                                                 <tr> | ||||
|                                                     <td> | ||||
|                                                         {{ meta['id'] }} | ||||
|                                                     </td> | ||||
|                                                     <td> | ||||
|                                                         {% if meta['first_seen'] %} | ||||
|                                                             {{ meta['first_seen'][0:4] }}-{{ meta['first_seen'][4:6] }}-{{ meta['first_seen'][6:8] }} | ||||
|                                                         {% endif %} | ||||
|                                                     </td> | ||||
|                                                     <td> | ||||
|                                                         {% if meta['last_seen'] %} | ||||
|                                                             {{ meta['last_seen'][0:4] }}-{{ meta['last_seen'][4:6] }}-{{ meta['last_seen'][6:8] }} | ||||
|                                                         {% endif %} | ||||
|                                                     </td> | ||||
|                                                     <td>{{ meta['nb_messages'] }}</td> | ||||
|                                                 </tr> | ||||
|                                             </tbody> | ||||
|                                         </table> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </li> | ||||
| {#                            <li class="list-group-item py-0">#} | ||||
| {#                                <br>#} | ||||
| {#                                <div class="mb-3">#} | ||||
| {#                                    Tags:#} | ||||
| {#                                    {% for tag in meta['tags'] %}#} | ||||
| {#                                        <button class="btn btn-{{ bootstrap_label[loop.index0 % 5] }}" data-toggle="modal" data-target="#edit_tags_modal"#} | ||||
| {#                                        data-tagid="{{ tag }}" data-objtype="chat" data-objsubtype="{{ meta["subtype"] }}" data-objid="{{ meta["id"] }}">#} | ||||
| {#                                            {{ tag }}#} | ||||
| {#                                        </button>#} | ||||
| {#                                    {%  endfor %}#} | ||||
| {#                                    <button type="button" class="btn btn-light" data-toggle="modal" data-target="#add_tags_modal">#} | ||||
| {#                                        <i class="far fa-plus-square"></i>#} | ||||
| {#                                    </button>#} | ||||
| {#                                </div>#} | ||||
| {#                            </li>#} | ||||
|                         </ul> | ||||
| 
 | ||||
| {#                        {% with obj_type='chat', obj_id=meta['id'], obj_subtype=meta['subtype'] %}#} | ||||
| {#                            {% include 'modals/investigations_register_obj.html' %}#} | ||||
| {#                        {% endwith %}#} | ||||
| {#                        <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#investigations_register_obj_modal">#} | ||||
| {#                            <i class="fas fa-microscope"></i> Investigations#} | ||||
| {#                        </button>#} | ||||
| 
 | ||||
|                     </div> | ||||
|                 </div> | ||||
| 
 | ||||
|                 {% for tag in meta['tags_messages'] %} | ||||
|                     <span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }} <span class="badge badge-light">{{ meta['tags_messages'][tag] }}</span></span> | ||||
|                 {%  endfor %} | ||||
| 
 | ||||
|                 <div> | ||||
|                     <div class="list-group d-inline-block"> | ||||
|                         {% for date in messages %} | ||||
|                             <a class="list-group-item list-group-item-action" href="#date_section_{{ date }}">{{ date }}</a> | ||||
|                         {% endfor %} | ||||
|                     </div> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <span class="mt-3"> | ||||
|                     {% include 'objects/image/block_blur_img_slider.html' %} | ||||
|                 </span> | ||||
| 
 | ||||
|                 <div class="position-relative"> | ||||
|                     <div class="chat-messages p-2"> | ||||
| 
 | ||||
|                         {% for date in meta['messages'] %} | ||||
| 
 | ||||
|                             <div class="divider d-flex align-items-center mb-4"> | ||||
|                                 <p class="text-center h2 mx-3 mb-0" style="color: #a2aab7;"> | ||||
|                                     <span class="badge badge-secondary mb-2" id="date_section_{{ date }}">{{ date }}</span> | ||||
|                                 </p> | ||||
|                             </div> | ||||
| 
 | ||||
|                             {% for mess in meta['messages'][date] %} | ||||
| 
 | ||||
|                                 {% with message=mess %} | ||||
|                                     {% include 'chats_explorer/block_message.html' %} | ||||
|                                 {% endwith %} | ||||
| 
 | ||||
|                             {% endfor %} | ||||
|                             <br> | ||||
|                         {% endfor %} | ||||
| 
 | ||||
|                     </div> | ||||
|                 </div> | ||||
| 
 | ||||
|             </div> | ||||
| 
 | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| <script> | ||||
|     var chart = {}; | ||||
|     $(document).ready(function(){ | ||||
|         $("#page-Decoded").addClass("active"); | ||||
|         $("#nav_chat").addClass("active"); | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| function toggle_sidebar(){ | ||||
| 	if($('#nav_menu').is(':visible')){ | ||||
| 		$('#nav_menu').hide(); | ||||
| 		$('#side_menu').removeClass('border-right') | ||||
| 		$('#side_menu').removeClass('col-lg-2') | ||||
| 		$('#core_content').removeClass('col-lg-10') | ||||
| 	}else{ | ||||
| 		$('#nav_menu').show(); | ||||
| 		$('#side_menu').addClass('border-right') | ||||
| 		$('#side_menu').addClass('col-lg-2') | ||||
| 		$('#core_content').addClass('col-lg-10') | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -74,6 +74,12 @@ | |||
|         {% for reaction in message['reactions'] %} | ||||
|             <span class="border rounded px-1">{{ reaction }} {{ message['reactions'][reaction] }}</span> | ||||
|         {%  endfor %} | ||||
|         {% if message['thread'] %} | ||||
|             <hr class="mb-1"> | ||||
|             <div class="my-2 text-center"> | ||||
|                 <a href="{{ url_for('chats_explorer.objects_thread_messages')}}?uuid={{ message['thread']['subtype'] }}&id={{ message['thread']['id'] }}"><i class="far fa-comment"></i> {{ message['thread']['nb'] }} Messages</a> | ||||
|             </div> | ||||
|         {% endif %} | ||||
|         {% for tag in message['tags'] %} | ||||
|             <span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }}</span> | ||||
|         {%  endfor %} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 terrtia
						terrtia