diff --git a/.pydocstyle b/.pydocstyle
new file mode 100755
index 0000000000000000000000000000000000000000..8839ea9e9b8c25d812682bfc9a581945a46c0a92
--- /dev/null
+++ b/.pydocstyle
@@ -0,0 +1,8 @@
+[pydocstyle]
+ignore = D104,D105,D107,D203,D204,D213
+# D104: Missing docstring in public package
+# D105 Missing docstring in magic method
+# D107 Missing docstring in __init__
+# D203 1 blank line required before class docstring
+# D204 1 blank line required after class docstring (found 0)
+# D213 Multi-line docstring summary should start at the second line
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000000000000000000000000000000000000..a71feb49d9695b224b1a9b153da4be2d3f7e85d0
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,4 @@
+[MESSAGES CONTROL]
+disable=no-self-use,too-many-instance-attributes
+
+good-names=id, i
diff --git a/papersurfer/papersurfer.py b/papersurfer/papersurfer.py
index 89cb9ee5b34c8a0e9fe655be84cb357c96216722..5165923dee5d98a09d83f6f6d2d9512ce66c7d0e 100644
--- a/papersurfer/papersurfer.py
+++ b/papersurfer/papersurfer.py
@@ -1,4 +1,4 @@
-""""Paper surfer - browse papers posted on the mattermost channel.
+"""Paper surfer - browse papers posted on the mattermost channel.
 
 UI:
 
@@ -16,6 +16,7 @@ from functools import partial
 import json
 import time
 import os
+import sys
 import requests
 import mattermostdriver
 import urwid
@@ -28,7 +29,7 @@ class ConfigError(Exception):
 
 @dataclass
 class PostDTO:
-    """"Encapsulate Mattermost Posts."""
+    """Encapsulate Mattermost Posts."""
     id: str
     message: str
     reporter: str
@@ -40,7 +41,7 @@ class PostDTO:
 
 @dataclass
 class PaperDTO:
-    """"Encapsulate Paper meta data."""
+    """Encapsulate Paper meta data."""
     author: str
     authors: str
     title: str
@@ -52,20 +53,24 @@ class PaperDTO:
 
 
 class Bibtex:
+    """Interface for bibtex string."""
     def entry_from_doi(self, doi):
+        """Get bibtex string for doi."""
         return Doi().get_bibtex(doi)
 
     def bib_from_dois(self, dois):
+        """Get bibtex string for mulitple dois."""
         return "\n".join([Doi().get_bibtex(doi) for doi in dois])
 
 
 class Doi:
-    """Interface w/ the doi.org api"""
+    """Interface w/ the doi.org api."""
     def get_doi_link(self, doi):
         """Assemble doi link."""
         return f"http://doi.org/{doi}"
 
     def load_doi_data(self, doi):
+        """Load data for doi."""
         headers = {
             'Accept': 'application/json',
         }
@@ -73,7 +78,7 @@ class Doi:
                             headers=headers).content
 
     def parse_doi_json(self, jsoncontent):
-        """Tranform doi json to PaperDTO"""
+        """Tranform doi json to PaperDTO."""
         info = json.loads(jsoncontent)
 
         with open("debug.json", "w") as file:
@@ -87,8 +92,7 @@ class Doi:
                    if "author" in info
                    else "Authors N/A")
         title = (info['title']
-                 if "title" in info
-                    and isinstance(info['title'], str)
+                 if "title" in info and isinstance(info['title'], str)
                  else "Title N/A")
         journal = (info['publisher']
                    if "publisher" in info
@@ -105,12 +109,14 @@ class Doi:
                         slug)
 
     def get_bibtex(self, doi):
+        """Get bibtex string for doi."""
         headers = {
             'Accept': 'text/bibliography; style=bibtex',
         }
         return requests.get(f'http://dx.doi.org/{doi}', headers=headers).text
 
     def get_info(self, doi):
+        """Get information for doi."""
         try:
             jsoncontent = self.load_doi_data(doi)
             data = self.parse_doi_json(jsoncontent)
@@ -132,6 +138,7 @@ class Doi:
 class Mattermost:
     """Provide a simplified interaction w/ mattermost api."""
     def __init__(self, url, channelname, username, password):
