A => .gitignore +202 -0
@@ 1,202 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# Coverage reports
+htmlcov/
+.coverage
+.coverage.*
+*,cover
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser<
\ No newline at end of file
A => .idea/.gitignore +8 -0
@@ 1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
A => .idea/atchannel.iml +21 -0
@@ 1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+ <component name="Flask">
+ <option name="enabled" value="true" />
+ </component>
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/venv" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+ <component name="TemplatesService">
+ <option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
+ <option name="TEMPLATE_FOLDERS">
+ <list>
+ <option value="$MODULE_DIR$/atch/templates" />
+ </list>
+ </option>
+ </component>
+</module><
\ No newline at end of file
A => .idea/inspectionProfiles/Project_Default.xml +13 -0
@@ 1,13 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0">
+ <option name="myName" value="Project Default" />
+ <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+ <option name="ignoredErrors">
+ <list>
+ <option value="N802" />
+ <option value="N803" />
+ </list>
+ </option>
+ </inspection_tool>
+ </profile>
+</component><
\ No newline at end of file
A => .idea/inspectionProfiles/profiles_settings.xml +6 -0
@@ 1,6 @@
+<component name="InspectionProjectProfileManager">
+ <settings>
+ <option name="USE_PROJECT_PROFILE" value="false" />
+ <version value="1.0" />
+ </settings>
+</component><
\ No newline at end of file
A => .idea/misc.xml +4 -0
@@ 1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (atchannel)" project-jdk-type="Python SDK" />
+</project><
\ No newline at end of file
A => .idea/modules.xml +8 -0
@@ 1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/atchannel.iml" filepath="$PROJECT_DIR$/.idea/atchannel.iml" />
+ </modules>
+ </component>
+</project><
\ No newline at end of file
A => .idea/vcs.xml +6 -0
@@ 1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project><
\ No newline at end of file
A => atch/__init__.py +27 -0
@@ 1,27 @@
+import os
+import db
+from flask import Flask, g
+
+
+def create_app(test_config=None):
+ app = Flask(__name__, instance_relative_config=True)
+ app['SECRET_KEY'] = "nya"
+ app['DATABASE'] = os.path.join(app.instance_path, 'atch.sqlite')
+
+ if test_config is None:
+ app.config.from_pyfile('config.py', silent=True)
+ else:
+ app.config.from_mapping(test_config)
+
+ try:
+ os.makedirs(app.instance_path)
+ except OSError:
+ pass
+
+ db.init_db()
+
+ g.boards = db.get_db().execute('SELECT uri FROM boards').fetchall()
+
+
+def get_boards():
+ pass
A => atch/board.py +46 -0
@@ 1,46 @@
+from flask import Blueprint, flash, g, redirect, render_template, request, session, url_for, abort
+from atch.db import get_db
+from hashlib import sha1
+
+bp = Blueprint('board', __name__)
+
+
+@bp.route('/<string:uri>/<int:page>')
+def browse_board(uri, page):
+ db = get_db()
+ threads = db.execute(
+ 'SELECT thread, thread_pos, board, created, op, email, title, body '
+ 'FROM posts WHERE board == ? '
+ 'ORDER BY created DESC',
+ uri
+ ).fetchmany(5 + 5 * page)
+
+ return render_template('board.html', threads=threads)
+
+
+@bp.route('/<string:uri>/new_thread', methods=['GET', 'POST'])
+def new_thread(uri):
+ if request.method == 'POST':
+ op = ""
+ if request.form['op']:
+ op = request.form['op']
+ if '#' in op: # process tripcode
+ go = op.index('#')
+ trip = sha1(op[go:].encode('utf-8'))
+ op = op[:go]
+ op = op + "◆" + trip.hexdigest()[:10]
+ else:
+ op = "Anonymous"
+ email = request.form['email']
+ title = request.form['title']
+ body = request.form['body']
+ db = get_db()
+ thread_number = db.execute('SELECT new_thread_number, uri FROM boards WHERE uri == ?', uri)
+ db.execute('UPDATE boards SET new_thread_number = new_thread_number + 1 WHERE uri == ?', uri)
+ db.execute(
+ 'INSERT INTO posts (thread, thread_pos, board, op, email, title, body) VALUES (?, 0, ?, ?, ?, ?, ?)',
+ [thread_number[0], uri, op, email, title, body]
+ )
+ db.commit()
+
+ abort(403)
A => atch/db.py +38 -0
@@ 1,38 @@
+import sqlite3
+import click
+from flask import current_app, g
+from flask.cli import with_appcontext
+
+
+def get_db():
+ if 'db' not in g:
+ g.db = sqlite3.connect(
+ current_app.config['DATABASE'],
+ detect_types=sqlite3.PARSE_DECLTYPES
+ )
+ g.db.row_factory = sqlite3.Row
+
+ return g.db
+
+
+def close_db(e=None):
+ db = g.pop('db', None)
+
+ if db is not None:
+ db.close()
+
+
+def init_db():
+ db = get_db()
+
+ with current_app.open_resource('schema.sql') as f:
+ db.executescript(f.read().decode('utf8'))
+
+
+@click.command('add_board')
+@click.argument('uri')
+@click.argument('name')
+@with_appcontext
+def add_board(uri, name):
+ get_db().execute('INSERT INTO boards VALUES (?, ?)', [uri, name])
+ click.echo('Added board /' + uri + "/ - " + name)<
\ No newline at end of file
A => atch/schema.sql +20 -0
@@ 1,20 @@
+CREATE TABLE IF NOT EXISTS boards (
+ uri VARCHAR(7) NOT NULL PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ new_thread_number INTEGER NOT NULL DEFAULT 0
+);
+
+CREATE TABLE IF NOT EXISTS posts (
+ thread INTEGER NOT NULL,
+ thread_pos INTEGER NOT NULL DEFAULT 0,
+ board VARCHAR(7) NOT NULL,
+ created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ bump_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ op VARCHAR(50) NOT NULL,
+ email VARCHAR (50),
+ title VARCHAR(50),
+ body TEXT NOT NULL,
+
+ PRIMARY KEY (thread, thread_pos, board),
+ FOREIGN KEY (board) REFERENCES boards(uri)
+);<
\ No newline at end of file
A => atch/static/css/style.css +0 -0
A => atch/static/js/reply.js +0 -0
A => atch/templates/base.html +26 -0
@@ 1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>{% block title %}{% endblock %} - @channel</title>
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
+</head>
+<body>
+<nav>
+ <h1>Boards </h1>
+ <ul>[
+ {% for board in g.boards %}
+ <li><a href="">{{ board.uri }}</a> </li>
+ </ul>]
+</nav>
+<section class="content">
+ <header>
+ {% block header %}{% endblock %}
+ </header>
+ {% for message in get_flashed_messages() %}
+ <div class="flash">{{ message }}</div>
+ {% endfor %}
+ {% block content %}{% endblock %}
+</section>
+</body>
+</html><
\ No newline at end of file
A => atch/templates/board.html +10 -0
@@ 1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Title</title>
+</head>
+<body>
+
+</body>
+</html><
\ No newline at end of file
A => atch/templates/thread.html +10 -0
@@ 1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Title</title>
+</head>
+<body>
+
+</body>
+</html><
\ No newline at end of file
A => setup.py +12 -0
@@ 1,12 @@
+from setuptools import setup, find_packages
+
+
+setup(
+ name='atchannel',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=[
+ 'flask', 'werkzeug', 'click'
+ ],
+)<
\ No newline at end of file