Community
    • Login

    Documentation History

    Scheduled Pinned Locked Moved Notepad++ & Plugin Development
    feature request
    4 Posts 3 Posters 441 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Brian HarrisB
      Brian Harris
      last edited by

      I think a feature that kept a total history of document creation and editing, storing all keypresses. Effectively this is a key logger but would be limited to the creation and editing of a document. I apologise if this is already available and if it isn’t would it be possible for me to create an addon to do this?

      PeterJonesP 1 Reply Last reply Reply Quote 0
      • PeterJonesP
        PeterJones @Brian Harris
        last edited by

        @Brian-Harris ,

        I don’t know of any plugins that actually log that or give the user access (though that doesn’t mean it doesn’t exist). I know that Scintilla (the underlying editor I.P.) does have some change-history tracking (undo/redo is one piece of evidence; the new change-history margins are another), but I don’t believe either Scintilla or Notepad++ gives direct access to those details of what all the changes were.

        “Would it be possible for” you could be interpreted two different ways: 1) Do you have permission? Yes; the Plugin Communication is published at https://npp-user-manual.org/docs/plugin-communication/ and anyone is allowed to make a plugin for Notepad++ using that Communication; after making it, you can either keep it to yourself, do your own distribution, or submit it to the nppPluginList project to request that it be added to the Plugins Admin inside Notepad++ for easy installation. 2) Is it technically possible and do you have the skills? The first is uncertain to me (it depends on how one might hook into the process, but it wouldn’t surprise me if given enough effort it were feasible); the second is unknowable to me, because I don’t know your skills.

        1 Reply Last reply Reply Quote 1
        • Mark OlsonM
          Mark Olson
          last edited by Mark Olson

          Here’s a simple PythonScript script that does something reasonably similar to what you’re looking for.

          from Npp import *
          
          import datetime
          import json
          from pathlib import Path
          
          # create a subdirectory of the PythonScript scripts directory called mod_logger
          LOGDIR = Path(__file__).parent / 'mod_logger'
          LOGFILE = LOGDIR / 'mod.log'
          
          MSG = '{time}@{fname}: {modlog}\n'
          FNAME = None
          
          def on_bufferactivate(notif):
              global FNAME
              FNAME = notepad.getCurrentFilename()
              
          def on_filesaved(notif):
              global FNAME
              formatted_msg = MSG.format(
                  time=datetime.datetime.now().ctime(),
                  fname=FNAME,
                  modlog = '"fileSaved"'
              )
              with LOGFILE.open('a') as f:
                  f.write(formatted_msg)
          
          def on_keypress(notif):
              global FNAME
              # see https://www.scintilla.org/ScintillaDoc.html#SCN_MODIFIED
              mod_type = notif['modificationType']
              keylog = {'position': notif['position']}
              if mod_type & 0x01 != 0:
                  keylog['textAdded'] = notif['text']
              elif mod_type & 0x02 != 0:
                  keylog['textRemoved'] = notif['text']
              else:
                  return
              formatted_msg = MSG.format(
                  time=datetime.datetime.now().ctime(),
                  fname=FNAME,
                  modlog=json.dumps(keylog)
              )
              with LOGFILE.open('a') as f:
                  f.write(formatted_msg)
          
          if __name__ == '__main__':
              try:
                  INITIALIZED
              except NameError:
                  INITIALIZED = True
                  if not LOGDIR.exists():
                      LOGDIR.mkdir()
                  if not LOGFILE.exists():
                      with LOGFILE.open('w') as f:
                          pass
                  # I *WANT* to use SCINTILLANOTIFICATION.KEY, which would *actually*
                  # log keypresses,
                  # but for some reason that notification never fires,
                  # and SCINTILLANOTIFICATION.MODIFIED is the closest we've got
                  FNAME = notepad.getCurrentFilename()
                  editor.callback(on_keypress, [SCINTILLANOTIFICATION.MODIFIED])
                  notepad.callback(on_bufferactivate, [NOTIFICATION.BUFFERACTIVATED])
                  notepad.callback(on_filesaved, [NOTIFICATION.FILEBEFORESAVE])
          

          And here’s the log it generated from inserting a newline and then the text asdf, and then selecting what I just added and also an f on the previous line and deleting it, then saving the file.

          Note that all the messages have the same format: the date, the character @, the filename, and then some description of the event. The description is always syntactically valid JSON, which is a useful property.

          Tue Jun  6 15:23:36 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 4, "textAdded": "\r\n"}
          Tue Jun  6 15:23:36 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 6, "textAdded": "a"}
          Tue Jun  6 15:23:37 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 7, "textAdded": "s"}
          Tue Jun  6 15:23:37 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 8, "textAdded": "d"}
          Tue Jun  6 15:23:37 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 9, "textAdded": "f"}
          Tue Jun  6 15:23:40 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 4, "textRemoved": "\r\nasdf"}
          Tue Jun  6 15:23:40 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: {"position": 3, "textRemoved": "f"}
          Tue Jun  6 15:23:41 2023@Path\To\AppData\Roaming\Notepad++\plugins\config\PythonScript\scripts\asdf.nroern: "fileSaved"
          
          1 Reply Last reply Reply Quote 5
          • Mark OlsonM
            Mark Olson
            last edited by Mark Olson

            I felt like my previous script was suboptimal as a keylogger because:

            1. it could only be parsed by regular expressions
            2. it generated a pretty verbose log with every keystroke, which seemed like an easy way to rapidly create a multi-megabyte log file.

            I’ve updated the keylogger so that:

            1. the log file is a JSON Lines document, which is very easy to parse (just split it into lines and use your favorite JSON parser to parse each line)
            2. the date is only logged when the date changes or when the logger first starts up
            3. The current filename is only logged when the file is saved or opened.
            4. key logs have the format {"+": <text added>, "t": hours:minutes:seconds, "pos": position}, with "-": <text removed> for text removal operations.

            While the user must later re-associate filenames and dates with each modification log, this is a very simple task.

            Below is an example log:

            {"date":"2023-06-07"}
            {"fileOpened":"Path\\To\\AppData\\Roaming\\Notepad++\\plugins\\config\\PythonScript\\scripts\\keylogger.py","t":"14:38:16"}
            {"fileOpened":"Path\\To\\Python311\\example_silly.txt","t":"14:38:16"}
            {"+":"a","pos":0,"t":"14:38:18"}
            {"+":"s","pos":1,"t":"14:38:18"}
            {"+":"d","pos":2,"t":"14:38:18"}
            {"+":"f","pos":3,"t":"14:38:18"}
            {"+":"\r\n","pos":4,"t":"14:38:19"}
            {"-":"asdf\r\n","pos":0,"t":"14:38:20"}
            {"fileSaved":"Path\\To\\Python311\\example_silly.txt","t":"14:38:21"}
            

            And here’s the new script:

            from Npp import *
            
            from datetime import datetime
            import json
            from pathlib import Path
            
            # create a subdirectory of the PythonScript scripts directory called mod_logger
            LOGDIR = Path(__file__).parent / 'mod_logger'
            LOGFILE = LOGDIR / 'mod.jsonl' # create a JSON Lines document in that directory
            
            def compact_json_dump(obj):
                return json.dumps(obj, separators=(',', ':'))
               
            def date_to_int(date):
                return (date.year << 16) | (date.month << 8) | date.day
            
            class ModLogger:
                def __init__(self):
                    self.FNAME = notepad.getCurrentFilename()
                    self.date_int = 0
                    self.log_date_change()
                
                def log(self, obj):
                    with LOGFILE.open('a') as f:
                        f.write(compact_json_dump(obj))
                        f.write('\n')
            
                def log_date_change(self):
                    now = datetime.now()
                    dint = date_to_int(now)
                    if dint > self.date_int:
                        self.date_int = dint
                        self.log({'date': '%d-%02d-%02d' % (now.year, now.month, now.day)})
                    return now
                
                def on_bufferactivate(self, notif):
                    self.FNAME = notepad.getCurrentFilename()
                    now = self.log_date_change()
                    self.log({'fileOpened': self.FNAME, 't': '%02d:%02d:%02d' % (now.hour, now.minute, now.second)})
                
                def on_filesaved(self, notif):
                    now = self.log_date_change()
                    self.log({'fileSaved': self.FNAME, 't': '%02d:%02d:%02d' % (now.hour, now.minute, now.second)})
            
                def on_modification(self, notif):
                    # see https://www.scintilla.org/ScintillaDoc.html#SCN_MODIFIED
                    mod_type = notif['modificationType']
                    keylog = {}
                    if mod_type & 0x01 != 0:
                        keylog['+'] = notif['text']
                    elif mod_type & 0x02 != 0:
                        keylog['-'] = notif['text']
                    else:
                        return
                    keylog['pos'] = notif['position']
                    now = self.log_date_change()
                    keylog['t'] = '%02d:%02d:%02d' % (now.hour, now.minute, now.second)
                    self.log(keylog)
            
            
            if __name__ == '__main__':
                try:
                    MOLO
                except NameError:
                    MOLO = ModLogger()
                    if not LOGDIR.exists():
                        LOGDIR.mkdir()
                    if not LOGFILE.exists():
                        with LOGFILE.open('w') as f:
                            pass
                    # I *WANT* to use SCINTILLANOTIFICATION.KEY, which would *actually*
                    # log keypresses,
                    # but for some reason that notification never fires,
                    # and SCINTILLANOTIFICATION.MODIFIED is the closest we've got
                    editor.callback(MOLO.on_modification, [SCINTILLANOTIFICATION.MODIFIED])
                    notepad.callback(MOLO.on_bufferactivate, [NOTIFICATION.BUFFERACTIVATED])
                    notepad.callback(MOLO.on_filesaved, [NOTIFICATION.FILEBEFORESAVE])
            
            1 Reply Last reply Reply Quote 4
            • PeterJonesP PeterJones referenced this topic on
            • First post
              Last post
            The Community of users of the Notepad++ text editor.
            Powered by NodeBB | Contributors