initial commit
This commit is contained in:
commit
f895fe5348
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
venv/*
|
||||||
|
helloworld/__pycache__/*
|
||||||
|
testdata/*
|
||||||
|
.vscode/*
|
7
CHANGELOG.md
Normal file
7
CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
This project uses Semantic Versioning
|
||||||
|
|
||||||
|
## 0.0.0
|
||||||
|
|
||||||
|
Initial release
|
66
imgmine/__init__.py
Normal file
66
imgmine/__init__.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from gevent import monkey
|
||||||
|
from gevent import sleep
|
||||||
|
monkey.patch_all()
|
||||||
|
from threading import Thread
|
||||||
|
from os import remove, mkdir, path
|
||||||
|
from shutil import rmtree
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from bottle import route, run
|
||||||
|
from bottle import static_file
|
||||||
|
from bottle import SimpleTemplate
|
||||||
|
|
||||||
|
from .get import get
|
||||||
|
from .config import IMAGE_CACHE, SINGLE_IMAGE_DELETE_AFTER_SECS, ALBUM_DELETE_AFTER_SECS, template_dir
|
||||||
|
|
||||||
|
|
||||||
|
def album(id):
|
||||||
|
req_id = str(uuid4())
|
||||||
|
req = IMAGE_CACHE
|
||||||
|
|
||||||
|
get("/a/" + id, req)
|
||||||
|
|
||||||
|
imgs = glob(req + "*")
|
||||||
|
for c, img in enumerate(imgs):
|
||||||
|
imgs[c] = img.replace(IMAGE_CACHE, '/')
|
||||||
|
with open(f'{template_dir}gallery.html', 'r') as img_view:
|
||||||
|
tpl = SimpleTemplate(img_view)
|
||||||
|
return tpl.render(imgs=imgs)
|
||||||
|
|
||||||
|
@route('/')
|
||||||
|
@route('')
|
||||||
|
def home():
|
||||||
|
return static_file("index.html", root=template_dir)
|
||||||
|
|
||||||
|
@route('/static/<file>')
|
||||||
|
def static(file=''):
|
||||||
|
return static_file(file, root=template_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@route('/gallery/<id>')
|
||||||
|
def gallery(id=''):
|
||||||
|
return album(id)
|
||||||
|
|
||||||
|
@route('/a/<id>')
|
||||||
|
def gallery(id=''):
|
||||||
|
return album(id)
|
||||||
|
|
||||||
|
|
||||||
|
@route('/<img>')
|
||||||
|
def hello(img=''):
|
||||||
|
|
||||||
|
if not path.exists(IMAGE_CACHE + img):
|
||||||
|
get(img, IMAGE_CACHE)
|
||||||
|
return static_file(img, root=IMAGE_CACHE)
|
||||||
|
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
try:
|
||||||
|
rmtree(IMAGE_CACHE)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
mkdir(IMAGE_CACHE)
|
||||||
|
|
||||||
|
run(server='gevent')
|
BIN
imgmine/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
imgmine/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
imgmine/__pycache__/config.cpython-39.pyc
Normal file
BIN
imgmine/__pycache__/config.cpython-39.pyc
Normal file
Binary file not shown.
BIN
imgmine/__pycache__/get.cpython-39.pyc
Normal file
BIN
imgmine/__pycache__/get.cpython-39.pyc
Normal file
Binary file not shown.
4
imgmine/config.py
Normal file
4
imgmine/config.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
IMAGE_CACHE = '/tmp/imgmine-imgur-images/'
|
||||||
|
SINGLE_IMAGE_DELETE_AFTER_SECS = 60
|
||||||
|
ALBUM_DELETE_AFTER_SECS = 120
|
||||||
|
template_dir = 'imgmine/web/'
|
64
imgmine/get.py
Normal file
64
imgmine/get.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import sys
|
||||||
|
from shutil import rmtree
|
||||||
|
from os import remove
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import bs4
|
||||||
|
from gevent import sleep
|
||||||
|
|
||||||
|
from .config import SINGLE_IMAGE_DELETE_AFTER_SECS, ALBUM_DELETE_AFTER_SECS
|
||||||
|
|
||||||
|
def delete_file(path):
|
||||||
|
sleep(SINGLE_IMAGE_DELETE_AFTER_SECS)
|
||||||
|
print('Erasing', path)
|
||||||
|
try:
|
||||||
|
remove(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def error(msg):
|
||||||
|
sys.stderr.write(msg + "\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def get(url: str, write_dir: str, delete=True):
|
||||||
|
if not url.startswith('https://imgur.com/'):
|
||||||
|
url = 'https://imgur.com/' + url
|
||||||
|
found_url = ''
|
||||||
|
|
||||||
|
print('it not album', url)
|
||||||
|
album = False
|
||||||
|
if "gallery" in url:
|
||||||
|
url = url.replace("gallery", "a")
|
||||||
|
print('it album')
|
||||||
|
if "/a/" in url:
|
||||||
|
print('it album')
|
||||||
|
album = True
|
||||||
|
if not url.endswith("blog"):
|
||||||
|
url += "/layout/blog"
|
||||||
|
|
||||||
|
if not album:
|
||||||
|
print('getting img', url)
|
||||||
|
url = 'https://i.imgur.com/' + url.rsplit('/', 1)[-1].replace('jpeg', '').replace('jpg', '')
|
||||||
|
with open(f'{write_dir}/{url[-12:]}', 'wb') as img:
|
||||||
|
img.write(requests.get(url).content)
|
||||||
|
if delete:
|
||||||
|
Thread(target=delete_file, args=[f"{write_dir}/{url[-12:]}"]).start()
|
||||||
|
else:
|
||||||
|
print('Detecting album/gallery images', url)
|
||||||
|
soup = bs4.BeautifulSoup(requests.get(url).text, 'html.parser')
|
||||||
|
for count, el in enumerate(soup.select('.post-image meta[itemprop="contentUrl"]'), start=1):
|
||||||
|
try:
|
||||||
|
found_url = "https:" + el['content']
|
||||||
|
except KeyError:
|
||||||
|
error("Could not obtain url for detected image")
|
||||||
|
continue
|
||||||
|
print(f"Downloading image {count}: {found_url}")
|
||||||
|
|
||||||
|
print("Writing image", f"{write_dir}{found_url[-11:]}")
|
||||||
|
with open(f"{write_dir}{found_url[-11:]}", "wb") as f:
|
||||||
|
f.write(requests.get(found_url).content)
|
||||||
|
if delete:
|
||||||
|
Thread(target=delete_file, args=[f"{write_dir}{found_url[-11:]}"]).start()
|
||||||
|
|
25
imgmine/web/gallery.html
Normal file
25
imgmine/web/gallery.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charself="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>imgmine - minimal & private imgur proxy</title>
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📷</text></svg>">
|
||||||
|
<link rel="stylesheet" href="/static/theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
% for img in imgs:
|
||||||
|
<img src="{{img}}" loading="lazy">
|
||||||
|
<br>
|
||||||
|
% end
|
||||||
|
<footer>
|
||||||
|
<small>
|
||||||
|
This website does not claim ownership of any media.
|
||||||
|
<br>This service simply acts as a proxy to Imgur.com and does not store images aside from a temporary cache.
|
||||||
|
<br>Abusive images should be reported to imgur. This website does not create new images/comments.
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
41
imgmine/web/index.html
Normal file
41
imgmine/web/index.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charself="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>imgmine - minimal & private imgur proxy</title>
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📷</text></svg>">
|
||||||
|
<link rel="stylesheet" href="/static/theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
imgmine
|
||||||
|
</h1>
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
A minimalist read-only Imgur proxy insipired by software like <a href="https://invidio.us/">Invidious</a>, <a href="https://nitter.net/">Nitter</a>, and <a href="https://bibliogram.art/">Bibliogram</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This project was started because <a href="https://chaoswebs.net/">Kevin Froman</a> got fed up with Imgur breaking with Tor because of cow-dung JS.
|
||||||
|
</p>
|
||||||
|
<h2>Feature roadmap</h2>
|
||||||
|
<p>Features are in order of priority of most to least</p>
|
||||||
|
<ul>
|
||||||
|
<li><input type="checkbox" checked> Image proxying</li>
|
||||||
|
<li><input type="checkbox" checked> Gallery/album proxying</li>
|
||||||
|
<li><input type="checkbox"> Proper order of gallery posts</li>
|
||||||
|
<li><input type="checkbox"> Image description and author info</li>
|
||||||
|
<li><input type="checkbox"> GIFs/videos</li>
|
||||||
|
<li><input type="checkbox"> Comments... maybe</li>
|
||||||
|
</ul>
|
||||||
|
<footer>
|
||||||
|
<small>
|
||||||
|
This website does not claim ownership of any media.
|
||||||
|
<br>This service simply acts as a proxy to Imgur.com and does not store images aside from a temporary cache.
|
||||||
|
<br>Abusive images should be reported to imgur. This website does not create new images/comments.
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
14
imgmine/web/theme.css
Normal file
14
imgmine/web/theme.css
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
body{
|
||||||
|
background-color: rgba(8, 6, 37, 0.781);
|
||||||
|
color: white;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin-left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul{
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
13
run_tests.sh
Executable file
13
run_tests.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
ran=0
|
||||||
|
SECONDS=0 ;
|
||||||
|
close () {
|
||||||
|
exit 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
for f in tests/*.py; do
|
||||||
|
python3 "$f" || close # if needed
|
||||||
|
let "ran++"
|
||||||
|
done
|
||||||
|
echo "ran $ran test files successfully in $SECONDS seconds"
|
||||||
|
rm -f *.dat
|
7
tests/test.py
Normal file
7
tests/test.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import unittest
|
||||||
|
class TestBasic(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user