@ -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> |