@ -0,0 +1,109 @@ | |||||
from flask import Flask, request, render_template, send_from_directory | |||||
from flask_sqlalchemy import SQLAlchemy | |||||
from mastodon import Mastodon | |||||
import re | |||||
#import html2text | |||||
BOT_NAME = '@ask_me_bot' | |||||
DOMAIN = 'thu.closed.social' | |||||
token = open('token.secret','r').read().strip('\n') | |||||
th = Mastodon( | |||||
access_token = token, | |||||
api_base_url = 'https://' + DOMAIN | |||||
) | |||||
app = Flask(__name__) | |||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///ask.db' | |||||
#h2t = html2text.HTML2Text() | |||||
#h2t.ignore_links = True | |||||
def PM(msg, name): | |||||
th.status_post(msg + '\n@' + name, visibility='direct') | |||||
db = SQLAlchemy(app) | |||||
''' | |||||
class Record(db.Model): | |||||
id = db.Column(db.Integer, primary_key=True) | |||||
s = db.Column(db.String(64)) | |||||
name_hash = db.Column(db.String(64)) | |||||
full_hash = db.Column(db.String(64)) | |||||
ip = db.Column(db.String(32)) | |||||
cs_username = db.Column(db.String(32)) | |||||
def __init__(self, s, name_hash, full_hash, ip): | |||||
self.s = s | |||||
self.name_hash = name_hash | |||||
self.full_hash = full_hash | |||||
self.ip = ip | |||||
self.cs_username = '' | |||||
def __repr__(self): | |||||
return '%s[%s]<%s>'%(self.s, self.cs_username, self.ip) | |||||
''' | |||||
@app.route('/js/<path:path>') | |||||
def send_js(path): | |||||
return send_from_directory('static/js', path) | |||||
@app.route('/img/<path:path>') | |||||
def send_img(path): | |||||
return send_from_directory('static/img', path) | |||||
@app.route('/askMe/') | |||||
def root(): | |||||
return app.send_static_file('ask.html') | |||||
@app.route('/askMe/inbox/', methods=['POST']) | |||||
def api(): | |||||
username = request.form.get('username') | |||||
if not re.match('[a-z0-9_]{1,30}(@[a-z\.-_]+)?', username): | |||||
return '闭社id格式错误', 422 | |||||
return 'okkk' | |||||
ha = request.form.get('hash') | |||||
if( not ha or len(ha) != 64 * 2): | |||||
return '哈希格式不正确', 422 | |||||
ip = request.remote_addr | |||||
if ip in ip_count: | |||||
ip_count[ip] += 1 | |||||
if ip_count[ip] > 50: | |||||
return '该ip告白次数太多', 403 | |||||
else: | |||||
ip_count[ip] = 1 | |||||
if Record.query.filter_by(s=s).count(): | |||||
return '暗号重复', 422 | |||||
if Record.query.filter_by(name_hash=ha[64:]).count(): | |||||
return '一个名字只能告白一次,\n重名/哈希冲突请联系主办方', 422 | |||||
ta = Record.query.filter_by(full_hash=ha[:64]).first() | |||||
rec = Record(s, ha[64:], ha[:64], ip) | |||||
rec = Record(s, ha[64:], ha[:64], ip) | |||||
db.session.add(rec) | |||||
db.session.commit() | |||||
if not ta: | |||||
return '' | |||||
else: | |||||
if ta.cs_username: | |||||
PM('叮~ TA也给你表白啦! https://closed.social/meetLove/result/', ta.cs_username) | |||||
return 'y' if ta.cs_username else 'n' | |||||
@app.route('/meetLove/result/') | |||||
def result(): | |||||
rs = Record.query.all() | |||||
rs.sort(key=lambda r:r.full_hash) | |||||
lovers = [(rs[i].s[:-4]+'****', rs[i+1].s[:-4]+'****') for i in range(len(rs)-1) if rs[i].full_hash == rs[i+1].full_hash] | |||||
return render_template('result.html', lovers=lovers) | |||||
if __name__ == '__main__': | |||||
app.run() |
@ -0,0 +1,141 @@ | |||||
<!doctype html> | |||||
<html> | |||||
<head> | |||||
<meta charset='UTF-8'> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | |||||
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> | |||||
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap-grid.min.css" rel="stylesheet"> | |||||
<script src="https://cdn.bootcss.com/js-sha256/0.9.0/sha256.min.js"></script> | |||||
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> | |||||
<script src="/js/notify.min.js"></script> | |||||
<title>匿名提问箱</title> | |||||
<style> | |||||
@keyframes downn { | |||||
0% { | |||||
transform: translate(0px, 0px); | |||||
} | |||||
50% { | |||||
transform: translate(0px, 7px); | |||||
} | |||||
100% { | |||||
transform: translate(0px, 0px); | |||||
} | |||||
} | |||||
</style> | |||||
</head> | |||||
<body style="background-color: #001a37;color:#e8e8e8"> | |||||
<div class="container" style="max-width:900px;" > | |||||
<div style="text-align:center; margin: 25px 0"> | |||||
<img align='center' src='/img/logo-text.svg' width=96px/> | |||||
<h1 align='center'>匿名提问箱</h1> | |||||
<hr/> | |||||
<p>提问无需登陆,真正匿名<br/> | |||||
链接路径加密,避免乱入</p> | |||||
</div> | |||||
<hr style="border-top: 1px solid white;"/> | |||||
<div style="text-align:center; margin:1%"> | |||||
<p><a type="button" class="btn btn-primary btn-lg" href="/askMe#start">创建提问箱</a></p> | |||||
<svg viewbox="0 0 50 100" width="64px" height="64px" stroke="#e8e8e8" stroke-width="7.5" stroke-miterlimit="10" style="animation: downn 2s infinite;"><path d="M25.684 10v50.4"/><path d="M40.284 45.4L24.684 61"/><path d="M10.884 45.4l15.6 15.6"/></svg> | |||||
<p>分享加密链接</p> | |||||
<svg viewbox="0 0 50 100" width="64px" height="64px" stroke="#e8e8e8" stroke-width="7.5" stroke-miterlimit="10" style="animation: downn 3s infinite;"><path d="M25.684 10v50.4"/><path d="M40.284 45.4L24.684 61"/><path d="M10.884 45.4l15.6 15.6"/></svg> | |||||
<p>好友匿名提问</p> | |||||
<svg viewbox="0 0 50 100" width="64px" height="64px" stroke="#e8e8e8" stroke-width="7.5" stroke-miterlimit="10" style="animation: downn 4s infinite;"><path d="M25.684 10v50.4"/><path d="M40.284 45.4L24.684 61"/><path d="M10.884 45.4l15.6 15.6"/></svg> | |||||
<p>bot在闭社私信提醒</p> | |||||
<svg viewbox="0 0 50 100" width="64px" height="64px" stroke="#e8e8e8" stroke-width="7.5" stroke-miterlimit="10" style="animation: downn 5s infinite;"><path d="M25.684 10v50.4"/><path d="M40.284 45.4L24.684 61"/><path d="M10.884 45.4l15.6 15.6"/></svg> | |||||
<p>回复私信进行答复或删除</p> | |||||
</div> | |||||
<hr style="border-top: 1px solid white;"/> | |||||
<div id="start" style="margin:30px auto;padding:15px;border: 1px dashed white;border-radius:15px; max-width:450px;"> | |||||
<p>输入闭社id并私信 <b>@ask_me_bot</b> “新建”或“重置”,然后点击按钮,即可新建提问箱或重置提问箱链接。</p> | |||||
<form class="form-inline" action="javascript:void(0);" onsubmit="return sendData()"> | |||||
<div class="input-group mb-2 mr-sm-2"> | |||||
<div class="input-group-prepend"> | |||||
<div class="input-group-text">@</div> | |||||
</div> | |||||
<input type="text" class="form-control" id="username" placeholder="Username"> | |||||
</div> | |||||
<button id="send" type="submit" class="btn btn-primary mb-2">我已私信</button> | |||||
</form> | |||||
<p style="font-size:80%"> <br/> | |||||
* 可通过重置链接并不再分享新链接来实现关闭提问箱。 <br/> | |||||
** 非清华站用户请使用完整用户名而非本地用户名,例如 @somebody@tha.closed.social。<br/> | |||||
** 高级用法:私信“重置[路径]”以指定加密路径,而非默认的随机路径。限长度不超过32的小写字母,示例:<br/>“@ask_me_bot 重置[hhhhhhh]”。 | |||||
</p> | |||||
</div> | |||||
<hr/> | |||||
</div> | |||||
<script type="text/javascript"> | |||||
$.notify.defaults({autoHideDelay: 1500}); | |||||
function test() {alert('test');} | |||||
function sendData() { | |||||
$('#send')[0].disabled = true; | |||||
var data = { | |||||
username: $('#username')[0].value | |||||
}; | |||||
$.ajax({ | |||||
type:'POST', | |||||
url:'inbox/', | |||||
data:data, | |||||
success:(result,status,xhr) => { | |||||
console.log(result+' : '+status); | |||||
$.notify("操作成功", "success"); | |||||
}, | |||||
error:(xhr,status,error) => { | |||||
$.notify(xhr.status+' : '+xhr.responseText, "error"); | |||||
$('#send')[0].disabled = false; | |||||
} | |||||
}); | |||||
return false; | |||||
} | |||||
</script> | |||||
<footer style="background-color:#000010;color:white;border-top: 1px solid white;margin-top:20px;padding-top:10px;"> | |||||
<div class="footer" id="footer"> | |||||
<div class="container"> | |||||
<div class="row"> | |||||
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6"> | |||||
<img style="float:left;margin:0 15px 15px 5px;" src="/img/logo.svg" width="48px"/> | |||||
<h4> 闭社出品 closed.social </h4> | |||||
<p>隐私 安全 开源</p> | |||||
</div> | |||||
<div class="col-lg-3 col-sm-2 col-xs-3"> | |||||
<h5> Contact / <small><a class="email" href="mailto:info@closed.social"> info@closed.social </a> </small></h5> | |||||
<h5> Code / <small><a class="web" href="//github.com/closed-social"> github.com/closed-social </a></small></h5> | |||||
<h5> Home / <small><a class="web" href="/">闭社</a></small> </h5> | |||||
</div> | |||||
<!--/.row--> | |||||
</div> | |||||
<!--/.container--> | |||||
</div> | |||||
<!--/.footer--> | |||||
<div> | |||||
<hr style="border-top: 1px solid white;"/> | |||||
<p style="font-size:75%;margin:0;text-align:center"> © 2020 Copyright: closed.social</p> | |||||
</div> | |||||
</div> | |||||
<!--/.footer-bottom--> | |||||
</footer> | |||||
</body> | |||||
</html> |
@ -0,0 +1,43 @@ | |||||
<?xml version="1.0" encoding="iso-8859-1"?> | |||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" | |||||
y="0px" viewBox="0 0 113.737 65.72" style="enable-background:new 0 0 113.737 65.72;" xml:space="preserve"> | |||||
<g> | |||||
<g> | |||||
<rect x="73.083" y="26.552" style="fill:#2EA7E0;" width="19.32" height="3.469"/> | |||||
<g> | |||||
<rect x="14.663" y="13.285" style="fill:#2EA7E0;" width="4.123" height="37.608"/> | |||||
</g> | |||||
<g> | |||||
<rect x="14.663" y="10.816" style="fill:#2EA7E0;" width="8.247" height="3.762"/> | |||||
</g> | |||||
<g> | |||||
<rect x="14.663" y="50.894" style="fill:#2EA7E0;" width="8.247" height="3.761"/> | |||||
</g> | |||||
<g> | |||||
<rect x="96.95" y="13.285" style="fill:#2EA7E0;" width="4.124" height="37.608"/> | |||||
</g> | |||||
<g> | |||||
<rect x="92.827" y="10.816" style="fill:#2EA7E0;" width="8.247" height="3.762"/> | |||||
</g> | |||||
<g> | |||||
<rect x="92.827" y="50.894" style="fill:#2EA7E0;" width="8.247" height="3.761"/> | |||||
</g> | |||||
<rect x="24.548" y="10.816" style="fill:#2EA7E0;" width="3.761" height="3.762"/> | |||||
<rect x="62.174" y="10.816" style="fill:#2EA7E0;" width="3.761" height="3.762"/> | |||||
<rect x="24.548" y="22.075" style="fill:#2EA7E0;" width="3.243" height="32.829"/> | |||||
<path style="fill:#2EA7E0;" d="M28.309,14.023v3.288h27.12l-0.113,33.037c0,0.667-0.222,0.81-0.889,0.857 | |||||
c-0.417,0.037-1.719,0.094-3.244,0.093v3.275c2.775,0.018,7.489,0,7.489,0s-0.002-1.697,0-4.147V14.023H28.309z"/> | |||||
<rect x="32.701" y="27.232" style="fill:#2EA7E0;" width="23.718" height="2.789"/> | |||||
<rect x="45.81" y="21.918" style="fill:#2EA7E0;" width="2.519" height="25.578"/> | |||||
<rect x="40.272" y="46.951" style="fill:#2EA7E0;" width="5.539" height="2.907"/> | |||||
<rect x="58.672" y="20.328" style="fill:#2EA7E0;" width="14.411" height="3.542"/> | |||||
<rect x="69.92" y="22.81" style="fill:#2EA7E0;" width="3.163" height="7.211"/> | |||||
<rect x="62.598" y="34.597" style="fill:#2EA7E0;" width="2.913" height="19.984"/> | |||||
<polygon style="fill:#2EA7E0;" points="58.672,35.742 70.017,27.807 73.083,30.021 58.672,39.77 "/> | |||||
<polygon style="fill:#2EA7E0;" points="32.701,40.71 45.81,32.242 48.329,34.677 32.701,44.739 "/> | |||||
<rect x="79.327" y="14.023" style="fill:#2EA7E0;" width="3.493" height="40.559"/> | |||||
<rect x="64.991" y="51.4" style="fill:#2EA7E0;" width="26.146" height="3.182"/> | |||||
</g> | |||||
</g> | |||||
</svg> |
@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#ffffff"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg> |