Browse Source

Merge branch 'master' of git.closed.social:closed-social/pastExam

master
TA 3 years ago
parent
commit
f9efc25ce7
6 changed files with 123 additions and 31 deletions
  1. +28
    -4
      app.py
  2. +55
    -0
      gen_wechat_html.py
  3. +1
    -0
      requirements.txt
  4. BIN
     
  5. +0
    -3
      static/login/index.html
  6. +39
    -24
      templates/list.html

+ 28
- 4
app.py View File

@ -7,7 +7,7 @@
# create or refresh the database
# $ python3 app.py
from flask import Flask, request, render_template, send_from_directory, abort, redirect, session, url_for
from flask import Flask, request, render_template, send_from_directory, abort, redirect, session, url_for, send_file
from flask_sqlalchemy import SQLAlchemy
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
@ -18,8 +18,10 @@ from mastodon import Mastodon
from datetime import date, datetime
from functools import wraps
import hashlib
from zipfile import ZipFile
import random
import os
import re
from config import C
app = Flask(__name__)
@ -56,6 +58,9 @@ class Paper(db.Model):
down_num = db.Column(db.Integer, index=True, default=0)
file_hash = db.Column(db.String(64))
# always increment 1 for the id of a new record
__table_args__ = { 'sqlite_autoincrement': True }
def is_downloaded(self):
return bool(DownloadRelation.query.filter_by(paper_id=self.id, username=session.get('username')).count())
@ -94,12 +99,17 @@ def login_required(allow_guest=True):
def login():
return app.send_static_file('login/index.html')
@app.route('/pastExam/logout')
def logout():
session.pop('username', None)
return redirect('login')
@app.route('/pastExam/login/guest/')
def guest_login():
return render_template('guest-login.html', vs=C.verify, allow_guest_upload=C.allow_guest_upload)
@app.route('/pastExam/login/guest/verify', methods=['POST'])
@limiter.limit("10 / hour")
@limiter.limit("5 / hour")
def guest_login_verify():
for name, ques, hint, ans in C.verify:
if request.form.get(name) != ans:
@ -109,7 +119,7 @@ def guest_login_verify():
session['uid'] = random.randint(0, 10000000)
session['username'] = 'guest<%s>' % session['uid']
session['avatar'] = None
session.pop('avatar', None)
session.permanent = True
return {'r':0}
@ -136,7 +146,7 @@ def mast_login_auth():
@app.route('/pastExam/')
@login_required()
def list(username):
avatar = session.get('avatar') or C.guest_avatar
avatar = session.get('avatar', C.guest_avatar)
course = request.args.get('course')
teacher = request.args.get('teacher')
year = request.args.get('year')
@ -226,6 +236,7 @@ def upload(username):
return redirect('.#part2')
@app.route('/pastExam/<pid>/download')
@limiter.limit("100 / hour")
@login_required()
def download(pid, username):
p = Paper.query.get_or_404(pid)
@ -238,9 +249,22 @@ def download(pid, username):
p.down_num += 1
db.session.commit()
if request.args.get('type') == 'zip':
target_file = '/tmp/%s.zip' % pid
if not os.path.exists(target_file):
ipfs_client.get(p.file_hash, target='/tmp')
with ZipFile(target_file, 'w') as z:
for fname in os.listdir(os.path.join('/tmp', p.file_hash)):
z.write(os.path.join('/tmp', p.file_hash, fname), fname)
filename = re.sub('[^\w@_()()-]', '_', '%s_%s_共享计划_%d' %(p.course, p.teacher, p.id)) + '.zip'
return send_file(target_file, as_attachment=True, attachment_filename=filename)
return redirect(C.ipfs_base_url + p.file_hash, code=301) # 301减少不必要的请求
@app.route('/pastExam/<pid>/like', methods=['POST', 'DELETE'])
@limiter.limit("100 / hour")
@login_required()
def like(pid, username):
p = Paper.query.get_or_404(pid)

+ 55
- 0
gen_wechat_html.py View File

