Compare commits

..

14 commits

Author SHA1 Message Date
Nyx
6b2345a726 Merge pull request 'Move upload to a seperate page' (#1) from seperate-upload-page into main
Reviewed-on: #1
2025-02-02 20:57:27 -06:00
nyx
d7a54202b3 Add a back button 2025-02-02 20:56:54 -06:00
nyx
d9ceca8b77 Add upload button 2025-02-02 20:54:43 -06:00
nyx
d348ba7a2e Fix formatting 2025-02-02 20:52:00 -06:00
nyx
6f49244dbd Split off upload 2025-02-02 20:51:25 -06:00
nyx
d50b0bd8de fix, remove readme that wasn't supposed to be there 2025-02-01 16:30:45 -06:00
nyx
d65bcb3e4a config file 2025-02-01 16:25:53 -06:00
nyx
b94a3d2ae4 A few pylint fixes 2025-02-01 15:51:58 -06:00
nyx
0dfb1fcd4e colors 2025-02-01 15:39:19 -06:00
nyx
d5e45f4838 more docs stuff 2025-02-01 15:39:11 -06:00
nyx
ff5bf4f5d3 css 2025-02-01 15:06:30 -06:00
nyx
236619cd42 caddy 2025-02-01 14:30:00 -06:00
nyx
39ecd971dc typo 2025-02-01 14:28:50 -06:00
nyx
c9a59162c0 read me!!! 2025-02-01 14:27:59 -06:00
13 changed files with 210 additions and 141 deletions

2
.gitignore vendored
View file

@ -168,3 +168,5 @@ cython_debug/
instance/
images/
config.json

66
README.md Normal file
View file

@ -0,0 +1,66 @@
# imag
> simple image board, intended for a quotebook of screenshots
the imag image board is made to be simple, though separated, so you
could easily add or remove features, update them, etc
example instance: https://quotes.everypizza.im/
# licensing
this program is under the Strongest Public License, mostly as a joke.
this one, Nyx Tutt, gives you full permission to ignore Clause ⑨.
# prerequisites
- tesseract: https://github.com/tesseract-ocr
- tesseract data
# bot
there's a matrix bot at n/quotes-bot and a very WIP rewrite in python at n/quotes-bot-python.s
# docs & running
see the [doc directory](/doc) for documentation, it also has an example nginx and caddy config,
and you can also run the app using [./scripts/run.sh](./scripts/run.sh) to match that config - but don't run it using
the run.sh as the first run.
running with gunicorn (run.sh) is for production use, for master key generation (first run), please
run it in dev mode after making the config:
make the config:
```sh
cp src/imag/config.sample.json src/imag/config.json && nano config.json
```
run it:
```sh
python3 src/main.py
```
(make sure to save the key!)
and only then with gunicorn
if you already ran it in production and don't know where the key is, run the following command:
```sh
rm -rf src/instance
```
and then run it in debug
### step-by-step
this comes from an email the original creator got from a user:
1. clone the repository: `git clone https://git.everypizza.im/n/imag`
2. make sure you have virtualenv installed (either through python-virtualenv / python3-virtualenv / py3-virtualenv packages, or by pip - `python3 -m pip install --user --break-system-packages --upgrade virtualenv`)
3. ensure you have sqlite3 and memcached installed: `apt install sqlite3 memcached`
4. create a new virtual environment: `python3 -m virtualenv venv && source venv/bin/activate`
5. install the dependencies in the environment: `pip install -r requirements.txt`
6. configure the instance: `cp src/imag/config.sample.json src/imag/config.json && nano src/imag/config.json`
6. run the app by either running `scripts/run.sh` or by manually starting memcached and running `src/main.py` with gunicorn (i assume you're reverse proxying it anyway)

View file

@ -1,64 +0,0 @@
# imag
> simple and hackable suckless image board
the imag image board is made to be simple, though separated, so you
could easily add or remove features, update them, etc
this is not sole software, it is suckless ( ? ) software, which you are meant
to hack yourself,, you can, of course, use the default settings and whatnot,
but it is highly encouraged to make forks and hack it yourself :)
example instance : https://quotes.ari.lt/
~~i hate github versioning so much~~
# licensing
you can distribute, modify, share, redistribute, etc etc etc with no credit or anything,
this project is released under unlicense and i give away all my rights to this project :)
license : unlicense
# prerequisites
- tesseract : <https://github.com/tesseract-ocr>
- tesseract english data ( or whatever other languages ) : <https://github.com/tesseract-ocr/tessdata/blob/main/eng.traineddata>
# bot
i made a matrix bot to integrate well with this, it is open source : <https://ari.lt/gh/quotes-bot>, mainly for purpose of posting quotes
# docs & running
see the [doc directory](/dov) for documentation, it also has an example nginx config,
and you can also run the app using [./scripts/run.sh](./scripts/run.sh) to match that config :) - but don't run it using
the run.sh as the first run if you ever want to post on it lol
running with gunicorn ( run.sh ) is for production use, for master key generation ( first run ), please
run it in dev mode :
```sh
python3 src/main.py
```
and only then with gunicorn :)
if you already ran it in production and don't know where the key is, run the following command :
```sh
rm -rf src/images src/instance
```
and then run it in debug
### step-by-step
this comes from an email i got from a user :
1. clone the repository : `git clone https://ari.lt/gh/imag && cd imag`
2. make sure you have virtualenv installed ( either through python-virtualenv / python3-virtualenv / py3-virtualenv packages, or by pip - `python3 -m pip install --user --break-system-packages --upgrade virtualenv`
3. ensure you have sqlite3 and memcached installed : `apt install sqlite3 memcached`
4. create a new virtual environment : `python3 -m virtualenv venv && source venv/bin/activate`
5. install the dependencies in the environment : `pip install -r requirements.txt`
6. run the app by either running `scripts/run.sh` or by manually starting memcached and running `src/main.py` with gunicorn ( i assume you're reverse proxying it anyway )

4
doc/Caddyfile Normal file
View file

@ -0,0 +1,4 @@
https://quotes.example.com {
tls alice@example.com
reverse_proxy http://127.0.0.1:19721
}

View file

@ -1,40 +0,0 @@
# imag: weird image board thingy
> note: this is a fork of a project that doesn't exist anymore. this document is mostly paraphrased from README.original.md.
this is intended to be hacked upon! please do make forks and tell the creator what you've made. our "showcase" is at [quotes.everypizza.im](https://quotes.everypizza.im/).
# prerequisites
- tesseract
- tesseract data
- python
# bot
there's a very small bot at [n/quotes-bot](https://git.everypizza.im/n/quotes-bot). copied from the original with some very small changes.
# running
first run:
```sh
python3 src/main.py
```
this generates a key. keep it, you need it to add new images!
afterwards:
```sh
rm -rf src/images src/instance
```
### step-by-step
> these are directly from the original docs.
this comes from an email i got from a user :
1. clone the repository : `git clone https://ari.lt/gh/imag && cd imag`
2. make sure you have virtualenv installed ( either through python-virtualenv / python3-virtualenv / py3-virtualenv packages, or by pip - `python3 -m pip install --user --break-system-packages --upgrade virtualenv`
3. ensure you have sqlite3 and memcached installed : `apt install sqlite3 memcached`
4. create a new virtual environment : `python3 -m virtualenv venv && source venv/bin/activate`
5. install the dependencies in the environment : `pip install -r requirements.txt`
6. run the app by either running `scripts/run.sh` or by manually starting memcached and running `src/main.py` with gunicorn ( i assume you're reverse proxying it anyway )

View file

@ -4,12 +4,19 @@
import secrets
import typing as t
from os import makedirs
from os import makedirs, path
import json
import flask
from werkzeug.middleware.proxy_fix import ProxyFix
from . import const
from . import const, models
from .api import api
from .views import views
configLocation = path.dirname(path.abspath(__file__))
with open(configLocation + '/config.json', 'r') as f:
config = json.load(f)
message = config["message"]
__version__: str = "3.1.2-nyx"
@ -28,9 +35,6 @@ def create_app(db: str = "sqlite:///imag.db") -> t.Tuple[flask.Flask, t.Optional
app.config["PREFERRED_URL_SCHEME"] = "https"
app.config["SECRET_KEY"] = secrets.SystemRandom().randbytes(1024 * 16)
from . import models
models.limiter.init_app(app)
models.db.init_app(app)
@ -55,6 +59,7 @@ def create_app(db: str = "sqlite:///imag.db") -> t.Tuple[flask.Flask, t.Optional
"desc_len": const.DESC_LEN,
"key_len": const.KEY_LEN,
"imagv": __version__,
"imagmessage": message
}
@app.after_request
@ -77,9 +82,6 @@ def create_app(db: str = "sqlite:///imag.db") -> t.Tuple[flask.Flask, t.Optional
return response
from .api import api
from .views import views
app.register_blueprint(api, url_prefix="/api/")
app.register_blueprint(views, url_prefix="/")

View file

@ -0,0 +1,3 @@
{
"message": "meow"
}

View file

@ -10,6 +10,7 @@ from .util import make_api
class Bp(Blueprint):
"""app routing helper"""
def get(self, rule: str, **kwargs: Any) -> Any:
"""wrapper for GET"""
return self.route(rule=rule, methods=("GET",), **kwargs)

View file

@ -7,13 +7,17 @@
font-family: sans-serif;
}
h1 {
color: #fdccd4 !important;
}
* {
color: whitesmoke;
color: whitesmoke
}
body {
margin: auto;
max-width: 1100px;
max-width: 40em;
padding: 2em;
height: 100%
}
@ -110,3 +114,7 @@ hr.thin{
width:50%;
}
hr.tiny {
width:25%;
}

View file

@ -60,6 +60,14 @@
<a href="https://matrix.to/#/@quotes-python:everypizza.im">@quotes-python:everypizza.im</a>
</small>
</center>
<hr class="tiny">
<center>
<small>
Board message: {{ imagmessage }}
</small>
</center>
<hr class="thin">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
@ -85,28 +93,9 @@
{% if q is not defined and "s=newest" not in request.url %} <a href="/?s=newest">Sort by newest</a> {% endif %}
<details>
<summary>Or post an image</summary>
<form method="POST" enctype="multipart/form-data">
<label for="desc">Image description:</label>
<input type="text" name="desc" id="desc" placeholder="description" autocomplete="off" maxlength="{{ desc_len }}" />
<br />
<label for="key">Access key:</label>
<input type="password" name="key" id="key" placeholder="key" maxlength="{{ key_len }}" required />
<br />
<label for="image">Image:</label>
<input type="file" accept="image/*" name="image" id="image" placeholder="the image" required />
<br />
<button type="post">Post</button>
</form>
</details>
<form action="upload">
<input type="submit" value="Upload an image" />
</form>
</div>
<br />

View file

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if title is defined %}
<title>Imag - {{ title | escape }}</title>
<meta name="description" content="The Imag image board | {{ title | escape }}" />
{% else %}
<title>Imag</title>
<meta name="description" content="The Imag image board" />
{% endif %}
<meta
name="keywords"
content="imageboard, image board, image, image hosting"
/>
<meta
name="robots"
content="follow, index, max-snippet:-1, max-video-preview:-1, max-image-preview:large"
/>
<meta property="og:type" content="website" />
<meta name="color-scheme" content="dark" />
<meta name="theme-color" content="black" />
<meta name="license" content="WTFPL" />
<!-- preloads the css ( technically you can replace it with a style tag -->
<link
href="{{ url_for("static", filename="index.css") }}"
rel="preload"
referrerpolicy="no-referrer"
type="text/css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript>
<link
href="{{ url_for("static", filename="index.css") }}"
rel="stylesheet"
referrerpolicy="no-referrer"
type="text/css"
/>
</noscript>
<script src="{{ url_for("static", filename="index.js") }}" defer></script>
</head>
<body>
<h1>The <a href="https://git.everypizza.im/n/imag/src/branch/main">Imag</a> image board ({{ imagv }}). | upload</h1>
<center>
Matrix chat: <a href="https://matrix.to/#/#quotes:everypizza.im">#quotes:everypizza.im</a> |
Matrix bot: <a href="https://matrix.to/#/@quotes:everypizza.im">@quotes:everypizza.im</a> <br>
<hr class="thin">
<small>
There's a very WIP complete rewrite of the bot in Python currently:
<a href="https://matrix.to/#/@quotes-python:everypizza.im">@quotes-python:everypizza.im</a>
</small>
</center>
<hr class="tiny">
<center>
<small>
Board message: {{ imagmessage }}
</small>
</center>
<hr class="thin">
<form method="POST" enctype="multipart/form-data">
<label for="desc">Image description:</label>
<input type="text" name="desc" id="desc" placeholder="description" autocomplete="off" maxlength="{{ desc_len }}" />
<br />
<label for="key">Access key:</label>
<input type="password" name="key" id="key" placeholder="key" maxlength="{{ key_len }}" required />
<br />
<label for="image">Image:</label>
<input type="file" accept="image/*" name="image" id="image" placeholder="the image" required />
<br />
<button onclick="history.back()">Back</button>
<button type="post">Post</button>
</form>
</div>
</body>
</html>

View file

@ -48,8 +48,7 @@ def with_access(
if access and (access.access_level.value >= access_level.value): # type: ignore
return fn(*args, **kwargs) # type: ignore
else:
flask.abort(403)
flask.abort(403)
return decorator

View file

@ -25,8 +25,15 @@ def index() -> str:
images=models.Image.query.order_by((models.Image.created if flask.request.args.get("s") == "newest" else models.Image.score).desc()).all(), # type: ignore
)
@views.get("/upload")
def upload() -> str:
"""upload page"""
return flask.render_template(
"upload.j2",
images=models.Image.query.order_by((models.Image.created if flask.request.args.get("s") == "newest" else models.Image.score).desc()).all(), # type: ignore
)
@views.post("/")
@views.post("/upload")
@util.with_access(models.AccessLevel.write)
def post_image() -> Response:
"""post image"""
@ -82,8 +89,10 @@ def image(iid: int) -> flask.Response:
with open(os.path.join(const.IMAGE_DIR, str(iid)), "rb") as fp:
file: bytes = fp.read()
return flask.Response(file, mimetype=magic.from_buffer(file, mime=True)) # type: ignore
except Exception:
except FileNotFoundError:
flask.abort(404)
except Exception:
flask.abort(500)
@views.post("/edit/<int:iid>")