├── .github └── workflows │ └── test.yml ├── .gitignore ├── README.md ├── conf ├── accounts.json.dist ├── spamcan.json.dist └── thug.json.dist ├── database.py ├── modules ├── __init__.py ├── mail_parser.py ├── mail_utils.py ├── protocols │ ├── __init__.py │ ├── imap_util.py │ └── pop_util.py └── send_util.py ├── requirements.txt ├── spamcan.py ├── static ├── css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── font-awesome.css │ └── font-awesome.min.css ├── favicon.ico ├── font │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff └── js │ ├── bootstrap.min.js │ ├── jquery-3.7.0.min.js │ └── utils.js ├── templates ├── base.html ├── files.html ├── index.html ├── mail.html ├── mails.html └── urls.html ├── test.py └── testing ├── __init__.py └── pop_server.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8"] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | cache: 'pip' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install -r requirements.txt 22 | - name: Run the tests 23 | run: | 24 | cp conf/accounts.json.dist conf/accounts.json 25 | cp conf/spamcan.json.dist conf/spamcan.json 26 | python test.py 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | /data 37 | /data/spamcan.db 38 | /data/spamcan.db-journal 39 | /conf/accounts.json 40 | /conf/spamcan.json 41 | env_spamcan 42 | maildir 43 | .idea 44 | .vscode 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpamCan ![test](https://github.com/mushorg/spamcan/actions/workflows/test.yml/badge.svg) 2 | 3 | SpamCan is a spam trap management framework. Main purpose is to manage your accounts and manage back-ground processing. 4 | 5 | 6 | ## Set-up 7 | 8 | Requirements: See requirements.txt 9 | 10 | Rename accounts.json.dist to accounts.json and spamcan.cfg.dist to spamcan.cfg 11 | 12 | Replace the dummy data in accounts.json with your accounts (one per line, for line comments use a #). 13 | 14 | 15 | ## Running SpamCan 16 | 17 | SpamCan uses ElasticSearch to parse and store spam. Before you run SpamCan make sure you have an ES instance running. 18 | Run `python spamcan.py` and point your browser to `http:\\localhost:8000` 19 | 20 | 21 | ## TODO 22 | 23 | - Integrate with [Thug](https://github.com/buffer/thug) honeyclient 24 | - Integrate with [Cuckoo Sandbox](https://www.cuckoosandbox.org/) or [Malwr](http://malwr.com/) 25 | - Better analysis of spam messages (e.g frequent keywords, correlate URLs) 26 | -------------------------------------------------------------------------------- /conf/accounts.json.dist: -------------------------------------------------------------------------------- 1 | # Accounts can be commented out using a '#' 2 | {"user_name": "user@example.com", "password": "p4ssw0rD", "protocol": "pop3", "hostname": "127.0.0.1:8088", "smtp_host": "smtp.example.com"} 3 | -------------------------------------------------------------------------------- /conf/spamcan.json.dist: -------------------------------------------------------------------------------- 1 | { "database": "sqlite:///data/spamcan.db" } 2 | -------------------------------------------------------------------------------- /conf/thug.json.dist: -------------------------------------------------------------------------------- 1 | { "delay": "120", "referer": "http://www.google.com", "proxy": "socks4://127.0.0.1:9050", "verbose": "no", "debug": "no", "adobepdf": "9.1.0", "shockwave": "10.0.64.0", "javaplugin": "1.6.0.32", "useragent": "winxpie70" } 2 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.pool import NullPool 6 | from sqlalchemy.exc import SQLAlchemyError 7 | from sqlalchemy.orm import sessionmaker 8 | from sqlalchemy.ext.declarative import declarative_base 9 | from sqlalchemy import Column, Integer, String, Sequence 10 | 11 | Base = declarative_base() 12 | 13 | 14 | class Account(Base): 15 | __tablename__ = "account" 16 | 17 | account_id = Column(Integer, Sequence("account_id_seq"), primary_key=True) 18 | user_name = Column(String(255), nullable=False, unique=True) 19 | password = Column(String(255)) 20 | protocol = Column(String(255)) 21 | hostname = Column(String(255)) 22 | smtp_host = Column(String(255)) 23 | remote_count = Column(Integer) 24 | mailbox_count = Column(Integer) 25 | urls_count = Column(Integer) 26 | 27 | def __init__(self, account_config): 28 | self.user_name = account_config["user_name"] 29 | self.password = account_config["password"] 30 | self.protocol = account_config["protocol"] 31 | self.hostname = account_config["hostname"] 32 | self.smtp_host = account_config["smtp_host"] 33 | self.remote_count = 0 34 | self.mailbox_count = 0 35 | self.urls_count = 0 36 | 37 | def __repr__(self): 38 | return "" % ( 39 | self.user_name, 40 | self.hostname, 41 | ) 42 | 43 | 44 | class Database(object): 45 | def __init__(self, conf_dir="conf"): 46 | try: 47 | with open(os.path.join(conf_dir, "spamcan.json"), "rb") as config_file: 48 | init_config = json.loads(config_file.read()) 49 | except IOError: 50 | raise IOError( 51 | "Modify and rename conf/spamcan.json.dist to conf/spamcan.json" 52 | ) 53 | 54 | self.db_path = init_config["database"] 55 | db_engine = create_engine(self.db_path, poolclass=NullPool) 56 | db_engine.echo = False 57 | 58 | Base.metadata.create_all(db_engine) 59 | self.Session = sessionmaker(bind=db_engine) 60 | self.session = self.Session() 61 | try: 62 | with open(os.path.join(conf_dir, "accounts.json"), "rb") as account_file: 63 | for line in account_file: 64 | if line.startswith(b'#'): 65 | continue 66 | account_config = json.loads(line) 67 | self.add_account(account_config) 68 | except IOError: 69 | raise IOError( 70 | "Modify and rename conf/accounts.json.dist to conf/accounts.json" 71 | ) 72 | 73 | def add_account(self, account_config): 74 | account = Account(account_config) 75 | self.session.add(account) 76 | try: 77 | self.session.commit() 78 | except SQLAlchemyError: 79 | self.session.rollback() 80 | 81 | def fetch_all(self): 82 | try: 83 | row = self.session.query(Account) 84 | except SQLAlchemyError: 85 | return None 86 | return row 87 | 88 | def fetch_by_id(self, account_id): 89 | try: 90 | row = ( 91 | self.session.query(Account) 92 | .filter(Account.account_id == account_id) 93 | .all() 94 | ) 95 | except SQLAlchemyError: 96 | return None 97 | return row[0] 98 | 99 | def delete_by_id(self, account_id): 100 | account = self.fetch_by_id(account_id) 101 | self.session.delete(account) 102 | self.session.commit() 103 | 104 | def fetch_by_id_list(self, account_id_list): 105 | accounts = [] 106 | for account_id in account_id_list: 107 | accounts.append(self.fetch_by_id(account_id)) 108 | return accounts 109 | 110 | def update_account(self, account): 111 | self.session.flush() 112 | self.session.commit() 113 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/modules/__init__.py -------------------------------------------------------------------------------- /modules/mail_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import chardet 3 | 4 | from email.parser import HeaderParser 5 | from bs4 import BeautifulSoup 6 | 7 | 8 | class MailParser(object): 9 | def __init__(self): 10 | pass 11 | 12 | def get_urls(self, data): 13 | urls = set( 14 | re.findall( 15 | "http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", 16 | data, 17 | ) 18 | ) 19 | return list(urls) 20 | 21 | def decode_body(self, body): 22 | result = chardet.detect(body) 23 | if not type(body) == unicode: 24 | if not result["encoding"]: 25 | result["encoding"] = "ascii" 26 | body = unicode(body, result["encoding"], "ignore") 27 | return body 28 | 29 | def get_headers(self, message): 30 | """https://docs.python.org/2/library/email.message.html#email.message.Message.items""" 31 | headers = message.items() 32 | return dict(headers) 33 | 34 | def show_headers(self, header_str): 35 | parser = HeaderParser() 36 | headers = parser.parsestr(header_str).items() 37 | return headers 38 | 39 | def get_subject(self, header_str): 40 | subject = header_str["Subject"] 41 | return subject 42 | 43 | def get_sender(self, header_str): 44 | sender = header_str["From"] 45 | return sender 46 | 47 | def get_plaintext_body(self, message): 48 | for part in message.walk(): 49 | content_type = part.get_content_type() 50 | if content_type == "text/plain": 51 | body = part.get_payload(decode=True) 52 | dec_body = self.decode_body(body) 53 | return dec_body 54 | elif content_type == "text/html": 55 | html = part.get_payload(decode=True) 56 | soup = BeautifulSoup(html) 57 | text = soup.get_text() 58 | return text 59 | else: 60 | return None 61 | 62 | def get_body(self, message): 63 | for part in message.walk(): 64 | content_type = part.get_content_type() 65 | if content_type in ["text/plain", "text/html"]: 66 | body = part.get_payload(decode=True) 67 | dec_body = self.decode_body(body) 68 | return dec_body 69 | -------------------------------------------------------------------------------- /modules/mail_utils.py: -------------------------------------------------------------------------------- 1 | import mailbox 2 | import os 3 | from modules.protocols import imap_util, pop_util 4 | from email.parser import Parser 5 | 6 | # Handles POP3 and IMAP connections 7 | class MailUtil(object): 8 | def __init__(self): 9 | self.imap_handler = imap_util.IMAPUtil() 10 | self.pop_handler = pop_util.POPUtil() 11 | 12 | def request(self, account): 13 | protocol_handler = None 14 | if account.protocol == "imap": 15 | self.imap_handler.imap_connect( 16 | account.user_name, account.password, account.hostname 17 | ) 18 | protocol_handler = self.imap_handler 19 | elif account.protocol == "pop3": 20 | try: 21 | self.pop_handler.pop_connect( 22 | account.user_name, account.password, account.hostname 23 | ) 24 | except: 25 | print("Unable to connect to %s" % account.hostname) 26 | protocol_handler = self.pop_handler 27 | return protocol_handler 28 | 29 | 30 | # Handles mailboxes 31 | class MaildirUtil(object): 32 | def __init__(self): 33 | self.mail_parser = Parser() 34 | 35 | def create_mailbox(self, dirname): 36 | directory = "maildir/" + dirname + "/" 37 | for folder in ["tmp", "new", "cur"]: 38 | if not os.path.exists(directory + folder): 39 | os.makedirs(directory + folder) 40 | self.mbox = mailbox.Maildir( 41 | directory, factory=self.mail_parser.parse, create=True 42 | ) 43 | 44 | def select_mailbox(self, dirname): 45 | directory = "maildir/" + dirname + "/" 46 | self.mbox = mailbox.Maildir(directory, factory=None, create=False) 47 | return self.mbox 48 | 49 | def add_mail(self, message): 50 | self.mbox.lock() 51 | self.mbox.add(message) 52 | self.mbox.flush() 53 | 54 | def count_local_mails(self): 55 | return self.mbox.__len__() 56 | -------------------------------------------------------------------------------- /modules/protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/modules/protocols/__init__.py -------------------------------------------------------------------------------- /modules/protocols/imap_util.py: -------------------------------------------------------------------------------- 1 | import imaplib 2 | import email 3 | 4 | 5 | class IMAPUtil(object): 6 | def __init__(self): 7 | pass 8 | 9 | def imap_connect( 10 | self, user_name, password, hostname, secure=True, keyfile=None, certfile=None 11 | ): 12 | if ":" in hostname: 13 | host, port = hostname.split(":", 1) 14 | port = int(port) 15 | if secure: 16 | self.mail = imaplib.IMAP4_SSL(host, port, keyfile, certfile) 17 | else: 18 | self.mail = imaplib.IMAP4(host, port) 19 | else: 20 | if secure: 21 | port = 993 22 | self.mail = imaplib.IMAP4_SSL(hostname, port, keyfile, certfile) 23 | else: 24 | self.mail = imaplib.IMAP4(hostname) 25 | self.mail.login(user_name, password) 26 | 27 | def get_stats(self): 28 | data = self.mail.select("Inbox")[1] 29 | return int(data[0]) 30 | 31 | def fetch_mails(self, mdir): 32 | remote_count = self.get_stats() 33 | local_count = mdir.count_local_mails() 34 | for num in range(local_count + 1, remote_count + 1): 35 | _typ, msg_data = self.mail.fetch(num, "(RFC822)") 36 | for response_part in msg_data: 37 | if isinstance(response_part, tuple): 38 | msg = email.message_from_string(response_part[1]) 39 | mdir.add_mail(msg) 40 | 41 | def disconnect(self): 42 | self.mail.close() 43 | self.mail.logout() 44 | -------------------------------------------------------------------------------- /modules/protocols/pop_util.py: -------------------------------------------------------------------------------- 1 | import poplib 2 | 3 | from email.parser import Parser 4 | 5 | 6 | class POPUtil(object): 7 | def __init__(self): 8 | pass 9 | 10 | def pop_connect( 11 | self, user_name, password, hostname, ssl=False, keyfile=None, certfile=None 12 | ): 13 | if ":" in hostname: 14 | host, port = hostname.split(":", 1) 15 | port = int(port) 16 | if ssl: 17 | self.M = poplib.POP3_SSL(host, port, keyfile, certfile) 18 | else: 19 | self.M = poplib.POP3(host, port, timeout=5) 20 | else: 21 | if ssl: 22 | port = 995 23 | self.M = poplib.POP3_SSL(hostname, port, keyfile, certfile) 24 | else: 25 | self.M = poplib.POP3(hostname, timeout=5) 26 | self.M.set_debuglevel(0) 27 | self.M.user(user_name) 28 | self.M.pass_(password) 29 | 30 | def get_stats(self): 31 | return self.M.stat()[0] 32 | 33 | def fetch_mails(self, mdir): 34 | mail_parser = Parser() 35 | remote_count = self.get_stats() 36 | local_count = mdir.count_local_mails() 37 | if local_count < remote_count: 38 | for i in range(local_count + 1, remote_count + 1): 39 | raw = "\n".join(self.M.retr(i)[1]) 40 | message = mail_parser.parsestr(raw) 41 | mdir.add_mail(message) 42 | 43 | def disconnect(self): 44 | self.M.quit() 45 | 46 | 47 | if __name__ == "__main__": 48 | pop_connection = POPUtil("user_name", "password", "hostname") 49 | pop_connection.get_stats() 50 | -------------------------------------------------------------------------------- /modules/send_util.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | 3 | from email.mime.text import MIMEText 4 | from email.Utils import formatdate 5 | 6 | 7 | def reply(to, subj, body, user_name, smtp_host): 8 | msg = MIMEText("Hello, how can I help you?" + "\n\n" + body.encode("UTF-8")) 9 | msg["From"] = user_name 10 | msg["To"] = to 11 | msg["Subject"] = "Re: " + subj 12 | msg["Date"] = formatdate(localtime=True) 13 | server = smtplib.SMTP(smtp_host) 14 | try: 15 | server.sendmail(msg["From"], msg["To"], msg.as_string()) 16 | except: 17 | print("Error sending mail") 18 | server.quit() 19 | print("Send message to", to) 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.12.2 2 | Jinja2==3.1.3 3 | SQLAlchemy==1.3.0 4 | argparse==1.2.1 5 | bottle==0.12.20 6 | chardet==5.1.0 7 | elasticsearch==2.2.0 8 | nose==1.3.7 9 | python-dateutil==2.4.2 10 | six==1.10.0 11 | urllib3==1.26.18 12 | -------------------------------------------------------------------------------- /spamcan.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import bottle 4 | import database 5 | 6 | from wsgiref.simple_server import make_server 7 | from bottle import Bottle, static_file, request, redirect 8 | from jinja2 import Environment, FileSystemLoader 9 | from modules.mail_utils import MailUtil, MaildirUtil 10 | 11 | from modules import mail_parser 12 | 13 | from elasticsearch import Elasticsearch 14 | 15 | 16 | DEBUG = False 17 | 18 | for path in ["data/", "data/files"]: 19 | if not os.path.exists(path): 20 | os.makedirs(path) 21 | 22 | # Enable debugging mode 23 | bottle.debug(DEBUG) 24 | 25 | app = Bottle() 26 | 27 | template_env = Environment(loader=FileSystemLoader("./templates")) 28 | 29 | mdir = MaildirUtil() 30 | mail_handler = MailUtil() 31 | parser = mail_parser.MailParser() 32 | 33 | # Connect to SQLite db to retrieve mail accounts 34 | db = database.Database() 35 | accounts = db.fetch_all() 36 | # Initialize ElasticSearch connection 37 | es = Elasticsearch("http://localhost:9200") 38 | 39 | 40 | def get_account_stats(account): 41 | protocol_handler = mail_handler.request(account) 42 | if protocol_handler: 43 | account.remote_count = protocol_handler.get_stats() 44 | protocol_handler.disconnect() 45 | else: 46 | raise Exception("Invalid account: {0}".format(account)) 47 | 48 | 49 | for account in accounts: 50 | get_account_stats(account) 51 | 52 | 53 | @app.route("/static/") 54 | def server_static(filepath): 55 | return static_file(filepath, root="./static") 56 | 57 | 58 | @app.route("/favicon.ico") 59 | def favicon(): 60 | return static_file("/favicon.ico", root="./static") 61 | 62 | 63 | @app.route("/get_stats", method="POST") 64 | def get_stats_button(): 65 | account_id = request.forms.get("id") 66 | account = db.fetch_by_id(account_id) 67 | get_account_stats(account) 68 | return str(account.remote_count) 69 | 70 | 71 | @app.route("/fetch_mails", method="POST") 72 | def fetch_mails_button(): 73 | res_dict = {} 74 | account_id_list = json.loads(request.forms.get("ids")) 75 | accounts = db.fetch_by_id_list(account_id_list) 76 | # create mailbox directories if they don't exist 77 | for account in accounts: 78 | mdir.create_mailbox(account.user_name) 79 | protocol_handler = mail_handler.request(account) 80 | if not protocol_handler: 81 | raise Exception("Invalid account: {0}".format(account)) 82 | # fetch mails from mail server 83 | protocol_handler.fetch_mails(mdir) 84 | protocol_handler.disconnect() 85 | # update mail counters 86 | res_dict[account.account_id] = mdir.count_local_mails() 87 | account.mailbox_count = res_dict[account.account_id] 88 | 89 | user_mbox = mdir.select_mailbox(account.user_name) 90 | # parse new messages and store them in ES 91 | for i, (key, msg) in enumerate(user_mbox.items()): 92 | if msg.get_subdir() == "new": 93 | mbody = parser.get_body(msg) 94 | text = parser.get_plaintext_body(msg) 95 | mheaders = parser.get_headers(msg) 96 | urls = parser.get_urls(mbody) 97 | entry = { 98 | "mailbox": account.account_id, 99 | "headers": mheaders, 100 | "body": mbody, 101 | "analysis": { 102 | "mail_text": text, 103 | "urls": urls, 104 | }, 105 | } 106 | res = es.index(index="mailbox", doc_type="mail", body=entry) 107 | # move parsed messages to cur folder 108 | msg.set_subdir("cur") 109 | user_mbox[key] = msg 110 | mdir.mbox.close() 111 | return json.dumps(res_dict) 112 | 113 | 114 | @app.route("/crawl_mails", method="POST") 115 | def crawl_urls_button(): 116 | res_dict = {} 117 | parser = mail_parser.MailParser() 118 | account_id_list = json.loads(request.forms.get("ids")) 119 | for account_id in account_id_list: 120 | account = db.fetch_by_id(account_id) 121 | mails = db.fetch_mail_by_user(account_id) 122 | for mail in mails: 123 | body = mail.body 124 | for link in parser.get_urls(body): 125 | url = database.Url(mail_id=mail.id, url=link) 126 | db.session.add(url) 127 | db.session.commit() 128 | return json.dumps(res_dict) 129 | 130 | 131 | @app.route("/delete_acc", method="POST") 132 | def delete_acc_button(): 133 | account_id = request.forms.get("id") 134 | res = db.delete_by_id(account_id) 135 | if res == True: 136 | ret = res 137 | else: 138 | ret = "Unable to delete account: {0}".format(res) 139 | redirect("/?error={0}".format(res)) 140 | 141 | 142 | @app.route("/add_account", method="POST") 143 | def add_account(): 144 | error = "" 145 | account_config = {} 146 | account_config["user_name"] = request.forms.get("user_name") 147 | account_config["password"] = request.forms.get("password") 148 | account_config["hostname"] = request.forms.get("hostname") 149 | account_config["protocol"] = request.forms.get("protocol") 150 | account_config["smtp_host"] = request.forms.get("smtp_host") 151 | account = database.Account(account_config) 152 | try: 153 | protocol_handler = mail_handler.request(account) 154 | protocol_handler.disconnect() 155 | except Exception as e: 156 | error = "Connection error ({0})".format(e) 157 | else: 158 | db.add_account(account_config) 159 | accounts = db.fetch_all() 160 | redirect("/?error={0}".format(error)) 161 | 162 | 163 | @app.route("/files") 164 | def files(): 165 | files_info = {} 166 | files_info["file_num"] = len(os.listdir("data/files")) 167 | template = template_env.get_template("files.html") 168 | return template.render(files_info=files_info) 169 | 170 | 171 | @app.route("/") 172 | def spamcan_handler(): 173 | accounts = db.fetch_all() 174 | template = template_env.get_template("index.html") 175 | if request.query.error == "": 176 | request.query.error = None 177 | return template.render(account_list=accounts, error=request.query.error) 178 | 179 | 180 | @app.route("/urls") 181 | def urls(): 182 | query = { 183 | "query": {"exists": {"field": "analysis.urls"}}, 184 | "fields": ["analysis.urls", "id"], 185 | } 186 | 187 | res = es.search(index="mailbox", body=query) 188 | res = res["hits"]["hits"] 189 | # urls = res['fields'] 190 | template = template_env.get_template("urls.html") 191 | if request.query.error == "": 192 | request.query.error = None 193 | return template.render(results=res, error=request.query.error) 194 | 195 | 196 | @app.route("/mails") 197 | def mails(): 198 | res = es.search(index="mailbox", body={"query": {"match_all": {}}}) 199 | mails = res["hits"]["hits"] 200 | template = template_env.get_template("mails.html") 201 | if request.query.error == "": 202 | request.query.error = None 203 | return template.render(mail_list=mails, error=request.query.error) 204 | 205 | 206 | @app.route("/mail/") 207 | def mail(mailId): 208 | res = es.get(index="mailbox", doc_type="mail", id=mailId) 209 | mail = res["_source"] 210 | mheaders = res["_source"]["headers"] 211 | template = template_env.get_template("mail.html") 212 | if request.query.error == "": 213 | request.query.error = None 214 | return template.render(mail=mail, error=request.query.error, header_dict=mheaders) 215 | 216 | 217 | if __name__ == "__main__": 218 | httpd = make_server("0.0.0.0", 8000, app) 219 | print("Serving on port 8000...") 220 | httpd.serve_forever() 221 | -------------------------------------------------------------------------------- /static/css/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.0.1 3 | * the iconic font designed for use with Twitter Bootstrap 4 | * ------------------------------------------------------- 5 | * The full suite of pictographic icons, examples, and documentation 6 | * can be found at: http://fortawesome.github.com/Font-Awesome/ 7 | * 8 | * License 9 | * ------------------------------------------------------- 10 | * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL 11 | * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - 12 | * http://opensource.org/licenses/mit-license.html 13 | * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ 14 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 15 | * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" 16 | 17 | * Contact 18 | * ------------------------------------------------------- 19 | * Email: dave@davegandy.com 20 | * Twitter: http://twitter.com/fortaweso_me 21 | * Work: Lead Product Designer @ http://kyruus.com 22 | */ 23 | @font-face { 24 | font-family: 'FontAwesome'; 25 | src: url('../font/fontawesome-webfont.eot?v=3.0.1'); 26 | src: url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), 27 | url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'), 28 | url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | /* Font Awesome styles 33 | ------------------------------------------------------- */ 34 | [class^="icon-"], 35 | [class*=" icon-"] { 36 | font-family: FontAwesome; 37 | font-weight: normal; 38 | font-style: normal; 39 | text-decoration: inherit; 40 | -webkit-font-smoothing: antialiased; 41 | 42 | /* sprites.less reset */ 43 | display: inline; 44 | width: auto; 45 | height: auto; 46 | line-height: normal; 47 | vertical-align: baseline; 48 | background-image: none; 49 | background-position: 0% 0%; 50 | background-repeat: repeat; 51 | margin-top: 0; 52 | } 53 | /* more sprites.less reset */ 54 | .icon-white, 55 | .nav-pills > .active > a > [class^="icon-"], 56 | .nav-pills > .active > a > [class*=" icon-"], 57 | .nav-list > .active > a > [class^="icon-"], 58 | .nav-list > .active > a > [class*=" icon-"], 59 | .navbar-inverse .nav > .active > a > [class^="icon-"], 60 | .navbar-inverse .nav > .active > a > [class*=" icon-"], 61 | .dropdown-menu > li > a:hover > [class^="icon-"], 62 | .dropdown-menu > li > a:hover > [class*=" icon-"], 63 | .dropdown-menu > .active > a > [class^="icon-"], 64 | .dropdown-menu > .active > a > [class*=" icon-"], 65 | .dropdown-submenu:hover > a > [class^="icon-"], 66 | .dropdown-submenu:hover > a > [class*=" icon-"] { 67 | background-image: none; 68 | } 69 | [class^="icon-"]:before, 70 | [class*=" icon-"]:before { 71 | text-decoration: inherit; 72 | display: inline-block; 73 | speak: none; 74 | } 75 | /* makes sure icons active on rollover in links */ 76 | a [class^="icon-"], 77 | a [class*=" icon-"] { 78 | display: inline-block; 79 | } 80 | /* makes the font 33% larger relative to the icon container */ 81 | .icon-large:before { 82 | vertical-align: -10%; 83 | font-size: 1.3333333333333333em; 84 | } 85 | .btn [class^="icon-"], 86 | .nav [class^="icon-"], 87 | .btn [class*=" icon-"], 88 | .nav [class*=" icon-"] { 89 | display: inline; 90 | /* keeps button heights with and without icons the same */ 91 | 92 | } 93 | .btn [class^="icon-"].icon-large, 94 | .nav [class^="icon-"].icon-large, 95 | .btn [class*=" icon-"].icon-large, 96 | .nav [class*=" icon-"].icon-large { 97 | line-height: .9em; 98 | } 99 | .btn [class^="icon-"].icon-spin, 100 | .nav [class^="icon-"].icon-spin, 101 | .btn [class*=" icon-"].icon-spin, 102 | .nav [class*=" icon-"].icon-spin { 103 | display: inline-block; 104 | } 105 | .nav-tabs [class^="icon-"], 106 | .nav-pills [class^="icon-"], 107 | .nav-tabs [class*=" icon-"], 108 | .nav-pills [class*=" icon-"] { 109 | /* keeps button heights with and without icons the same */ 110 | 111 | } 112 | .nav-tabs [class^="icon-"], 113 | .nav-pills [class^="icon-"], 114 | .nav-tabs [class*=" icon-"], 115 | .nav-pills [class*=" icon-"], 116 | .nav-tabs [class^="icon-"].icon-large, 117 | .nav-pills [class^="icon-"].icon-large, 118 | .nav-tabs [class*=" icon-"].icon-large, 119 | .nav-pills [class*=" icon-"].icon-large { 120 | line-height: .9em; 121 | } 122 | li [class^="icon-"], 123 | .nav li [class^="icon-"], 124 | li [class*=" icon-"], 125 | .nav li [class*=" icon-"] { 126 | display: inline-block; 127 | width: 1.25em; 128 | text-align: center; 129 | } 130 | li [class^="icon-"].icon-large, 131 | .nav li [class^="icon-"].icon-large, 132 | li [class*=" icon-"].icon-large, 133 | .nav li [class*=" icon-"].icon-large { 134 | /* increased font size for icon-large */ 135 | 136 | width: 1.5625em; 137 | } 138 | ul.icons { 139 | list-style-type: none; 140 | text-indent: -0.75em; 141 | } 142 | ul.icons li [class^="icon-"], 143 | ul.icons li [class*=" icon-"] { 144 | width: .75em; 145 | } 146 | .icon-muted { 147 | color: #eeeeee; 148 | } 149 | .icon-border { 150 | border: solid 1px #eeeeee; 151 | padding: .2em .25em .15em; 152 | -webkit-border-radius: 3px; 153 | -moz-border-radius: 3px; 154 | border-radius: 3px; 155 | } 156 | .icon-2x { 157 | font-size: 2em; 158 | } 159 | .icon-2x.icon-border { 160 | border-width: 2px; 161 | -webkit-border-radius: 4px; 162 | -moz-border-radius: 4px; 163 | border-radius: 4px; 164 | } 165 | .icon-3x { 166 | font-size: 3em; 167 | } 168 | .icon-3x.icon-border { 169 | border-width: 3px; 170 | -webkit-border-radius: 5px; 171 | -moz-border-radius: 5px; 172 | border-radius: 5px; 173 | } 174 | .icon-4x { 175 | font-size: 4em; 176 | } 177 | .icon-4x.icon-border { 178 | border-width: 4px; 179 | -webkit-border-radius: 6px; 180 | -moz-border-radius: 6px; 181 | border-radius: 6px; 182 | } 183 | .pull-right { 184 | float: right; 185 | } 186 | .pull-left { 187 | float: left; 188 | } 189 | [class^="icon-"].pull-left, 190 | [class*=" icon-"].pull-left { 191 | margin-right: .3em; 192 | } 193 | [class^="icon-"].pull-right, 194 | [class*=" icon-"].pull-right { 195 | margin-left: .3em; 196 | } 197 | .btn [class^="icon-"].pull-left.icon-2x, 198 | .btn [class*=" icon-"].pull-left.icon-2x, 199 | .btn [class^="icon-"].pull-right.icon-2x, 200 | .btn [class*=" icon-"].pull-right.icon-2x { 201 | margin-top: .18em; 202 | } 203 | .btn [class^="icon-"].icon-spin.icon-large, 204 | .btn [class*=" icon-"].icon-spin.icon-large { 205 | line-height: .8em; 206 | } 207 | .btn.btn-small [class^="icon-"].pull-left.icon-2x, 208 | .btn.btn-small [class*=" icon-"].pull-left.icon-2x, 209 | .btn.btn-small [class^="icon-"].pull-right.icon-2x, 210 | .btn.btn-small [class*=" icon-"].pull-right.icon-2x { 211 | margin-top: .25em; 212 | } 213 | .btn.btn-large [class^="icon-"], 214 | .btn.btn-large [class*=" icon-"] { 215 | margin-top: 0; 216 | } 217 | .btn.btn-large [class^="icon-"].pull-left.icon-2x, 218 | .btn.btn-large [class*=" icon-"].pull-left.icon-2x, 219 | .btn.btn-large [class^="icon-"].pull-right.icon-2x, 220 | .btn.btn-large [class*=" icon-"].pull-right.icon-2x { 221 | margin-top: .05em; 222 | } 223 | .btn.btn-large [class^="icon-"].pull-left.icon-2x, 224 | .btn.btn-large [class*=" icon-"].pull-left.icon-2x { 225 | margin-right: .2em; 226 | } 227 | .btn.btn-large [class^="icon-"].pull-right.icon-2x, 228 | .btn.btn-large [class*=" icon-"].pull-right.icon-2x { 229 | margin-left: .2em; 230 | } 231 | .icon-spin { 232 | display: inline-block; 233 | -moz-animation: spin 2s infinite linear; 234 | -o-animation: spin 2s infinite linear; 235 | -webkit-animation: spin 2s infinite linear; 236 | animation: spin 2s infinite linear; 237 | } 238 | @-moz-keyframes spin { 239 | 0% { -moz-transform: rotate(0deg); } 240 | 100% { -moz-transform: rotate(359deg); } 241 | } 242 | @-webkit-keyframes spin { 243 | 0% { -webkit-transform: rotate(0deg); } 244 | 100% { -webkit-transform: rotate(359deg); } 245 | } 246 | @-o-keyframes spin { 247 | 0% { -o-transform: rotate(0deg); } 248 | 100% { -o-transform: rotate(359deg); } 249 | } 250 | @-ms-keyframes spin { 251 | 0% { -ms-transform: rotate(0deg); } 252 | 100% { -ms-transform: rotate(359deg); } 253 | } 254 | @keyframes spin { 255 | 0% { transform: rotate(0deg); } 256 | 100% { transform: rotate(359deg); } 257 | } 258 | @-moz-document url-prefix() { 259 | .icon-spin { 260 | height: .9em; 261 | } 262 | .btn .icon-spin { 263 | height: auto; 264 | } 265 | .icon-spin.icon-large { 266 | height: 1.25em; 267 | } 268 | .btn .icon-spin.icon-large { 269 | height: .75em; 270 | } 271 | } 272 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 273 | readers do not read off random characters that represent icons */ 274 | .icon-glass:before { content: "\f000"; } 275 | .icon-music:before { content: "\f001"; } 276 | .icon-search:before { content: "\f002"; } 277 | .icon-envelope:before { content: "\f003"; } 278 | .icon-heart:before { content: "\f004"; } 279 | .icon-star:before { content: "\f005"; } 280 | .icon-star-empty:before { content: "\f006"; } 281 | .icon-user:before { content: "\f007"; } 282 | .icon-film:before { content: "\f008"; } 283 | .icon-th-large:before { content: "\f009"; } 284 | .icon-th:before { content: "\f00a"; } 285 | .icon-th-list:before { content: "\f00b"; } 286 | .icon-ok:before { content: "\f00c"; } 287 | .icon-remove:before { content: "\f00d"; } 288 | .icon-zoom-in:before { content: "\f00e"; } 289 | 290 | .icon-zoom-out:before { content: "\f010"; } 291 | .icon-off:before { content: "\f011"; } 292 | .icon-signal:before { content: "\f012"; } 293 | .icon-cog:before { content: "\f013"; } 294 | .icon-trash:before { content: "\f014"; } 295 | .icon-home:before { content: "\f015"; } 296 | .icon-file:before { content: "\f016"; } 297 | .icon-time:before { content: "\f017"; } 298 | .icon-road:before { content: "\f018"; } 299 | .icon-download-alt:before { content: "\f019"; } 300 | .icon-download:before { content: "\f01a"; } 301 | .icon-upload:before { content: "\f01b"; } 302 | .icon-inbox:before { content: "\f01c"; } 303 | .icon-play-circle:before { content: "\f01d"; } 304 | .icon-repeat:before { content: "\f01e"; } 305 | 306 | /* \f020 doesn't work in Safari. all shifted one down */ 307 | .icon-refresh:before { content: "\f021"; } 308 | .icon-list-alt:before { content: "\f022"; } 309 | .icon-lock:before { content: "\f023"; } 310 | .icon-flag:before { content: "\f024"; } 311 | .icon-headphones:before { content: "\f025"; } 312 | .icon-volume-off:before { content: "\f026"; } 313 | .icon-volume-down:before { content: "\f027"; } 314 | .icon-volume-up:before { content: "\f028"; } 315 | .icon-qrcode:before { content: "\f029"; } 316 | .icon-barcode:before { content: "\f02a"; } 317 | .icon-tag:before { content: "\f02b"; } 318 | .icon-tags:before { content: "\f02c"; } 319 | .icon-book:before { content: "\f02d"; } 320 | .icon-bookmark:before { content: "\f02e"; } 321 | .icon-print:before { content: "\f02f"; } 322 | 323 | .icon-camera:before { content: "\f030"; } 324 | .icon-font:before { content: "\f031"; } 325 | .icon-bold:before { content: "\f032"; } 326 | .icon-italic:before { content: "\f033"; } 327 | .icon-text-height:before { content: "\f034"; } 328 | .icon-text-width:before { content: "\f035"; } 329 | .icon-align-left:before { content: "\f036"; } 330 | .icon-align-center:before { content: "\f037"; } 331 | .icon-align-right:before { content: "\f038"; } 332 | .icon-align-justify:before { content: "\f039"; } 333 | .icon-list:before { content: "\f03a"; } 334 | .icon-indent-left:before { content: "\f03b"; } 335 | .icon-indent-right:before { content: "\f03c"; } 336 | .icon-facetime-video:before { content: "\f03d"; } 337 | .icon-picture:before { content: "\f03e"; } 338 | 339 | .icon-pencil:before { content: "\f040"; } 340 | .icon-map-marker:before { content: "\f041"; } 341 | .icon-adjust:before { content: "\f042"; } 342 | .icon-tint:before { content: "\f043"; } 343 | .icon-edit:before { content: "\f044"; } 344 | .icon-share:before { content: "\f045"; } 345 | .icon-check:before { content: "\f046"; } 346 | .icon-move:before { content: "\f047"; } 347 | .icon-step-backward:before { content: "\f048"; } 348 | .icon-fast-backward:before { content: "\f049"; } 349 | .icon-backward:before { content: "\f04a"; } 350 | .icon-play:before { content: "\f04b"; } 351 | .icon-pause:before { content: "\f04c"; } 352 | .icon-stop:before { content: "\f04d"; } 353 | .icon-forward:before { content: "\f04e"; } 354 | 355 | .icon-fast-forward:before { content: "\f050"; } 356 | .icon-step-forward:before { content: "\f051"; } 357 | .icon-eject:before { content: "\f052"; } 358 | .icon-chevron-left:before { content: "\f053"; } 359 | .icon-chevron-right:before { content: "\f054"; } 360 | .icon-plus-sign:before { content: "\f055"; } 361 | .icon-minus-sign:before { content: "\f056"; } 362 | .icon-remove-sign:before { content: "\f057"; } 363 | .icon-ok-sign:before { content: "\f058"; } 364 | .icon-question-sign:before { content: "\f059"; } 365 | .icon-info-sign:before { content: "\f05a"; } 366 | .icon-screenshot:before { content: "\f05b"; } 367 | .icon-remove-circle:before { content: "\f05c"; } 368 | .icon-ok-circle:before { content: "\f05d"; } 369 | .icon-ban-circle:before { content: "\f05e"; } 370 | 371 | .icon-arrow-left:before { content: "\f060"; } 372 | .icon-arrow-right:before { content: "\f061"; } 373 | .icon-arrow-up:before { content: "\f062"; } 374 | .icon-arrow-down:before { content: "\f063"; } 375 | .icon-share-alt:before { content: "\f064"; } 376 | .icon-resize-full:before { content: "\f065"; } 377 | .icon-resize-small:before { content: "\f066"; } 378 | .icon-plus:before { content: "\f067"; } 379 | .icon-minus:before { content: "\f068"; } 380 | .icon-asterisk:before { content: "\f069"; } 381 | .icon-exclamation-sign:before { content: "\f06a"; } 382 | .icon-gift:before { content: "\f06b"; } 383 | .icon-leaf:before { content: "\f06c"; } 384 | .icon-fire:before { content: "\f06d"; } 385 | .icon-eye-open:before { content: "\f06e"; } 386 | 387 | .icon-eye-close:before { content: "\f070"; } 388 | .icon-warning-sign:before { content: "\f071"; } 389 | .icon-plane:before { content: "\f072"; } 390 | .icon-calendar:before { content: "\f073"; } 391 | .icon-random:before { content: "\f074"; } 392 | .icon-comment:before { content: "\f075"; } 393 | .icon-magnet:before { content: "\f076"; } 394 | .icon-chevron-up:before { content: "\f077"; } 395 | .icon-chevron-down:before { content: "\f078"; } 396 | .icon-retweet:before { content: "\f079"; } 397 | .icon-shopping-cart:before { content: "\f07a"; } 398 | .icon-folder-close:before { content: "\f07b"; } 399 | .icon-folder-open:before { content: "\f07c"; } 400 | .icon-resize-vertical:before { content: "\f07d"; } 401 | .icon-resize-horizontal:before { content: "\f07e"; } 402 | 403 | .icon-bar-chart:before { content: "\f080"; } 404 | .icon-twitter-sign:before { content: "\f081"; } 405 | .icon-facebook-sign:before { content: "\f082"; } 406 | .icon-camera-retro:before { content: "\f083"; } 407 | .icon-key:before { content: "\f084"; } 408 | .icon-cogs:before { content: "\f085"; } 409 | .icon-comments:before { content: "\f086"; } 410 | .icon-thumbs-up:before { content: "\f087"; } 411 | .icon-thumbs-down:before { content: "\f088"; } 412 | .icon-star-half:before { content: "\f089"; } 413 | .icon-heart-empty:before { content: "\f08a"; } 414 | .icon-signout:before { content: "\f08b"; } 415 | .icon-linkedin-sign:before { content: "\f08c"; } 416 | .icon-pushpin:before { content: "\f08d"; } 417 | .icon-external-link:before { content: "\f08e"; } 418 | 419 | .icon-signin:before { content: "\f090"; } 420 | .icon-trophy:before { content: "\f091"; } 421 | .icon-github-sign:before { content: "\f092"; } 422 | .icon-upload-alt:before { content: "\f093"; } 423 | .icon-lemon:before { content: "\f094"; } 424 | .icon-phone:before { content: "\f095"; } 425 | .icon-check-empty:before { content: "\f096"; } 426 | .icon-bookmark-empty:before { content: "\f097"; } 427 | .icon-phone-sign:before { content: "\f098"; } 428 | .icon-twitter:before { content: "\f099"; } 429 | .icon-facebook:before { content: "\f09a"; } 430 | .icon-github:before { content: "\f09b"; } 431 | .icon-unlock:before { content: "\f09c"; } 432 | .icon-credit-card:before { content: "\f09d"; } 433 | .icon-rss:before { content: "\f09e"; } 434 | 435 | .icon-hdd:before { content: "\f0a0"; } 436 | .icon-bullhorn:before { content: "\f0a1"; } 437 | .icon-bell:before { content: "\f0a2"; } 438 | .icon-certificate:before { content: "\f0a3"; } 439 | .icon-hand-right:before { content: "\f0a4"; } 440 | .icon-hand-left:before { content: "\f0a5"; } 441 | .icon-hand-up:before { content: "\f0a6"; } 442 | .icon-hand-down:before { content: "\f0a7"; } 443 | .icon-circle-arrow-left:before { content: "\f0a8"; } 444 | .icon-circle-arrow-right:before { content: "\f0a9"; } 445 | .icon-circle-arrow-up:before { content: "\f0aa"; } 446 | .icon-circle-arrow-down:before { content: "\f0ab"; } 447 | .icon-globe:before { content: "\f0ac"; } 448 | .icon-wrench:before { content: "\f0ad"; } 449 | .icon-tasks:before { content: "\f0ae"; } 450 | 451 | .icon-filter:before { content: "\f0b0"; } 452 | .icon-briefcase:before { content: "\f0b1"; } 453 | .icon-fullscreen:before { content: "\f0b2"; } 454 | 455 | .icon-group:before { content: "\f0c0"; } 456 | .icon-link:before { content: "\f0c1"; } 457 | .icon-cloud:before { content: "\f0c2"; } 458 | .icon-beaker:before { content: "\f0c3"; } 459 | .icon-cut:before { content: "\f0c4"; } 460 | .icon-copy:before { content: "\f0c5"; } 461 | .icon-paper-clip:before { content: "\f0c6"; } 462 | .icon-save:before { content: "\f0c7"; } 463 | .icon-sign-blank:before { content: "\f0c8"; } 464 | .icon-reorder:before { content: "\f0c9"; } 465 | .icon-list-ul:before { content: "\f0ca"; } 466 | .icon-list-ol:before { content: "\f0cb"; } 467 | .icon-strikethrough:before { content: "\f0cc"; } 468 | .icon-underline:before { content: "\f0cd"; } 469 | .icon-table:before { content: "\f0ce"; } 470 | 471 | .icon-magic:before { content: "\f0d0"; } 472 | .icon-truck:before { content: "\f0d1"; } 473 | .icon-pinterest:before { content: "\f0d2"; } 474 | .icon-pinterest-sign:before { content: "\f0d3"; } 475 | .icon-google-plus-sign:before { content: "\f0d4"; } 476 | .icon-google-plus:before { content: "\f0d5"; } 477 | .icon-money:before { content: "\f0d6"; } 478 | .icon-caret-down:before { content: "\f0d7"; } 479 | .icon-caret-up:before { content: "\f0d8"; } 480 | .icon-caret-left:before { content: "\f0d9"; } 481 | .icon-caret-right:before { content: "\f0da"; } 482 | .icon-columns:before { content: "\f0db"; } 483 | .icon-sort:before { content: "\f0dc"; } 484 | .icon-sort-down:before { content: "\f0dd"; } 485 | .icon-sort-up:before { content: "\f0de"; } 486 | 487 | .icon-envelope-alt:before { content: "\f0e0"; } 488 | .icon-linkedin:before { content: "\f0e1"; } 489 | .icon-undo:before { content: "\f0e2"; } 490 | .icon-legal:before { content: "\f0e3"; } 491 | .icon-dashboard:before { content: "\f0e4"; } 492 | .icon-comment-alt:before { content: "\f0e5"; } 493 | .icon-comments-alt:before { content: "\f0e6"; } 494 | .icon-bolt:before { content: "\f0e7"; } 495 | .icon-sitemap:before { content: "\f0e8"; } 496 | .icon-umbrella:before { content: "\f0e9"; } 497 | .icon-paste:before { content: "\f0ea"; } 498 | .icon-lightbulb:before { content: "\f0eb"; } 499 | .icon-exchange:before { content: "\f0ec"; } 500 | .icon-cloud-download:before { content: "\f0ed"; } 501 | .icon-cloud-upload:before { content: "\f0ee"; } 502 | 503 | .icon-user-md:before { content: "\f0f0"; } 504 | .icon-stethoscope:before { content: "\f0f1"; } 505 | .icon-suitcase:before { content: "\f0f2"; } 506 | .icon-bell-alt:before { content: "\f0f3"; } 507 | .icon-coffee:before { content: "\f0f4"; } 508 | .icon-food:before { content: "\f0f5"; } 509 | .icon-file-alt:before { content: "\f0f6"; } 510 | .icon-building:before { content: "\f0f7"; } 511 | .icon-hospital:before { content: "\f0f8"; } 512 | .icon-ambulance:before { content: "\f0f9"; } 513 | .icon-medkit:before { content: "\f0fa"; } 514 | .icon-fighter-jet:before { content: "\f0fb"; } 515 | .icon-beer:before { content: "\f0fc"; } 516 | .icon-h-sign:before { content: "\f0fd"; } 517 | .icon-plus-sign-alt:before { content: "\f0fe"; } 518 | 519 | .icon-double-angle-left:before { content: "\f100"; } 520 | .icon-double-angle-right:before { content: "\f101"; } 521 | .icon-double-angle-up:before { content: "\f102"; } 522 | .icon-double-angle-down:before { content: "\f103"; } 523 | .icon-angle-left:before { content: "\f104"; } 524 | .icon-angle-right:before { content: "\f105"; } 525 | .icon-angle-up:before { content: "\f106"; } 526 | .icon-angle-down:before { content: "\f107"; } 527 | .icon-desktop:before { content: "\f108"; } 528 | .icon-laptop:before { content: "\f109"; } 529 | .icon-tablet:before { content: "\f10a"; } 530 | .icon-mobile-phone:before { content: "\f10b"; } 531 | .icon-circle-blank:before { content: "\f10c"; } 532 | .icon-quote-left:before { content: "\f10d"; } 533 | .icon-quote-right:before { content: "\f10e"; } 534 | 535 | .icon-spinner:before { content: "\f110"; } 536 | .icon-circle:before { content: "\f111"; } 537 | .icon-reply:before { content: "\f112"; } 538 | .icon-github-alt:before { content: "\f113"; } 539 | .icon-folder-close-alt:before { content: "\f114"; } 540 | .icon-folder-open-alt:before { content: "\f115"; } 541 | -------------------------------------------------------------------------------- /static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.0.1 3 | * the iconic font designed for use with Twitter Bootstrap 4 | * ------------------------------------------------------- 5 | * The full suite of pictographic icons, examples, and documentation 6 | * can be found at: http://fortawesome.github.com/Font-Awesome/ 7 | * 8 | * License 9 | * ------------------------------------------------------- 10 | * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL 11 | * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - 12 | * http://opensource.org/licenses/mit-license.html 13 | * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ 14 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 15 | * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" 16 | 17 | * Contact 18 | * ------------------------------------------------------- 19 | * Email: dave@davegandy.com 20 | * Twitter: http://twitter.com/fortaweso_me 21 | * Work: Lead Product Designer @ http://kyruus.com 22 | */ 23 | 24 | @font-face{ 25 | font-family:'FontAwesome'; 26 | src:url('../font/fontawesome-webfont.eot?v=3.0.1'); 27 | src:url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), 28 | url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'), 29 | url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); 30 | font-weight:normal; 31 | font-style:normal } 32 | 33 | [class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}a [class^="icon-"],a [class*=" icon-"]{display:inline-block}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}.btn [class^="icon-"],.nav [class^="icon-"],.btn [class*=" icon-"],.nav [class*=" icon-"]{display:inline}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}li [class^="icon-"],.nav li [class^="icon-"],li [class*=" icon-"],.nav li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li [class^="icon-"].icon-large,.nav li [class^="icon-"].icon-large,li [class*=" icon-"].icon-large,.nav li [class*=" icon-"].icon-large{width:1.5625em}ul.icons{list-style-type:none;text-indent:-0.75em}ul.icons li [class^="icon-"],ul.icons li [class*=" icon-"]{width:.75em}.icon-muted{color:#eee}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-moz-document url-prefix(){.icon-spin{height:.9em}.btn .icon-spin{height:auto}.icon-spin.icon-large{height:1.25em}.btn .icon-spin.icon-large{height:.75em}}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"} -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/static/favicon.ico -------------------------------------------------------------------------------- /static/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/static/font/FontAwesome.otf -------------------------------------------------------------------------------- /static/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/static/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/static/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/static/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.0.0 (https://getbootstrap.com) 3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); 7 | //# sourceMappingURL=bootstrap.min.js.map -------------------------------------------------------------------------------- /static/js/jquery-3.7.0.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.7.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.0",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},R=function(){V()},M=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&z(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function X(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&M(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function U(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function z(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",R),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Me(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return R(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return R(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 2 | 3 | 4 | 5 | SpamCan 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 |
42 | 49 |

