From f895fe5348e61daf4d4801623e78b6830174b2d3 Mon Sep 17 00:00:00 2001 From: Kevin F Date: Sun, 3 Oct 2021 00:56:33 +0000 Subject: [PATCH] initial commit --- .gitignore | 4 ++ CHANGELOG.md | 7 +++ Makefile | 2 + imgmine/__init__.py | 66 ++++++++++++++++++++ imgmine/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 2096 bytes imgmine/__pycache__/config.cpython-39.pyc | Bin 0 -> 282 bytes imgmine/__pycache__/get.cpython-39.pyc | Bin 0 -> 2114 bytes imgmine/config.py | 4 ++ imgmine/get.py | 64 +++++++++++++++++++ imgmine/web/gallery.html | 25 ++++++++ imgmine/web/index.html | 41 ++++++++++++ imgmine/web/theme.css | 14 +++++ run.py | 3 + run_tests.sh | 13 ++++ tests/test.py | 7 +++ 15 files changed, 250 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Makefile create mode 100644 imgmine/__init__.py create mode 100644 imgmine/__pycache__/__init__.cpython-39.pyc create mode 100644 imgmine/__pycache__/config.cpython-39.pyc create mode 100644 imgmine/__pycache__/get.cpython-39.pyc create mode 100644 imgmine/config.py create mode 100644 imgmine/get.py create mode 100644 imgmine/web/gallery.html create mode 100644 imgmine/web/index.html create mode 100644 imgmine/web/theme.css create mode 100644 run.py create mode 100755 run_tests.sh create mode 100644 tests/test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abcb1cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +venv/* +helloworld/__pycache__/* +testdata/* +.vscode/* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7a79fdb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +This project uses Semantic Versioning + +## 0.0.0 + +Initial release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..110e50a --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + ./run_tests.sh diff --git a/imgmine/__init__.py b/imgmine/__init__.py new file mode 100644 index 0000000..c819c8f --- /dev/null +++ b/imgmine/__init__.py @@ -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/') +def static(file=''): + return static_file(file, root=template_dir) + + +@route('/gallery/') +def gallery(id=''): + return album(id) + +@route('/a/') +def gallery(id=''): + return album(id) + + +@route('/') +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') \ No newline at end of file diff --git a/imgmine/__pycache__/__init__.cpython-39.pyc b/imgmine/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f565fbaddcac252910c62903b2285727e7375932 GIT binary patch literal 2096 zcma)7OK%%D5GJ_~N!Ch{AF-8}Ezx2ji;T3%a@XNf zZY;f6a|u_|I%K2%DK0kIz!oxYv<5i@Z)ee%NY*8;dUyODFV;ke}3vb^$7VJ z2PYpF2H!%a!OHVAT!P=k@U6mhFF~wyUOF9mEG7^1zu1^UQ{JsvN|uG zQe|GYvL6R3;DMD3aYa>m)yl>AoT~AfnXT!QfopRw{3$g_MF)LaJ5 z0%&fCy0~C9S3q+SG&jYi6PnAQxh1ZMMQgJtu8Jk_b5&g1B#q^_n7;t%bQ`}N3?lJmgXNx12S(lkn3jOktSYyB6t@9uje@3n83aNe+yGji9+ z^HozY#XpH~|6;-$vp?$lzA24-__pvEDCojFb|^_|cS!bP6)1@B&(KDNr&A+G_^&7& zNufP-nw@4&Z@;6>VbJTuvDAAjeWT)>YRHotsb1D7=T2s{#)8n(2sMNfKwgr`KuHa$ z%?nyWj=M5<(;b=UIkc$)a5&OLNZlyrED|_+9zyRxuj{x5?dA^n;YEOOHzVj04uzH^D$J1K`2n_osdO zlf`d=$;2zuw?Q6OnujObT#Lk6rx>8!J5tq<5ik%O()Ph5Wzb!LiPO#shijsTBX27p z4-3Gk|L;eCf@^<@@EO9=F<=@su88f>!_bGNRqyN`juVGTGUPSX7gp9An$?X1X*{Yh zkR}P$AA@1T(*@{qU%rSklbs+p9&Ur`tI(YaDfjv^j?*t-Ci4M^Qw6H&RgliG>bUm5 zg4tnUs~>(5k4ZA)LL_^OM|GL7Rbm{t8(`MB0=Z{@C6<4=-;=vCF%8OnsDfQ6B~Rnh zHr{;t9#Em3ntSOW5iPA#tuZc*Og<6&5)Lgj1E7gE;bf_=L7kx~p^OIb`n1S2?ye2{ zzXAP+yCr=G<}`9gI{>@{H+8ok!WEL2jJ?gHq^I#*a+YQq{-IPopz03HG-G;3-yiDxX!~JfWJc)Xf^TKM^vk3Oeu~m-6X1Q+(WDm#g s>4W(wuBlWEpd7IRv(EuA4@GEmm=PAR+N7s-r&#eJLxBU$J<%8b1=8K7tN;K2 literal 0 HcmV?d00001 diff --git a/imgmine/__pycache__/config.cpython-39.pyc b/imgmine/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb6652bd709d8179afe8317e73f3bdf56354cfb5 GIT binary patch literal 282 zcmYe~<>g`kg5w(^61#x(V-N=!FakLaKwK;WBvKfn7*ZIc7*m*{m{ORdm{VAS88lg| zr1VR23-mK{({nTPQgwlJX%Uc2OiwM=e`y2M_Ob#HBH2+~W51b#!-) zcXo94aJ?lL?CIz3;~EbUb8+=?4RMWkbPI6}iVt>m4!$Ms=;IXX8;>c$Q<9onkds)F z8lRF`w34BS9cUMr_@$zsk)NBYUs{}61almmPR`Fu%S_iRsJz8tlbfGXnv-e=@@X*} JkYHg1K>++NOO^lt literal 0 HcmV?d00001 diff --git a/imgmine/__pycache__/get.cpython-39.pyc b/imgmine/__pycache__/get.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ee801be49c1b99b962e66d01ac36b9317154f0a GIT binary patch literal 2114 zcmZuy%WoS+7@wJac>PKo*KJ5C!HtkwBN3+vfm8wNBTiM(=0H-FE>zLRGqyL{S7*iz zUfBpKL5h$30sJ#_-~gxI%cYm{%{UEx&1mQO?f3Y7j|r;P5`u>R{4Vf3g#OW+ zSDOu+EAWY*V4#TN2>E&@5sryZuwGk{72CdT)^_B?uJ4*PjtYLk{Nwo^%uZB{OMVIN zTv~|AamBC1Rlf>*j}{*xe~y-D8RmIfW6){!H$vy={3FL-pm+c7?hLoFp=ES>xGRlVw5q3)DSz;(2s9YsBJ*E`u4CpOSov;;hR zV0ifp;5oJru{Z-Xm1C||ps{siP6@3oX}$}TObN_&J!?IrGsaI4odX`HDEfsgp(p5f zi#86(uPYO0>>RnWa0Jq)c(16Ty$g`q&<`w(lG-IC{#rAkqk{*dF*zjsV_A~rcX91w zdipUrs%Xa?;-mqZo|$PXt4DJua^~qG*zU^O*gaaH?@ckCr%R8?I=cV&4`fo9czKPU z9eecsC5@y7LA(M%TtCF4pJ!n#PtkK4wR<4t!Nx48LDmQ30$92{w!lK$qy(lPYCXpm zZJyXpv_EF3yncR+NpV~}#>9FjCiP1oRXlqG70fqYqVhV|*D-mpVFLF2W?mminTgA7 zoi0PZ+D+rOg3JwNGf8DLh<1nZhAQ-fC}Mo1aFBa&WsY;J9kjQ7dpAn^d8Hi%qjV_S zW>Dolfn0pgK@^w?5mY-nn;$& zJ3LysZ)zimk-0n5QWcJ`^9$G0VMGlI>8=dIqzQ@G?4`U(jUtTBBdZR8muCS|xr)Gh5Vr+*zUYi#=U#cXau24C2ZH|M|AijGZoAN<~abf%jFXJ`r;)_7hz-OTt*YKR7Y>>0~9R3vU z496DUZq<1WID!=eFf!s$@sJ{^m^~9kfE3<4fP4nc1-RucL*Hi)!R6+8Q7(A67xv9F y!}Sw3!%$aWsN9kpVXv5^-wAHYwD)D4(qY8D){mcPz@QC+PP9oCQX^i`v;PD8>=9c4 literal 0 HcmV?d00001 diff --git a/imgmine/config.py b/imgmine/config.py new file mode 100644 index 0000000..5cc0401 --- /dev/null +++ b/imgmine/config.py @@ -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/' \ No newline at end of file diff --git a/imgmine/get.py b/imgmine/get.py new file mode 100644 index 0000000..574ad74 --- /dev/null +++ b/imgmine/get.py @@ -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() + diff --git a/imgmine/web/gallery.html b/imgmine/web/gallery.html new file mode 100644 index 0000000..51139b7 --- /dev/null +++ b/imgmine/web/gallery.html @@ -0,0 +1,25 @@ + + + + + + + imgmine - minimal & private imgur proxy + + + + + % for img in imgs: + +
+ % end +
+ + This website does not claim ownership of any media. +
This service simply acts as a proxy to Imgur.com and does not store images aside from a temporary cache. +
Abusive images should be reported to imgur. This website does not create new images/comments. +
+
+ + + \ No newline at end of file diff --git a/imgmine/web/index.html b/imgmine/web/index.html new file mode 100644 index 0000000..19c56fe --- /dev/null +++ b/imgmine/web/index.html @@ -0,0 +1,41 @@ + + + + + + + imgmine - minimal & private imgur proxy + + + + +

+ imgmine +

+
+

+ A minimalist read-only Imgur proxy insipired by software like Invidious, Nitter, and Bibliogram. +

+

+ This project was started because Kevin Froman got fed up with Imgur breaking with Tor because of cow-dung JS. +

+

Feature roadmap

+

Features are in order of priority of most to least

+
    +
  • Image proxying
  • +
  • Gallery/album proxying
  • +
  • Proper order of gallery posts
  • +
  • Image description and author info
  • +
  • GIFs/videos
  • +
  • Comments... maybe
  • +
+ + + + \ No newline at end of file diff --git a/imgmine/web/theme.css b/imgmine/web/theme.css new file mode 100644 index 0000000..a59cdb8 --- /dev/null +++ b/imgmine/web/theme.css @@ -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; +} \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..834f28a --- /dev/null +++ b/run.py @@ -0,0 +1,3 @@ +import imgmine + +imgmine.start_server() \ No newline at end of file diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..ba03150 --- /dev/null +++ b/run_tests.sh @@ -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 diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..0d6f79c --- /dev/null +++ b/tests/test.py @@ -0,0 +1,7 @@ +import unittest +class TestBasic(unittest.TestCase): + + def test_basic(self): + self.assertTrue(True) + +unittest.main() \ No newline at end of file