@ -0,0 +1,55 @@
import io
from datetime import datetime
from paramiko import SSHClient
from scp import SCPClient
from app import Paper, C, db
from time import sleep
html_text = '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script>
if(navigator.userAgent.search('MicroMessenger') == -1)
window.location.host = '{target_host}';
</script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body style="margin:0;background:#101010;color:white;font-size:28px;text-align:center">
<img src="https://bbs.pku.edu.cn/attach/d3/ee/d3eee0cd4e94ce11/pastExam_wechat.png" width="100%">
<hr style="margin:0 40px">
<p>{time}<br>{nc}{np}<br></p>
</body>
</html>
'''
def check_and_update(last_number):
np = Paper.query.count()
if np != last_number:
nc = db.session.query(Paper.course.distinct()).count()
t = datetime.now()
username, host, path, target_host = C.wechat_html_address
ssh = SSHClient()
ssh.load_system_host_keys()
ssh.connect(host, username=username)
with SCPClient(ssh.get_transport()) as scp:
fl = io.BytesIO()
fl.write(bytes(html_text.format(time=t.strftime("%Y-%m-%d %H:%M"), nc=nc, np=np, target_host=target_host), encoding='utf-8'))
fl.seek(0)
scp.putfo(fl, path)
return np
n = 0
while True:
try:
print(str(datetime.now()), 'start ------')
n = check_and_update(n)
print(n)
except:
print('scp failed')
sleep(300)

+ 1
- 0
requirements.txt View File

@ -3,3 +3,4 @@ Mastodon.py==1.5.1
Flask_Limiter==1.3.1
Flask_SQLAlchemy==2.4.4
ipfshttpclient==0.7.0a1
scp==0.13.3

BIN
View File


+ 0
- 3
static/login/index.html View File

@ -86,9 +86,6 @@
<div class="text-center">
<iframe class="guest-login-form" style="display: none;border:none"></iframe>
</div>
<div class="text-center pt-4 mb-2" style="border-top: 1px white solid">
<a href="../" class="btn btn-link btn-lg"><i>(已登陆?)</i></a>
</div>
</div>
<div class="text-center">

+ 39
- 24
templates/list.html View File

@ -46,6 +46,10 @@
font-weight: 700;
}
.num-infos {
display: inline-block;
}
a,
a:hover,
.btn-link,
@ -184,7 +188,7 @@
display: none;
}
.user-info .btn {
.btn, input, select, textarea, label {
box-shadow: none !important;
}
@ -216,8 +220,7 @@
}
.wechat-share img {
margin: 32px;
width: 128px;
width: 196px;
}
</style>
</head>
@ -234,21 +237,21 @@
<img class="rounded-circle" src="{{avatar}}" width="24">
<span class="username ml-2">{{username}}</span>
</button>
<button type="button" class="btn btn-dark rounded-0 dropdown-toggle dropdown-toggle-split" id="dropdownMenuReference" data-toggle="dropdown" data-reference="parent">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="?my_upload=1#part2">我的上传</a>
<a class="dropdown-item" href="?my_fav=1#part2">我的点赞</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="login">切换帐</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item wechat-share" href="##">
微信传播
<div><img src="img/wechat-share.png"></div>
</a>
</div>
</div>
<button type="button" class="btn btn-dark rounded-0 dropdown-toggle dropdown-toggle-split" id="dropdownMenuReference" data-toggle="dropdown" data-reference="parent">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="?my_upload=1#part2">我的上传</a>
<a class="dropdown-item" href="?my_fav=1#part2">我的点赞</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="logout">退出账</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item wechat-share" href="##">
微信传播
<div><img src="img/wechat-share.png"></div>
</a>
</div>
</div>
</div>
<div class="part1 new">
@ -387,12 +390,24 @@
<pre class="ml-3">{{p.notes}}</pre>
<div class="text-right">
<small>@{{'匿名用户' if p.anon else p.author}} | {{p.create_date}}</small>
<button class="btn btn-link" id="like-{{p.id}}" onclick="like('{{p.id}}')">
<span class="num-info {{'num-info-active' if p.is_liked() else ''}}">好评(<span>{{p.like_num}}</span>)</span>
</button>
<a href="{{p.id}}/download?v={{ipfs_version}}" target="_blank" class="btn btn-link pl-0" id="download-{{p.id}}">
<span class="num-info {{'num-info-active' if p.is_downloaded() else ''}}">去下载({{p.down_num}})</sp>
</a>
<div class="num-infos">
<button class="btn btn-link" id="like-{{p.id}}" onclick="like('{{p.id}}')">
<span class="num-info {{'num-info-active' if p.is_liked() else ''}}">好评(<span>{{p.like_num}}</span>)</span>
</button>
<span>
<a href="{{p.id}}/download?v={{ipfs_version}}" target="_blank" class="btn btn-link px-0" id="download-{{p.id}}">
<span class="num-info {{'num-info-active' if p.is_downloaded() else ''}}">去下载({{p.down_num}})</span>
</a>
<button type="button" class="btn btn-link px-0 dropdown-toggle dropdown-toggle-split" id="downloadDropdownMenuReference" data-toggle="dropdown" data-reference="parent">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu" style="min-width: 5rem">
<a class="dropdown-item" href="{{p.id}}/download?type=zip" target="_blank" id="download-zip-{{p.id}}">
<span class="num-info">打包下载</span>
</a>
</div>
</span>
</div>
</div>
</div>
{% endfor %}

Loading…
Cancel
Save