commit
f895fe5348
@ -0,0 +1,4 @@ |
||||
venv/* |
||||
helloworld/__pycache__/* |
||||
testdata/* |
||||
.vscode/* |
@ -0,0 +1,7 @@ |
||||
# Changelog |
||||
|
||||
This project uses Semantic Versioning |
||||
|
||||
## 0.0.0 |
||||
|
||||
Initial release |
@ -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') |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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/' |
@ -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() |
||||
|
@ -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> |
@ -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> |
@ -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; |
||||
} |
@ -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 |
@ -0,0 +1,7 @@ |
||||
import unittest |
||||
class TestBasic(unittest.TestCase): |
||||
|
||||
def test_basic(self): |
||||
self.assertTrue(True) |
||||
|
||||
unittest.main() |
Loading…
Reference in new issue