SpamCan Management Framework

50 |
51 | 52 |
53 |
54 | {% block content %} 55 | {% endblock %} 56 |
57 | 58 |
59 | 60 | 63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /templates/files.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | Number of files: {{ files_info["file_num"] }} 4 | {% endblock %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | {% for account in account_list %} 16 |
17 |
18 |
19 |
20 | 23 |
24 |
25 | ID: {{ account.account_id }} 26 |
27 | 30 |
31 | {{ account.remote_count }} 32 |
33 |
34 | {{ account.mailbox_count }} 35 |
36 |
37 | {{ account.urls_count }} 38 |
39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Server: {{ account.hostname }} 53 |
54 |
55 | Protocol: {% filter upper %}{{ account.protocol }}{% endfilter %} 56 |
57 |
58 |
59 |
60 |
61 | {% endfor %} 62 |
63 |
64 |
65 |
66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 |
74 |
75 | 76 | 77 |
78 |
79 | 80 | 86 |
87 |
88 | 89 | 90 |
91 | 92 |
93 | {% endblock %} -------------------------------------------------------------------------------- /templates/mail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 | {% for key, value in header_dict.items() recursive %} 6 |

{{key}}: {{value}}

7 | {% endfor %} 8 |
9 |
10 |
11 | 12 |
13 |
14 | {{mail.body}} 15 |
16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /templates/mails.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for mail in mail_list %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% endfor %} 21 |
IDAccountSubjectSender
{{mail._id}}{{mail._source.mailbox}}{{mail._source.subject}}{{mail._source.sender}}
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /templates/urls.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% for res in results %} 13 | 14 | 15 | 30 | 31 | {% endfor %} 32 |
URLMail
{{res._id}} 16 | {% for key,value in res.items() recursive %} 17 | {% if key == "fields" %} 18 | {% for fld_name, fld_val in value.items() recursive %} 19 | {% if fld_name == "analysis.urls" %} 20 | {% for url in fld_val %} 21 |
22 | {{url}} 23 |
24 | {% endfor %} 25 | {% endif %} 26 | {% endfor %} 27 | {% endif %} 28 | {% endfor %} 29 |
33 | {% endblock %} -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import unittest 3 | import os 4 | import shutil 5 | import threading 6 | import socket 7 | import time 8 | import tempfile 9 | import json 10 | 11 | import database 12 | 13 | from testing import pop_server 14 | from modules.mail_utils import MailUtil, MaildirUtil 15 | 16 | 17 | class SpamCanDBTest(unittest.TestCase): 18 | def setUp(self): 19 | self.tmpdir = tempfile.mkdtemp() 20 | paths = [ 21 | os.path.join(self.tmpdir, "data-test/"), 22 | os.path.join(self.tmpdir, "data-test/files"), 23 | ] 24 | 25 | for path in paths: 26 | if not os.path.exists(path): 27 | os.makedirs(path) 28 | 29 | write_config_files(self.tmpdir, 12345) 30 | 31 | def tearDown(self): 32 | shutil.rmtree(self.tmpdir) 33 | 34 | def test_database(self): 35 | conf_dir = os.path.join(self.tmpdir) 36 | self.db = database.Database(conf_dir) 37 | accounts = self.db.fetch_all() 38 | self.assert_(len([acc for acc in accounts]) == 1) 39 | self.db.session.close() 40 | 41 | 42 | class SpamCanPOPTest(unittest.TestCase): 43 | @classmethod 44 | def setUpClass(cls): 45 | paths = [ 46 | "data/", 47 | ] 48 | for path in paths: 49 | if not os.path.exists(path): 50 | os.makedirs(path) 51 | configs = ["conf/accounts.json", "conf/spamcan.json"] 52 | for conf in configs: 53 | if not os.path.exists(conf): 54 | shutil.copyfile(conf + ".dist", conf) 55 | cls.server = pop_server.pop_server() 56 | cls.server_port = cls.server.server_address[1] 57 | cls.t = threading.Thread(target=cls.server.serve_forever) 58 | cls.t.start() 59 | 60 | @classmethod 61 | def tearDownClass(cls): 62 | cls.server.shutdown() 63 | cls.server.socket.close() 64 | cls.t.join() 65 | 66 | def test_pop_server(self): 67 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 68 | try: 69 | sock.connect(("localhost", SpamCanPOPTest.server_port)) 70 | received = sock.recv(1024) 71 | sock.sendall(b'QUIT foobar\n') 72 | finally: 73 | sock.close() 74 | self.assert_(received == b'+OK SpamCan test server ready\r\n') 75 | 76 | def test_pop_client(self): 77 | account_config = { 78 | "user_name": "foo@localhost", 79 | "password": "foobar", 80 | "protocol": "pop3", 81 | "hostname": "localhost:{0}".format(SpamCanPOPTest.server_port), 82 | "smtp_host": "localhost", 83 | } 84 | account = database.Account(account_config) 85 | mail_handler = MailUtil() 86 | protocol_handler = mail_handler.request(account) 87 | count = protocol_handler.get_stats() 88 | protocol_handler.disconnect() 89 | self.assert_(count == 1) 90 | 91 | def test_get_stats_method(self): 92 | 93 | tmpdir = tempfile.mkdtemp() 94 | try: 95 | write_config_files(tmpdir, SpamCanPOPTest.server_port) 96 | mail_handler = MailUtil() 97 | db = database.Database(conf_dir=tmpdir) 98 | account = db.fetch_by_id(1) 99 | protocol_handler = mail_handler.request(account) 100 | if protocol_handler: 101 | account.remote_count = protocol_handler.get_stats() 102 | protocol_handler.disconnect() 103 | self.assert_(account.remote_count == 1) 104 | finally: 105 | db.session.close() 106 | if os.path.isdir(tmpdir): 107 | shutil.rmtree(tmpdir) 108 | 109 | 110 | def write_config_files(tmpdir, server_port): 111 | account_config = { 112 | "user_name": "user@example.com", 113 | "password": "p4ssw0rD", 114 | "protocol": "pop3", 115 | "hostname": "127.0.0.1:{0}".format(server_port), 116 | "smtp_host": "smtp.example.com", 117 | } 118 | 119 | spamcan_config = { 120 | "database": "sqlite:///{0}".format(os.path.join(tmpdir, "spamcan.db")) 121 | } 122 | 123 | with open(os.path.join(tmpdir, "accounts.json"), "w") as f: 124 | json.dump(account_config, f) 125 | with open(os.path.join(tmpdir, "spamcan.json"), "w") as f: 126 | json.dump(spamcan_config, f) 127 | 128 | 129 | if __name__ == "__main__": 130 | nose_conf = nose.config.Config() 131 | nose_conf.verbosity = 3 132 | nose.main(config=nose_conf) 133 | -------------------------------------------------------------------------------- /testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mushorg/spamcan/a85da1d1598d3978dff709f306b53f22c810bd70/testing/__init__.py -------------------------------------------------------------------------------- /testing/pop_server.py: -------------------------------------------------------------------------------- 1 | ## Based on http://code.activestate.com/recipes/534131/ 2 | 3 | import socketserver 4 | 5 | 6 | class Message(object): 7 | def __init__(self): 8 | self.data = "fooo http://google.com" 9 | self.size = len(self.data) 10 | self.top = "foo" 11 | self.bot = "bar" 12 | 13 | 14 | def handleUser(data, msg): 15 | return "+OK user accepted" 16 | 17 | 18 | def handlePass(data, msg): 19 | return "+OK pass accepted" 20 | 21 | 22 | def handleStat(data, msg): 23 | return "+OK 1 %i" % (msg.size * 1) 24 | 25 | 26 | def handleList(data, msg): 27 | return "+OK 1 messages (%i octets)\r\n1 %i\r\n." % (msg.size, msg.size) 28 | 29 | 30 | def handleTop(data, msg): 31 | cmd, num, lines = data.split() 32 | assert num == "1", "unknown message number: %s" % num 33 | lines = int(lines) 34 | text = msg.top + "\r\n\r\n" + "\r\n".join(msg.bot[:lines]) 35 | return "+OK top of message follows\r\n%s\r\n." % text 36 | 37 | 38 | def handleRetr(data, msg): 39 | return "+OK %i octets\r\n%s\r\n." % (msg.size, msg.data) 40 | 41 | 42 | def handleDele(data, msg): 43 | return "+OK message 1 deleted" 44 | 45 | 46 | def handleNoop(data, msg): 47 | return "+OK" 48 | 49 | 50 | def handleQuit(data, msg): 51 | return "+OK POP3 server signing off" 52 | 53 | 54 | dispatch = dict( 55 | USER=handleUser, 56 | PASS=handlePass, 57 | STAT=handleStat, 58 | LIST=handleList, 59 | TOP=handleTop, 60 | RETR=handleRetr, 61 | DELE=handleDele, 62 | NOOP=handleNoop, 63 | QUIT=handleQuit, 64 | ) 65 | 66 | 67 | class TCPHandler(socketserver.BaseRequestHandler): 68 | def handle(self): 69 | self.request.sendall(b'+OK SpamCan test server ready\r\n') 70 | while True: 71 | self.data = self.request.recv(1024).strip() 72 | if self.data: 73 | command = self.data.decode().split(" ", 1)[0] 74 | cmd = dispatch[command] 75 | self.request.sendall(bytes(cmd(self.data, Message()), 'utf-8') + b'\r\n') 76 | if command == "QUIT": 77 | break 78 | else: 79 | break 80 | 81 | 82 | def pop_server(port=0): 83 | """ 84 | Returns a new instance of SocketServer.TCPServer. If port == 0 a ephemeral port will be assigned. 85 | """ 86 | return socketserver.TCPServer(("localhost", port), TCPHandler) 87 | 88 | 89 | if __name__ == "__main__": 90 | server = pop_server(8088) 91 | server.serve_forever() 92 | --------------------------------------------------------------------------------