+        self.msgs = []
         self.mattermost = mattermostdriver.Driver({
             'url': url,
             'login_id': username,
@@ -155,13 +162,14 @@ class Mattermost:
         self.reporters = {}
 
     def get_channel(self, channelname):
-        """"Try to find the paper channel by display name."""
-        mm = self.mattermost
-        teams = [team["id"] for team in mm.teams.get_user_teams("me")]
+        """Try to find the paper channel by display name."""
+        teamapi = self.mattermost.teams
+        channelapi = self.mattermost.channels
+        teams = [team["id"] for team in teamapi.get_user_teams("me")]
         channels = []
         for team in teams:
             teamchannels = [channel for channel
-                            in mm.channels.get_channels_for_user("me", team)
+                            in channelapi.get_channels_for_user("me", team)
                             if channel["display_name"] == channelname]
             channels.extend(teamchannels)
 
@@ -171,22 +179,20 @@ class Mattermost:
             raise ConfigError
         return channels[0]["id"]
 
-    def get_reporter(self, id):
+    def get_reporter(self, userid):
         """Load user from mattermost api and cache."""
-        if id not in self.reporters:
-            self.reporters[id] = self.mattermost.users.get_user(id)["username"]
+        userapi = self.mattermost.users
+        if userid not in self.reporters:
+            self.reporters[userid] = userapi.get_user(userid)["username"]
 
-        return self.reporters[id]
+        return self.reporters[userid]
 
     def retrieve_all_messages(self):
         """Retrieve all messages from mattermost, unfiltered for papers."""
         posts = self.mattermost.posts.get_posts_for_channel(self.channel)
-        return [PostDTO(
-                    id=m['id'],
-                    message=m['message'],
-                    reporter=self.get_reporter(m['user_id']),
-                    doi=Doi().extract_doi(m['message']),
-                )
+        return [PostDTO(id=m['id'], message=m['message'],
+                        reporter=self.get_reporter(m['user_id']),
+                        doi=Doi().extract_doi(m['message']),)
                 for m in posts['posts'].values()]
 
     def filter_incoming(self, posts):
@@ -200,6 +206,7 @@ class Mattermost:
         return self.msgs
 
     def check_doi_exits(self, doi):
+        """Check for doi in current paper list."""
         doi_needle = Doi().extract_doi(doi)
         msg_found = [msg for msg in self.msgs
                      if Doi().extract_doi(msg.doi) == doi_needle]
@@ -212,12 +219,15 @@ class Mattermost:
                 or needle.lower() in m.reporter.lower()]
 
     def post(self, message):
+        """Post message to thread."""
         self.mattermost.posts.create_post({"channel_id": self.channel,
                                            "message": message})
 
 
 class PrettyButton(urwid.WidgetWrap):
+    """Prettified urwid Button."""
     def __init__(self, label, on_press=None, user_data=None):
+        self.label = ""
         self.text = urwid.Text("")
         self.set_label(label)
         self.widget = urwid.AttrMap(self.text, '', 'highlight')
@@ -226,21 +236,26 @@ class PrettyButton(urwid.WidgetWrap):
         self._hidden_btn = urwid.Button(f"hidden {self.label}",
                                         on_press, user_data)
 
-        super(self.__class__, self).__init__(self.widget)
+        super().__init__(self.widget)
 
     def selectable(self):
+        """Make button selectable."""
         return True
 
     def keypress(self, *args, **kw):
+        """Handle keypresses."""
         return self._hidden_btn.keypress(*args, **kw)
 
     def mouse_event(self, *args, **kw):
+        """Handle mouse events."""
         return self._hidden_btn.mouse_event(*args, **kw)
 
     def get_label(self):
+        """Return current input label."""
         return self.label
 
     def set_label(self, label):
+        """Return current input label."""
         self.label = label
         self.text.set_text(f"[ {label} ]")
 
@@ -308,16 +323,19 @@ class Papersurfer:
         self.mainloop.run()
 
     def h_unhandled_input(self, key):
+        """Handle keyboard input not otherwise handled."""
         if key == "esc":
             raise urwid.ExitMainLoop()
 
     def load_list(self, _loop, _data):
+        """Load and display paper list."""
         body = [self.list_item(paper) for paper in self.mtm.retrieve()]
         self.listcontent.clear()
         self.listcontent.extend(body)
         self.mainloop.widget = self.top
 
     def loading_indicator(self):
+        """Create loading indicator dialog."""
         body_text = urwid.Text("Loading...", align='center')
         body_filler = urwid.Filler(body_text, valign='middle')
         body_padding = urwid.Padding(
@@ -329,6 +347,7 @@ class Papersurfer:
         return urwid.Frame(body_padding)
 
     def details_popup(self, paper):
+        """Create Dialog with paper details."""
         header_text = urwid.Text(('banner', 'Paper details'), align='center')
         header = urwid.AttrMap(header_text, 'banner')
 
@@ -391,7 +410,7 @@ class Papersurfer:
         return pile
 
     def updscrn(self):
-        """"Update (redraw) screen."""
+        """Update (redraw) screen."""
         self.mainloop.draw_screen()
 
     def onchange(self, _, needle):
@@ -402,6 +421,7 @@ class Papersurfer:
                                  for paper in self.mtm.get_filtered(needle)])
 
     def running_export(self, state):
+        """Set exporting state."""
         btn = self.exportbutton
         label = btn.get_label()
         running_indicator = " (running...)"
@@ -422,6 +442,7 @@ class Papersurfer:
         self.running_export(False)
 
     def export_to_bibtex(self):
+        """Export current filtered list to bibtex file."""
         papers = self.mtm.get_filtered(self.filter)
         dois = [paper.doi for paper in papers]
         string = Bibtex().bib_from_dois(dois)
@@ -455,13 +476,15 @@ class Papersurfer:
         self.mainloop.widget = self.details_popup(paper)
 
     def h_close_dialog(self, _):
+        """Handle close dialog button."""
         self.close_dialog()
 
     def close_dialog(self):
+        """Close currently open dialog."""
         self.mainloop.widget = self.top
 
     def open_submit_paper(self, _):
-
+        """Open submit paper dialog."""
         self._pile = urwid.Pile(
             [
                 PostDialog(self.mtm, close=self.h_close_dialog)
@@ -494,7 +517,6 @@ def get_config_file_paths():
     >>> type(get_config_file_paths())
     <class 'list'>
     """
-
     env = os.environ
     xdg_home = None
     if 'XDG_CONFIG_HOME' in env:
@@ -517,6 +539,7 @@ def get_config_file_paths():
 
 
 def interactive_configuration():
+    """Query user for credentials."""
     url = input("Mattermost URL (eg. mattermost.example.net): ")
     channel = input("Channel (eg. Paper Club): ")
     username = input("Username (same as mattermost login, "
@@ -526,7 +549,8 @@ def interactive_configuration():
 
 
 class PostDialog(urwid.WidgetWrap):
-    """
+    """Dialog to submit a new paper to mattermost thread.
+
     UI:
         DOI: [ _________________]
         Generated Message:
@@ -535,6 +559,8 @@ class PostDialog(urwid.WidgetWrap):
         [Submit]       [Close]
     """
     def __init__(self, mattermost, close):
+        self.doi = None
+        self.msg = None
         self.mattermost = mattermost
         self.close = close
         self.doi_input = urwid.Edit("Doi: ")
@@ -558,21 +584,21 @@ class PostDialog(urwid.WidgetWrap):
             right=1
         )
         body = urwid.LineBox(body_padding)
-        frame = urwid.Frame(
-                body,
-                header=urwid.Text("Submit new paper to list"),
-            )
+        frame = urwid.Frame(body,
+                            header=urwid.Text("Submit new paper to list"))
 
         self.widget = frame
 
-        super(self.__class__, self).__init__(self.widget)
+        super().__init__(self.widget)
 
     def submit(self, _):
+        """Submit post to thread."""
         if not self.mattermost.check_doi_exits(self.doi):
             self.mattermost.post(self.msg)
         self.close(_)
 
     def create_mgs(self, paper):
+        """Format post message."""
         msg = f"""\
 {paper.title}
 {paper.authors}
@@ -581,6 +607,7 @@ class PostDialog(urwid.WidgetWrap):
         return msg
 
     def h_input(self, _, doi):
+        """Handle doi input field."""
         self.doi_result.set_text("... loading ...")
         self.doi = None
         self.msg = None
@@ -602,8 +629,8 @@ class PostDialog(urwid.WidgetWrap):
 
 def parse_args():
     """Parse command line arguments and config file."""
-    parser = configargparse.ArgParser()
-    parser._default_config_files = get_config_file_paths()
+    parser = configargparse.ArgParser(
+        default_config_files=get_config_file_paths())
     parser.add("-w", "--write-out-config-file",
                help="takes the current command line args and writes them out "
                     "to a config file at the given path",
@@ -630,7 +657,7 @@ def parse_args():
                 Mattermost(url, channel, username, password)
             except ConfigError:
                 print("Failed to validate configuration, exiting.")
-                exit(1)
+                sys.exit(1)
 
             options.url = url
             options.channel = channel
@@ -648,7 +675,7 @@ def parse_args():
             time.sleep(2)
         else:
             parser.print_help()
-            exit(0)
+            sys.exit(0)
 
     return options
 
@@ -661,12 +688,14 @@ def just_papers(url, channel, username, password):
 
 
 def just_bibtex(url, channel, username, password):
+    """Retrieve and dump bibtext formated data, unfiltered."""
     posts = Mattermost(url, channel, username, password).retrieve()
     dois = [post.doi for post in posts]
     print(Bibtex().bib_from_dois(dois))
 
 
 def main():
+    """Run main program."""
     opt = parse_args()
     if opt.dump_posts:
         just_papers(opt.url, opt.channel, opt.username, opt.password)