Hoshino-Auth
--------------------------------------------------------------------------------
/web_activate.py:
--------------------------------------------------------------------------------
1 | from quart import request, Blueprint, jsonify, render_template
2 |
3 | import nonebot
4 |
5 | from . import util
6 |
7 |
8 |
9 | activate = Blueprint('activate', __name__, url_prefix='/activate', template_folder="./activate"
10 | , static_folder='./activate', static_url_path='')
11 | bot = nonebot.get_bot()
12 | app = bot.server_app
13 |
14 |
15 | @activate.route("/", methods=["GET", "POST"])
16 | async def activate_group():
17 | if request.method == "GET":
18 | if key := request.args.get("key"):
19 | if gid := request.args.get('group'):
20 | group_id = int(gid)
21 | days = util.query_key(key)
22 | result = await util.reg_group(group_id, key)
23 | if result:
24 | log_info = f'卡密{key}通过网页被激活\n为群聊{group_id}增加了{days}天授权时长'
25 | util.log(log_info,'card_use')
26 | await util.notify_master(log_info)
27 | return await render_template("activate.html")
28 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import on_command, get_bot
2 |
3 | import hoshino
4 |
5 | from .constant import config, __version__
6 | from .web_server import auth
7 | from .web_activate import activate
8 |
9 |
10 |
11 | if config.ENABLE_WEB:
12 | # 开启web请修改authMS.py
13 | app = get_bot().server_app
14 | app.register_blueprint(auth)
15 | app.register_blueprint(activate)
16 |
17 |
18 | @on_command('充值帮助', aliases=('我要充钱','续费帮助','我要续费','👴要充钱'), only_to_me=False)
19 | async def reg_help_chat(session):
20 | if session.event.detail_type == 'private':
21 | msg = config.REG_HELP_PRIVATE
22 | else:
23 | msg = config.REG_HELP_GROUP
24 | #else:
25 | # 新版QQ已不在有discuss, 所有多人聊天都是群消息
26 | # return
27 | await session.finish(msg)
28 |
29 |
30 | @on_command('管理员帮助', only_to_me=False)
31 | async def master_help_chat(session):
32 | if session.event.detail_type == 'group':
33 | return
34 | if session.event.user_id not in hoshino.config.SUPERUSERS:
35 | await session.finish('只有主人才能查看此页帮助')
36 | await session.finish(config.ADMIN_HELP)
37 |
38 |
39 | @on_command('授权系统版本', only_to_me=True)
40 | async def check_new_ver_chat(session):
41 | if session.event.user_id not in hoshino.config.SUPERUSERS:
42 | return
43 | await session.finish(f'授权系统当前版本v{__version__}')
44 |
--------------------------------------------------------------------------------
/vue/js/7.a056b5fa.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[7],{d684:function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"q-ma-xl"},[a("span",{staticStyle:{"font-size":"28px"}},[t._v("授权列表")]),a("div",{staticClass:"q-pa-md"},[a("div",{staticClass:"row justify-center q-gutter-sm",staticStyle:{width:"auto"}},t._l(t.groupList,(function(e,s){return a("q-intersection",{key:s,staticClass:"example-item",attrs:{once:"",transition:"scale"}},[a("q-card",{staticClass:"bg-primary text-white",staticStyle:{width:"180px"}},[a("br"),a("div",{staticClass:"text-bold text-center"},[t._v("\n 群号: "+t._s(e.gid)+"\n ")]),a("br"),a("q-card-section",{staticClass:"justify-center bg-teal-14",staticStyle:{"vertical-align":"middle"}},[t._v("\n 授权截止至: "+t._s(e.deadline)+"\n ")])],1)],1)})),1)])])},i=[],n=a("18d6"),r={name:"Group",data(){return{groupList:[{gid:123456,deadline:"2020-12-20"}]}},methods:{updateGroup(){this.$axios.get("/get/group?password="+n["a"].getItem("password")).then(t=>{this.groupList=t.data})}},created(){this.updateGroup()}},c=r,d=a("2877"),o=a("ad56"),l=a("f09f"),p=a("a370"),u=a("eebe"),g=a.n(u),f=Object(d["a"])(c,s,i,!1,null,"5aeff71e",null);e["default"]=f.exports;g()(f,"components",{QIntersection:o["a"],QCard:l["a"],QCardSection:p["a"]})}}]);
--------------------------------------------------------------------------------
/vue/js/8.a093542b.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[8],{"013f":function(a,t,s){"use strict";s.r(t);var e=function(){var a=this,t=a.$createElement,s=a._self._c||t;return s("q-page",{staticClass:"flex flex-center"},[s("q-dialog",{attrs:{persistent:""},model:{value:a.alert,callback:function(t){a.alert=t},expression:"alert"}},[s("q-card",{staticClass:"flex flex-center"},[s("q-card-section",[s("div",{staticClass:"text-h6"},[a._v("请输入管理员密码")])]),s("q-card-section",{staticClass:"q-pt-none"},[s("q-input",{attrs:{type:"password"},model:{value:a.password,callback:function(t){a.password=t},expression:"password"}})],1),s("q-card-actions",{attrs:{align:"right"}},[s("q-btn",{attrs:{flat:"",label:"确定",color:"primary"},on:{click:a.submit}})],1)],1)],1)],1)},o=[],l=(s("5319"),{name:"Login",data(){return{password:"",alert:!0}},methods:{submit(){this.$axios.post("login?password="+this.password).then(a=>{console.log(a),"success"===a.data&&(this.$q.localStorage.set("login",!0),this.$q.localStorage.set("password",this.password),this.$router.replace("/"))}).catch(a=>{console.log(a)})}}}),r=l,n=s("2877"),c=s("9989"),i=s("24e8"),p=s("f09f"),d=s("a370"),u=s("27f9"),f=s("4b7e"),w=s("9c40"),g=s("eebe"),h=s.n(g),b=Object(n["a"])(r,e,o,!1,null,null,null);t["default"]=b.exports;h()(b,"components",{QPage:c["a"],QDialog:i["a"],QCard:p["a"],QCardSection:d["a"],QInput:u["a"],QCardActions:f["a"],QBtn:w["a"]})}}]);
--------------------------------------------------------------------------------
/activate/activate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 授权激活
6 |
7 |
8 |
9 |
10 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/vue/js/5.35de94fb.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[5],{"7aa6":function(e,t,s){"use strict";s.r(t);var a=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("q-page",{staticClass:"flex flex-center"},[s("q-dialog",{on:{hide:e.onHide},model:{value:e.alert,callback:function(t){e.alert=t},expression:"alert"}},[s("q-card",{staticClass:"flex flex-center"},[s("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[s("q-form",{staticClass:"q-gutter-md"},[s("q-input",{attrs:{filled:"",label:"注册码","lazy-rules":"",rules:[function(e){return e&&e.length>0||"请输入注册码!"}]},model:{value:e.key,callback:function(t){e.key=t},expression:"key"}}),s("q-input",{attrs:{filled:"",type:"number",label:"群号*","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"请输入群号!"}]},model:{value:e.gid,callback:function(t){e.gid=t},expression:"gid"}}),s("div",{attrs:{align:"center"}},[s("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:e.onSubmit}}),s("q-btn",{staticClass:"q-ml-sm",attrs:{label:"Reset",color:"primary",flat:""},on:{click:e.onReset}})],1)],1)],1)])],1)],1)},i=[],l=(s("5319"),{name:"Login",data(){return{key:"",gid:0,alert:!0}},methods:{message(e){this.$q.dialog({title:"消息",message:e}).onOk(()=>{}).onCancel(()=>{}).onDismiss(()=>{})},submit(){this.$axios.post("activate?key="+this.key+"&gid="+this.gid).then(e=>{"success"===e.data?(this.message("已为您的群"+this.gid.toString()+"注册成功!"),this.$router.replace("/")):this.message("注册失败!")}).catch(e=>{console.log(e)})},onSubmit(){this.$q.dialog({title:"Confirm",message:"确定为群"+this.gid.toString()+"注册?",cancel:!0,persistent:!0}).onOk(()=>{}).onOk(()=>{this.submit()}).onCancel(()=>{}).onDismiss(()=>{})},onReset(){this.gid=0,this.key=""},onHide(){this.$router.replace("/")}}}),n=l,o=s("2877"),r=s("9989"),c=s("24e8"),u=s("f09f"),d=s("0378"),m=s("27f9"),g=s("9c40"),p=s("eebe"),h=s.n(p),f=Object(o["a"])(n,a,i,!1,null,null,null);t["default"]=f.exports;h()(f,"components",{QPage:r["a"],QDialog:c["a"],QCard:u["a"],QForm:d["a"],QInput:m["a"],QBtn:g["a"]})}}]);
--------------------------------------------------------------------------------
/vue/js/6.f72bbf7f.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[6],{"7aa6":function(e,t,s){"use strict";s.r(t);var a=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("q-page",{staticClass:"flex flex-center"},[s("q-dialog",{on:{hide:e.onHide},model:{value:e.alert,callback:function(t){e.alert=t},expression:"alert"}},[s("q-card",{staticClass:"flex flex-center"},[s("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[s("q-form",{staticClass:"q-gutter-md"},[s("q-input",{attrs:{filled:"",label:"注册码","lazy-rules":"",rules:[function(e){return e&&e.length>0||"请输入注册码!"}]},model:{value:e.key,callback:function(t){e.key=t},expression:"key"}}),s("q-input",{attrs:{filled:"",type:"number",label:"群号*","lazy-rules":"",rules:[function(e){return null!==e&&""!==e||"请输入群号!"}]},model:{value:e.gid,callback:function(t){e.gid=t},expression:"gid"}}),s("div",{attrs:{align:"center"}},[s("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:e.onSubmit}}),s("q-btn",{staticClass:"q-ml-sm",attrs:{label:"Reset",color:"primary",flat:""},on:{click:e.onReset}})],1)],1)],1)])],1)],1)},i=[],l=(s("5319"),{name:"Login",data(){return{key:"",gid:0,alert:!0}},methods:{message(e){this.$q.dialog({title:"消息",message:e}).onOk(()=>{}).onCancel(()=>{}).onDismiss(()=>{})},submit(){this.$axios.post("activate?key="+this.key+"&gid="+this.gid).then(e=>{"success"===e.data?(this.message("已为您的群"+this.gid.toString()+"注册成功!"),this.$router.replace("/")):this.message("注册失败!")}).catch(e=>{console.log(e)})},onSubmit(){this.$q.dialog({title:"Confirm",message:"确定为群"+this.gid.toString()+"注册?",cancel:!0,persistent:!0}).onOk(()=>{}).onOk(()=>{this.submit()}).onCancel(()=>{}).onDismiss(()=>{})},onReset(){this.gid=0,this.key=""},onHide(){this.$router.replace("/")}}}),n=l,o=s("2877"),r=s("9989"),c=s("24e8"),u=s("f09f"),d=s("0378"),m=s("27f9"),g=s("9c40"),p=s("eebe"),h=s.n(p),f=Object(o["a"])(n,a,i,!1,null,null,null);t["default"]=f.exports;h()(f,"components",{QPage:r["a"],QDialog:c["a"],QCard:u["a"],QForm:d["a"],QInput:m["a"],QBtn:g["a"]})}}]);
--------------------------------------------------------------------------------
/activate/js/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var placeholders = document.querySelectorAll('.styled-input__placeholder-text'),
4 | inputs = document.querySelectorAll('.styled-input__input');
5 |
6 | placeholders.forEach(function (el, i) {
7 | var value = el.innerText,
8 | html = '';
9 | for (var _iterator = value, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
10 | var _ref;
11 |
12 | if (_isArray) {
13 | if (_i >= _iterator.length) break;
14 | _ref = _iterator[_i++];
15 | } else {
16 | _i = _iterator.next();
17 | if (_i.done) break;
18 | _ref = _i.value;
19 | }
20 |
21 | var w = _ref;
22 |
23 | if (!value) value = ' ';
24 | html += '' + w + '';
25 | }
26 | el.innerHTML = html;
27 | });
28 |
29 | inputs.forEach(function (el) {
30 | var parent = el.parentNode;
31 | el.addEventListener('focus', function () {
32 | parent.classList.add('filled');
33 | placeholderAnimationIn(parent, true);
34 | }, false);
35 | el.addEventListener('blur', function () {
36 | if (el.value.length) return;
37 | parent.classList.remove('filled');
38 | placeholderAnimationIn(parent, false);
39 | }, false);
40 | });
41 |
42 | function placeholderAnimationIn(parent, action) {
43 | var act = action ? 'add' : 'remove';
44 | var letters = parent.querySelectorAll('.letter');
45 | letters = [].slice.call(letters, 0);
46 | if (!action) letters = letters.reverse();
47 | letters.forEach(function (el, i) {
48 | setTimeout(function () {
49 | var contains = parent.classList.contains('filled');
50 | if (action && !contains || !action && contains) return;
51 | el.classList[act]('active');
52 | }, 50 * i);
53 | });
54 | }
55 |
56 | setTimeout(function () {
57 | document.body.classList.add('on-start');
58 | }, 100);
59 |
60 | setTimeout(function () {
61 | document.body.classList.add('document-loaded');
62 | }, 1800);
63 |
--------------------------------------------------------------------------------
/vue/js/4.cf5327cd.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[4],{"713b":function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("q-layout",{attrs:{view:"lHh Lpr lFf"}},[n("q-header",{attrs:{elevated:""}},[n("q-toolbar",[n("q-btn",{attrs:{flat:"",dense:"",round:"",icon:"menu","aria-label":"Menu"},on:{click:function(t){e.leftDrawerOpen=!e.leftDrawerOpen}}}),n("q-toolbar-title",[e._v("\n Hoshino授权管理系统\n ")])],1)],1),n("q-drawer",{attrs:{"show-if-above":"",bordered:"","content-class":"bg-grey-1"},model:{value:e.leftDrawerOpen,callback:function(t){e.leftDrawerOpen=t},expression:"leftDrawerOpen"}},[n("q-list",[n("q-item-label",{staticClass:"text-grey-8",attrs:{header:""}},[e._v("\n 导航\n ")]),e._l(e.essentialLinks,(function(t){return n("EssentialLink",e._b({key:t.title},"EssentialLink",t,!1))}))],2)],1),n("q-page-container",[n("router-view")],1)],1)},l=[],i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("q-item",{attrs:{clickable:"",tag:"a"},on:{click:function(t){return e.$router.replace(e.link)}}},[e.icon?n("q-item-section",{attrs:{avatar:""}},[n("q-icon",{attrs:{name:e.icon}})],1):e._e(),n("q-item-section",[n("q-item-label",[e._v(e._s(e.title))]),n("q-item-label",{attrs:{caption:""}},[e._v("\n "+e._s(e.caption)+"\n ")])],1)],1)},r=[],o={name:"EssentialLink",props:{title:{type:String,required:!0},caption:{type:String,default:""},link:{type:String,default:"#"},icon:{type:String,default:""}}},s=o,c=n("2877"),u=n("66e5"),p=n("4074"),b=n("0016"),f=n("0170"),k=n("eebe"),m=n.n(k),d=Object(c["a"])(s,i,r,!1,null,null,null),w=d.exports;m()(d,"components",{QItem:u["a"],QItemSection:p["a"],QIcon:b["a"],QItemLabel:f["a"]});const q=[{title:"首页",link:"/"},{title:"激活",link:"/activate"},{title:"卡密管理",link:"/regkey"},{title:"群组管理",link:"/group"}];var v={name:"MainLayout",components:{EssentialLink:w},data(){return{leftDrawerOpen:!1,essentialLinks:q}}},Q=v,_=n("4d5a"),g=n("e359"),L=n("65c6"),y=n("9c40"),h=n("6ac5"),O=n("9404"),D=n("1c1c"),E=n("09e3"),I=Object(c["a"])(Q,a,l,!1,null,null,null);t["default"]=I.exports;m()(I,"components",{QLayout:_["a"],QHeader:g["a"],QToolbar:L["a"],QBtn:y["a"],QToolbarTitle:h["a"],QDrawer:O["a"],QList:D["a"],QItemLabel:f["a"],QPageContainer:E["a"]})}}]);
--------------------------------------------------------------------------------
/vue/js/5.69a84b0f.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[5],{"713b":function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("q-layout",{attrs:{view:"lHh Lpr lFf"}},[n("q-header",{attrs:{elevated:""}},[n("q-toolbar",[n("q-btn",{attrs:{flat:"",dense:"",round:"",icon:"menu","aria-label":"Menu"},on:{click:function(t){e.leftDrawerOpen=!e.leftDrawerOpen}}}),n("q-toolbar-title",[e._v("\n Hoshino授权管理系统\n ")])],1)],1),n("q-drawer",{attrs:{"show-if-above":"",bordered:"","content-class":"bg-grey-1"},model:{value:e.leftDrawerOpen,callback:function(t){e.leftDrawerOpen=t},expression:"leftDrawerOpen"}},[n("q-list",[n("q-item-label",{staticClass:"text-grey-8",attrs:{header:""}},[e._v("\n 导航\n ")]),e._l(e.essentialLinks,(function(t){return n("EssentialLink",e._b({key:t.title},"EssentialLink",t,!1))}))],2)],1),n("q-page-container",[n("router-view")],1)],1)},l=[],i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("q-item",{attrs:{clickable:"",tag:"a"},on:{click:function(t){return e.$router.replace(e.link)}}},[e.icon?n("q-item-section",{attrs:{avatar:""}},[n("q-icon",{attrs:{name:e.icon}})],1):e._e(),n("q-item-section",[n("q-item-label",[e._v(e._s(e.title))]),n("q-item-label",{attrs:{caption:""}},[e._v("\n "+e._s(e.caption)+"\n ")])],1)],1)},r=[],o={name:"EssentialLink",props:{title:{type:String,required:!0},caption:{type:String,default:""},link:{type:String,default:"#"},icon:{type:String,default:""}}},s=o,c=n("2877"),u=n("66e5"),p=n("4074"),b=n("0016"),f=n("0170"),k=n("eebe"),m=n.n(k),d=Object(c["a"])(s,i,r,!1,null,null,null),w=d.exports;m()(d,"components",{QItem:u["a"],QItemSection:p["a"],QIcon:b["a"],QItemLabel:f["a"]});const q=[{title:"首页",link:"/"},{title:"激活",link:"/activate"},{title:"卡密管理",link:"/regkey"},{title:"群组管理",link:"/group"}];var v={name:"MainLayout",components:{EssentialLink:w},data(){return{leftDrawerOpen:!1,essentialLinks:q}}},Q=v,_=n("4d5a"),g=n("e359"),L=n("65c6"),y=n("9c40"),h=n("6ac5"),O=n("9404"),D=n("1c1c"),E=n("09e3"),I=Object(c["a"])(Q,a,l,!1,null,null,null);t["default"]=I.exports;m()(I,"components",{QLayout:_["a"],QHeader:g["a"],QToolbar:L["a"],QBtn:y["a"],QToolbarTitle:h["a"],QDrawer:O["a"],QList:D["a"],QItemLabel:f["a"],QPageContainer:E["a"]})}}]);
--------------------------------------------------------------------------------
/authMS.py.example:
--------------------------------------------------------------------------------
1 | # 注意, 此配置中的部分选项可能暂时不生效, 请以README中列举出的功能为准
2 |
3 | class auth_config(object):
4 |
5 | # 授权系统自动检测总开关, 默认为关闭状态,此时不会自动检测/自动退群/停止响应无授权群聊
6 | # 设置此项目的是初次使用authMS时系统时的过渡选项, 正式使用应当配置为True
7 | ENABLE_AUTH = False
8 |
9 | # ------------------网页管理---------------------
10 | # 是否启用WEB
11 | ENABLE_WEB = False
12 |
13 | # WEB管理密码, 推荐使用强密码
14 | PASSWORD = 'Password123456!'
15 |
16 |
17 | # -------------------事件过滤器---------------------
18 | # 事件过滤器配置文件的地址,例如/root/go-cqhttp/filter.json(必须填写)
19 | EVENT_FILTER = r''
20 |
21 |
22 | # -------------------数据互通---------------------
23 | # 互通数据库的目录,例如group.sqlite位置/root/database/group.sqlite
24 | # 此时便填写/root/database/即可
25 | DB_PATH = ''
26 |
27 | # 是否启用互通
28 | ENABLE_COM = False
29 |
30 |
31 | # --------------------充值/卡密相关----------------
32 |
33 | # 是否允许私聊充值
34 | ALLOW_PRIVATE_REG = True
35 |
36 |
37 | #卡密开头, 可以是字母或数字, 卡密总长度固定为16位, 因此不推荐太长
38 | BEGIN = ''
39 |
40 |
41 | # --------------------群组管理相关---------------------
42 |
43 | # 授权到期后是否自动退群
44 | AUTO_LEAVE = True
45 |
46 | # 新群试用天数, 0天意味着机器人将不接受未授权群的入群邀请, 即便
47 | # 不允许重复试用, 如果群已经在试用列表且试用过期中仍然会被拒绝
48 | NEW_GROUP_DAYS = 0
49 |
50 | # 到期前的多少天开始提醒, 设置为0时将不会提醒
51 | REMIND_BRFORE_EXPIRED = 2
52 |
53 | # 到期后多少天退群, 仅当配置AUTO_LEAVE为True是此项有效, 设置为0则立即退群
54 | LEAVE_AFTER_DAYS = 0
55 |
56 | # 允许的最大群人数, 3000为不限制, 因为QQ群最大人数为3000
57 | # 进群后, 立刻检测一次人数(因为进群邀请不包含人数信息)
58 | MAX_GROUP_NUM = 70
59 |
60 | # 提醒/检查的时间间隔, 单位小时,
61 | # 值为1, 意为每小时检查一次, 注意实际每天的检查次数会向下取整
62 | FREQUENCY = 6
63 |
64 | # ----------------------授权列表与卡密列表----------------------------
65 |
66 | # 私聊使用授权列表时, 每页显示的群的个数,不推荐超过10, 否则消息会非常长
67 | GROUPS_IN_PAGE = 5
68 |
69 | # 私聊查看卡密列表时, 每页显示的卡密的个数,不推荐超过20
70 | CARDS_IN_PAGE = 10
71 |
72 | #----------------------发言文本---------------------------
73 |
74 | # 加入新群时的发言, 同时适用于50人以上群和50人以下群
75 | NEW_GROUP_MSG = '''
76 | Bot已成功加入本群, 发送帮助以获得更多信息
77 | '''.strip()
78 |
79 | # 离开群之前的发言
80 | GROUP_LEAVE_MSG = '''
81 | Bot即将退出本群
82 | 退群原因:
83 | '''.strip()
84 |
85 |
86 | # 提醒授权即将过期的发言, 在支持变量替换前不生效
87 | REMIND_MSG = '''
88 | 本群授权即将到期
89 | '''.strip()
90 |
91 | REG_HELP_GROUP = '''
92 | 实际使用时, 应当将abcd替换为您收到的16位卡密,12345替换为您的群号
93 | 授权系统指令示例, 发送方括号内的内容即可, 注意空格不可省略
94 | 群聊指令:
95 | [充值 abcd]为当前群充值
96 | [查询授权]查询当前群的授权状态
97 | [检查卡密 abcd]检查此卡密是否有效
98 | 私聊指令请私聊Bot发送[充值帮助]
99 | '''.strip()
100 | REG_HELP_PRIVATE = '''
101 | 实际使用时, 应当将abcd替换为您收到的16位卡密,12345替换为您的群号
102 | 授权系统指令示例, 发送方括号内的内容即可, 注意空格不可省略
103 | 私聊指令:
104 | [充值 abcd*12345]为指定群充值
105 | [查询授权 12345]查询指定群的授权信息
106 | [检查卡密 abcd]检查此卡密是否有效
107 | 群聊指令请在群聊中发送[充值帮助]
108 | '''.strip()
109 |
110 | ADMIN_HELP='''
111 | 实际使用时, 应当将abcd替换为16位卡密,12345替换为群号
112 | 超级管理员可用指令一览:
113 | [生成卡密 a*b]生成b张a天的卡密
114 | [卡密列表]查看卡密列表
115 | [授权列表]检查所有已授权群
116 | [变更授权 12345+1]为一个群变更授权时间, +或-
117 | [转移授权 12345*54321]转移已有群的授权
118 | [授权状态]查看Bot当前已加入群/已授权群的统计
119 | [清除授权 12345]完全移除一个群的授权并退群(如果配置了的话)
120 | [退群 12345]字面意思
121 | [变更所有授权 3]为所有已授权的群增加3天时间
122 | '''.strip()
123 |
124 | # --------------------加好友相关-------------------------------
125 | # 自动接受加好友请求, 请把加我为好友的方式设置为“需要验证信息”
126 | FRIEND_APPROVE = True
127 |
128 | # ------------------------日志-----------------------------
129 | # 日志, 记录卡密生成, 卡密使用, 群组加入, 群组退出, 群组过期, 好友添加
130 | LOG = True
131 |
132 | # 调试开关, 记录本系统所有信息
133 | DEBUG = False
134 |
135 |
--------------------------------------------------------------------------------
/vue/js/2.c47ca998.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[2],{"25b6":function(t,e,a){"use strict";var s=a("862c"),i=a.n(s);i.a},"862c":function(t,e,a){},ad61:function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("q-page",{staticClass:"doc-page"},[a("div",{staticClass:"q-pa-md"},[a("q-table",{attrs:{title:"Treats",data:t.data,columns:t.columns,"row-key":"key",selection:"multiple",selected:t.selected},on:{"update:selected":function(e){t.selected=e}},scopedSlots:t._u([{key:"top",fn:function(){return[a("div",{staticClass:"q-table__title row"},[t._v("\n 充值卡密管理\n ")]),a("q-space"),a("div",{staticClass:"row"},[a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"positive"},on:{click:t.output}},[t._v("导出")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"info"},on:{click:t.updateKey}},[t._v("刷新")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"secondary"},on:{click:function(e){t.isAdd=!0}}},[t._v("添加")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"primary"},on:{click:function(e){return t.modifySelect()}}},[t._v("修改")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"negative"},on:{click:t.deleteSelect}},[t._v("删除")])],1)]},proxy:!0}])})],1),a("q-dialog",{model:{value:t.isAdd,callback:function(e){t.isAdd=e},expression:"isAdd"}},[a("q-card",{staticClass:"flex flex-center"},[a("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[a("q-form",{staticClass:"q-gutter-md"},[a("q-input",{attrs:{filled:"",type:"number",label:"时长","lazy-rules":"",rules:[function(t){return null!==t&&t>0},"请正确的时长!"]},model:{value:t.duration,callback:function(e){t.duration=e},expression:"duration"}}),a("q-input",{attrs:{filled:"",type:"number",label:"数量","lazy-rules":"",rules:[function(t){return null!==t&&t>0},"请正确的数量!"]},model:{value:t.num,callback:function(e){t.num=e},expression:"num"}}),a("div",{attrs:{align:"center"}},[a("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:function(e){return t.onSubmit()}}})],1)],1)],1)])],1)],1)},i=[],l=a("18d6"),n={data(){return{selected:[],columns:[{name:"key",required:!0,label:"充值卡密",align:"left",field:t=>t.key,format:t=>""+t,sortable:!0},{name:"duration",align:"right",label:"时长",field:"duration",sortable:!0,sort:(t,e)=>parseInt(t,10)-parseInt(e,10)}],data:[{key:"error",duration:114},{key:"请清理浏览器缓存",duration:514}],isAdd:!1,num:1,duration:30}},methods:{updateKey(){this.$axios.get("/get/key?password="+l["a"].getItem("password")).then(t=>{this.data=t.data})},deleteSelect(){this.$q.dialog({title:"Confirm",message:"此操作将永久删除, 是否继续?",cancel:!0,persistent:!0}).onOk(()=>{}).onOk(()=>{this.selected.forEach(t=>{this.deleteItem(t)}),this.updateKey(),this.message("成功","删除完毕!")})},deleteItem(t){this.$axios.delete("del/key?key="+t.key)},modifySelect(){this.$q.dialog({title:"修改",message:"请填写注册码的授权时长",prompt:{model:"",isValid:t=>t.length>0,type:"number"},cancel:!0,persistent:!0}).onOk(t=>{this.selected.forEach(e=>{this.modifyItem(e,t)}),this.updateKey(),this.message("成功","修改完毕!")})},modifyItem(t,e){this.$axios.post("update/key?key="+t.key+"&duration="+e)},message(t,e){this.$q.dialog({title:t,message:e,html:!0})},output(){let t="";this.selected.forEach(e=>{t=t+"卡密:"+e.key+" 时长:"+e.duration+"天 "}),this.message("导出卡密",t)},onSubmit(){this.$axios.post("add/key?duration="+this.duration+"&num="+this.num).then(t=>{t&&(this.message("成功","添加完成!"),this.isAdd=!1,this.updateKey())}).catch(t=>{console.log(t)})}},created(){this.updateKey()}},o=n,r=(a("25b6"),a("2877")),c=a("9989"),d=a("eaac"),u=a("2c91"),m=a("9c40"),p=a("24e8"),f=a("f09f"),h=a("0378"),y=a("27f9"),b=a("eebe"),k=a.n(b),g=Object(r["a"])(o,s,i,!1,null,null,null);e["default"]=g.exports;k()(g,"components",{QPage:c["a"],QTable:d["a"],QSpace:u["a"],QBtn:m["a"],QDialog:p["a"],QCard:f["a"],QForm:h["a"],QInput:y["a"]})}}]);
--------------------------------------------------------------------------------
/vue/js/3.39f6dfb4.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[3],{"25b6":function(e,t,s){"use strict";var a=s("862c"),i=s.n(a);i.a},"862c":function(e,t,s){},ad61:function(e,t,s){"use strict";s.r(t);var a=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("q-page",{staticClass:"doc-page"},[s("div",{staticClass:"q-pa-md"},[s("q-table",{attrs:{title:"Treats",data:e.data,columns:e.columns,"row-key":"key",selection:"multiple",selected:e.selected,filter:e.filter},on:{"update:selected":function(t){e.selected=t}},scopedSlots:e._u([{key:"top",fn:function(){return[s("div",{staticClass:"q-table__title row"},[e._v("\n 充值卡密管理\n ")]),s("q-space"),s("div",{staticClass:"row"},[s("q-input",{attrs:{borderless:"",dense:"",debounce:"300",placeholder:"Search"},scopedSlots:e._u([{key:"append",fn:function(){return[s("q-icon",{attrs:{name:"search"}})]},proxy:!0}]),model:{value:e.filter,callback:function(t){e.filter=t},expression:"filter"}}),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"positive"},on:{click:e.output}},[e._v("导出")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"info"},on:{click:e.updateKey}},[e._v("刷新")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"secondary"},on:{click:function(t){e.isAdd=!0}}},[e._v("添加")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"primary"},on:{click:function(t){return e.modifySelect()}}},[e._v("修改")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"negative"},on:{click:e.deleteSelect}},[e._v("删除")])],1)]},proxy:!0}])})],1),s("q-dialog",{model:{value:e.isAdd,callback:function(t){e.isAdd=t},expression:"isAdd"}},[s("q-card",{staticClass:"flex flex-center"},[s("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[s("q-form",{staticClass:"q-gutter-md"},[s("q-input",{attrs:{filled:"",type:"number",label:"时长","lazy-rules":"",rules:[function(e){return null!==e&&e>0},"请正确的时长!"]},model:{value:e.duration,callback:function(t){e.duration=t},expression:"duration"}}),s("q-input",{attrs:{filled:"",type:"number",label:"数量","lazy-rules":"",rules:[function(e){return null!==e&&e>0},"请正确的数量!"]},model:{value:e.num,callback:function(t){e.num=t},expression:"num"}}),s("div",{attrs:{align:"center"}},[s("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:function(t){return e.onSubmit()}}})],1)],1)],1)])],1)],1)},i=[],l=s("18d6"),n={data(){return{filter:"",selected:[],columns:[{name:"key",required:!0,label:"充值卡密",align:"left",field:e=>e.key,format:e=>""+e,sortable:!0},{name:"duration",align:"right",label:"时长",field:"duration",sortable:!0,sort:(e,t)=>parseInt(e,10)-parseInt(t,10)}],data:[],isAdd:!1,num:1,duration:30}},methods:{updateKey(){this.$axios.get("/get/key?password="+l["a"].getItem("password")).then(e=>{this.data=e.data})},deleteSelect(){this.$q.dialog({title:"Confirm",message:"此操作将永久删除, 是否继续?",cancel:!0,persistent:!0}).onOk(()=>{}).onOk(()=>{this.selected.forEach(e=>{this.deleteItem(e)}),this.updateKey(),this.message("成功","删除完毕!")})},deleteItem(e){this.$axios.delete("del/key?key="+e.key)},modifySelect(){this.$q.dialog({title:"修改",message:"请填写注册码的授权时长",prompt:{model:"",isValid:e=>e.length>0,type:"number"},cancel:!0,persistent:!0}).onOk(e=>{this.selected.forEach(t=>{this.modifyItem(t,e)}),this.updateKey(),this.message("成功","修改完毕!")})},modifyItem(e,t){this.$axios.post("update/key?key="+e.key+"&duration="+t)},message(e,t){this.$q.dialog({title:e,message:t,html:!0})},output(){let e="";this.selected.forEach(t=>{e=e+"卡密:"+t.key+" 时长:"+t.duration+"天 "}),this.message("导出卡密",e)},onSubmit(){this.$axios.post("add/key?duration="+this.duration+"&num="+this.num).then(e=>{e&&(this.message("成功","添加完成!"),this.isAdd=!1,this.updateKey())}).catch(e=>{console.log(e)})}},created(){this.updateKey()}},o=n,r=(s("25b6"),s("2877")),c=s("9989"),d=s("eaac"),u=s("2c91"),m=s("27f9"),p=s("0016"),f=s("9c40"),h=s("24e8"),b=s("f09f"),y=s("0378"),k=s("eebe"),q=s.n(k),g=Object(r["a"])(o,a,i,!1,null,null,null);t["default"]=g.exports;q()(g,"components",{QPage:c["a"],QTable:d["a"],QSpace:u["a"],QInput:m["a"],QIcon:p["a"],QBtn:f["a"],QDialog:h["a"],QCard:b["a"],QForm:y["a"]})}}]);
--------------------------------------------------------------------------------
/vue/js/3.0457a8b4.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[3],{"25b6":function(e,t,s){"use strict";var a=s("862c"),i=s.n(a);i.a},"862c":function(e,t,s){},ad61:function(e,t,s){"use strict";s.r(t);var a=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("q-page",{staticClass:"doc-page"},[s("div",{staticClass:"q-pa-md"},[s("q-table",{attrs:{title:"Treats",data:e.data,columns:e.columns,"row-key":"key",selection:"multiple",selected:e.selected,filter:e.filter},on:{"update:selected":function(t){e.selected=t}},scopedSlots:e._u([{key:"top",fn:function(){return[s("div",{staticClass:"q-table__title row"},[e._v("\n 充值卡密管理\n ")]),s("q-space"),s("div",{staticClass:"row"},[s("q-input",{attrs:{borderless:"",dense:"",debounce:"300",placeholder:"Search"},scopedSlots:e._u([{key:"append",fn:function(){return[s("q-icon",{attrs:{name:"search"}})]},proxy:!0}]),model:{value:e.filter,callback:function(t){e.filter=t},expression:"filter"}}),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"positive"},on:{click:e.output}},[e._v("导出")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"info"},on:{click:e.updateKey}},[e._v("刷新")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"secondary"},on:{click:function(t){e.isAdd=!0}}},[e._v("添加")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"primary"},on:{click:function(t){return e.modifySelect()}}},[e._v("修改")]),s("q-btn",{staticClass:"q-mr-xs",attrs:{color:"negative"},on:{click:e.deleteSelect}},[e._v("删除")])],1)]},proxy:!0}])})],1),s("q-dialog",{model:{value:e.isAdd,callback:function(t){e.isAdd=t},expression:"isAdd"}},[s("q-card",{staticClass:"flex flex-center"},[s("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[s("q-form",{staticClass:"q-gutter-md"},[s("q-input",{attrs:{filled:"",type:"number",label:"时长","lazy-rules":"",rules:[function(e){return null!==e&&e>0},"请正确的时长!"]},model:{value:e.duration,callback:function(t){e.duration=t},expression:"duration"}}),s("q-input",{attrs:{filled:"",type:"number",label:"数量","lazy-rules":"",rules:[function(e){return null!==e&&e>0},"请正确的数量!"]},model:{value:e.num,callback:function(t){e.num=t},expression:"num"}}),s("div",{attrs:{align:"center"}},[s("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:function(t){return e.onSubmit()}}})],1)],1)],1)])],1)],1)},i=[],l=s("18d6"),n={data(){return{filter:"",selected:[],columns:[{name:"key",required:!0,label:"充值卡密",align:"left",field:e=>e.key,format:e=>""+e,sortable:!0},{name:"duration",align:"right",label:"时长",field:"duration",sortable:!0,sort:(e,t)=>parseInt(e,10)-parseInt(t,10)}],data:[],isAdd:!1,num:1,duration:30}},methods:{updateKey(){this.$axios.get("/get/key?password="+l["a"].getItem("password")).then(e=>{this.data=e.data})},deleteSelect(){this.$q.dialog({title:"Confirm",message:"此操作将永久删除, 是否继续?",cancel:!0,persistent:!0}).onOk(()=>{}).onOk(()=>{this.selected.forEach(e=>{this.deleteItem(e)}),this.updateKey(),this.message("成功","删除完毕!")})},deleteItem(e){this.$axios.delete("del/key?key="+e.key+"&password="+l["a"].getItem("password"))},modifySelect(){this.$q.dialog({title:"修改",message:"请填写注册码的授权时长",prompt:{model:"",isValid:e=>e.length>0,type:"number"},cancel:!0,persistent:!0}).onOk(e=>{this.selected.forEach(t=>{this.modifyItem(t,e)}),this.updateKey(),this.message("成功","修改完毕!")})},modifyItem(e,t){this.$axios.post("update/key?key="+e.key+"&duration="+t+"&password="+l["a"].getItem("password"))},message(e,t){this.$q.dialog({title:e,message:t,html:!0})},output(){let e="";this.selected.forEach(t=>{e=e+"卡密:"+t.key+" 时长:"+t.duration+"天 "}),this.message("导出卡密",e)},onSubmit(){this.$axios.post("add/key?duration="+this.duration+"&num="+this.num+"&password="+l["a"].getItem("password")).then(e=>{e&&(this.message("成功","添加完成!"),this.isAdd=!1,this.updateKey())}).catch(e=>{console.log(e)})}},created(){this.updateKey()}},o=n,r=(s("25b6"),s("2877")),c=s("9989"),d=s("eaac"),u=s("2c91"),m=s("27f9"),p=s("0016"),f=s("9c40"),h=s("24e8"),b=s("f09f"),y=s("0378"),g=s("eebe"),k=s.n(g),q=Object(r["a"])(o,a,i,!1,null,null,null);t["default"]=q.exports;k()(q,"components",{QPage:c["a"],QTable:d["a"],QSpace:u["a"],QInput:m["a"],QIcon:p["a"],QBtn:f["a"],QDialog:h["a"],QCard:b["a"],QForm:y["a"]})}}]);
--------------------------------------------------------------------------------
/web_server.py:
--------------------------------------------------------------------------------
1 | from hoshino import Service, priv
2 | from quart import request, Blueprint, jsonify, render_template
3 |
4 | import string
5 | import random
6 | import nonebot
7 | import hoshino
8 |
9 | from datetime import *
10 | from . import util
11 | from .constant import config
12 |
13 |
14 | auth = Blueprint('auth', __name__, url_prefix='/auth', template_folder="./vue", static_folder='./vue',
15 | static_url_path='')
16 | bot = nonebot.get_bot()
17 | app = bot.server_app
18 | manage_password = config.PASSWORD # 管理密码请在authMS.py中修改
19 |
20 |
21 | @auth.route('/')
22 | async def index():
23 | return await render_template("index.html")
24 |
25 |
26 | @auth.route('/api/login', methods=['POST'])
27 | async def login_auth():
28 | password = request.args.get('password')
29 | if password == manage_password:
30 | return 'success'
31 | return 'failed'
32 |
33 |
34 | @auth.route('/api/get/key', methods=['GET'])
35 | async def get_key():
36 | password = request.args.get('password')
37 | if password != manage_password:
38 | return 'failed'
39 | return jsonify(util.get_key_list())
40 |
41 |
42 | @auth.route('/api/add/key', methods=['POST'])
43 | async def add_key():
44 | password = request.args.get('password')
45 | if password != manage_password:
46 | return 'failed'
47 | if request.method == 'POST':
48 | duration = int(request.args.get('duration'))
49 | num = int(request.args.get('num'))
50 | for _ in range(num):
51 | util.add_key(duration)
52 | return 'success'
53 | return 'failed'
54 |
55 |
56 | @auth.route('/api/del/key', methods=['DELETE'])
57 | async def del_key():
58 | password = request.args.get('password')
59 | if password != manage_password:
60 | return 'failed'
61 | if request.method == 'DELETE':
62 | key = request.args.get('key')
63 | return jsonify(util.del_key(key))
64 |
65 |
66 | @auth.route('/api/update/key', methods=['POST'])
67 | async def update_key():
68 | password = request.args.get('password')
69 | if password != manage_password:
70 | return 'failed'
71 | key = request.args.get('key')
72 | duration = int(request.args.get('duration'))
73 | return jsonify(util.update_key(key, duration))
74 |
75 |
76 | @auth.route('/api/get/group', methods=['GET'])
77 | async def get_group():
78 | password = request.args.get('password')
79 | if password != manage_password:
80 | return 'failed'
81 | return jsonify(await util.get_authed_group_list())
82 |
83 |
84 | @auth.route('/api/add/group', methods=['POST'])
85 | @auth.route('/api/update/group', methods=['POST'])
86 | async def update_group():
87 | password = request.args.get('password')
88 | if password != manage_password:
89 | return 'failed'
90 | gid = int(request.args.get('gid'))
91 | time_change = int(request.args.get('duration'))
92 | await util.change_authed_time(gid, time_change)
93 | return 'success'
94 |
95 |
96 | @auth.route('/api/activate', methods=['POST'])
97 | async def activate_group():
98 | key = request.args.get('key')
99 | gid = request.args.get('gid')
100 | if await util.reg_group(gid, key):
101 | return 'success'
102 | return 'failed'
103 |
104 |
105 | @auth.route('/api/notify/group', methods=['POST'])
106 | async def notify_group():
107 | password = request.args.get('password')
108 | if password != manage_password:
109 | return 'failed'
110 | gid = int(request.args.get('gid'))
111 | msg = request.args.get('msg')
112 | if await util.notify_group(gid, msg):
113 | return 'success'
114 | return 'failed'
115 |
116 |
117 | @auth.route('/api/gun/group', methods=['POST'])
118 | async def gun_group():
119 | password = request.args.get('password')
120 | if password != manage_password:
121 | return 'failed'
122 | gid = int(request.args.get('gid'))
123 | if await util.gun_group(gid):
124 | return 'success'
125 | return 'failed'
126 |
--------------------------------------------------------------------------------
/vue/js/2.59ffaecf.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[2],{"2db1":function(t,e,a){"use strict";var s=a("39bc"),i=a.n(s);i.a},"39bc":function(t,e,a){},d684:function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("q-page",{staticClass:"doc-page"},[a("div",{staticClass:"q-pa-md"},[a("q-table",{attrs:{title:"Treats",data:t.data,columns:t.columns,"row-key":"gid",selection:"multiple",selected:t.selected,filter:t.filter},on:{"update:selected":function(e){t.selected=e}},scopedSlots:t._u([{key:"top",fn:function(){return[a("div",{staticClass:"q-table__title row"},[t._v("\n 授权群组管理\n ")]),a("q-space"),a("div",{staticClass:"row"},[a("q-input",{attrs:{borderless:"",dense:"",debounce:"300",placeholder:"Search"},scopedSlots:t._u([{key:"append",fn:function(){return[a("q-icon",{attrs:{name:"search"}})]},proxy:!0}]),model:{value:t.filter,callback:function(e){t.filter=e},expression:"filter"}}),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"info"},on:{click:t.updateGroup}},[t._v("刷新")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"secondary"},on:{click:function(e){return t.modifySelect()}}},[t._v("修改")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"primary"},on:{click:function(e){t.isAdd=!0}}},[t._v("添加授权")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"accent"},on:{click:t.broadcastSelect}},[t._v("提醒续费")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"negative"},on:{click:t.gunSelect}},[t._v("退群")])],1)]},proxy:!0}])})],1),a("q-dialog",{model:{value:t.isAdd,callback:function(e){t.isAdd=e},expression:"isAdd"}},[a("q-card",{staticClass:"flex flex-center"},[a("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[a("q-form",{staticClass:"q-gutter-md"},[a("q-input",{attrs:{filled:"",type:"number",label:"群号","lazy-rules":"",rules:[function(t){return null!==t&&t>0},"请正确的群号!"]},model:{value:t.addGid,callback:function(e){t.addGid=e},expression:"addGid"}}),a("q-input",{attrs:{filled:"",type:"number",label:"时长","lazy-rules":"",rules:[function(t){return null!==t&&t>0},"请正确的时长!"]},model:{value:t.duration,callback:function(e){t.duration=e},expression:"duration"}}),a("div",{attrs:{align:"center"}},[a("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:function(e){return t.onSubmit()}}})],1)],1)],1)])],1)],1)},i=[],o=a("18d6"),l={data(){return{filter:"",selected:[],columns:[{name:"gid",required:!0,label:"群号",align:"left",field:t=>t.gid,format:t=>""+t,sortable:!0,sort:(t,e)=>parseInt(t,20)-parseInt(e,20)},{name:"groupName",required:!0,label:"群名称",align:"left",field:t=>t.groupName,format:t=>""+t},{name:"deadline",align:"right",label:"截至日期",field:"deadline",sortable:!0}],data:[],isAdd:!1,addGid:123456,duration:30}},methods:{updateGroup(){this.$axios.get("/get/group?password="+o["a"].getItem("password")).then(t=>{this.data=t.data,console.log(t.data)})},modifySelect(){this.$q.dialog({title:"修改",message:"请填写群组增加(减少)授权时长",prompt:{model:"",isValid:t=>t.length>0,type:"number"},cancel:!0,persistent:!0}).onOk(t=>{this.selected.forEach(e=>{this.modifyItem(e,t)}),this.updateGroup(),this.message("成功","修改完毕!")})},modifyItem(t,e){this.$axios.post("update/group?gid="+t.gid+"&duration="+e)},message(t,e){this.$q.dialog({title:t,message:e,html:!0})},onSubmit(){this.$axios.post("add/group?gid="+this.addGid+"&duration="+this.duration).then(t=>{t&&(this.message("成功","添加完成!"),this.isAdd=!1,this.updateGroup())}).catch(t=>{console.log(t)})},broadcastSelect(){this.$q.dialog({title:"请填写推送语",prompt:{model:"您的机器人即将到期,请及时续费!",type:"text"},cancel:!0}).onOk(t=>{this.selected.forEach(e=>{this.broadcastItem(e,t)})})},broadcastItem(t,e){this.$axios.post("/notify/group?gid="+t.gid+"&msg="+e).then(e=>{"failed"===e.data?this.$q.notify({message:"群"+t.gid+"推送失败!",position:"top",color:"purple"}):this.$q.notify({message:"群"+t.gid+"推送成功!",position:"top",color:"green"})}).catch(()=>{this.$q.notify({message:"群"+t.gid+"推送失败!",position:"top",color:"purple"})})},gunSelect(){this.$q.dialog({title:"确认",message:"确定退出群聊吗?",cancel:!0}).onOk(t=>{this.selected.forEach(t=>{this.gunItem(t)}),this.updateGroup()})},gunItem(t){this.$axios.post("/gun/group?gid="+t.gid).then(e=>{"failed"===e.data?this.$q.notify({message:"群"+t.gid+"退出失败!",position:"top",color:"purple"}):this.$q.notify({message:"群"+t.gid+"退出成功!",position:"top",color:"green"})}).catch(()=>{this.$q.notify({message:"群"+t.gid+"推送失败!",position:"top",color:"purple"})})}},created(){this.updateGroup()}},n=l,r=(a("2db1"),a("2877")),d=a("9989"),c=a("eaac"),u=a("2c91"),p=a("27f9"),m=a("0016"),g=a("9c40"),f=a("24e8"),h=a("f09f"),b=a("0378"),q=a("eebe"),y=a.n(q),x=Object(r["a"])(n,s,i,!1,null,null,null);e["default"]=x.exports;y()(x,"components",{QPage:d["a"],QTable:c["a"],QSpace:u["a"],QInput:p["a"],QIcon:m["a"],QBtn:g["a"],QDialog:f["a"],QCard:h["a"],QForm:b["a"]})}}]);
--------------------------------------------------------------------------------
/vue/js/2.4383789c.js:
--------------------------------------------------------------------------------
1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([[2],{"2db1":function(t,e,a){"use strict";var s=a("39bc"),i=a.n(s);i.a},"39bc":function(t,e,a){},d684:function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("q-page",{staticClass:"doc-page"},[a("div",{staticClass:"q-pa-md"},[a("q-table",{attrs:{title:"Treats",data:t.data,columns:t.columns,"row-key":"gid",selection:"multiple",selected:t.selected,filter:t.filter},on:{"update:selected":function(e){t.selected=e}},scopedSlots:t._u([{key:"top",fn:function(){return[a("div",{staticClass:"q-table__title row"},[t._v("\n 授权群组管理\n ")]),a("q-space"),a("div",{staticClass:"row"},[a("q-input",{attrs:{borderless:"",dense:"",debounce:"300",placeholder:"Search"},scopedSlots:t._u([{key:"append",fn:function(){return[a("q-icon",{attrs:{name:"search"}})]},proxy:!0}]),model:{value:t.filter,callback:function(e){t.filter=e},expression:"filter"}}),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"info"},on:{click:t.updateGroup}},[t._v("刷新")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"secondary"},on:{click:function(e){return t.modifySelect()}}},[t._v("修改")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"primary"},on:{click:function(e){t.isAdd=!0}}},[t._v("添加授权")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"accent"},on:{click:t.broadcastSelect}},[t._v("提醒续费")]),a("q-btn",{staticClass:"q-mr-xs",attrs:{color:"negative"},on:{click:t.gunSelect}},[t._v("退群")])],1)]},proxy:!0}])})],1),a("q-dialog",{model:{value:t.isAdd,callback:function(e){t.isAdd=e},expression:"isAdd"}},[a("q-card",{staticClass:"flex flex-center"},[a("div",{staticClass:"q-pa-md",staticStyle:{width:"480px"}},[a("q-form",{staticClass:"q-gutter-md"},[a("q-input",{attrs:{filled:"",type:"number",label:"群号","lazy-rules":"",rules:[function(t){return null!==t&&t>0},"请正确的群号!"]},model:{value:t.addGid,callback:function(e){t.addGid=e},expression:"addGid"}}),a("q-input",{attrs:{filled:"",type:"number",label:"时长","lazy-rules":"",rules:[function(t){return null!==t&&t>0},"请正确的时长!"]},model:{value:t.duration,callback:function(e){t.duration=e},expression:"duration"}}),a("div",{attrs:{align:"center"}},[a("q-btn",{attrs:{label:"Submit",color:"primary"},on:{click:function(e){return t.onSubmit()}}})],1)],1)],1)])],1)],1)},i=[],o=a("18d6"),l={data(){return{filter:"",selected:[],columns:[{name:"gid",required:!0,label:"群号",align:"left",field:t=>t.gid,format:t=>""+t,sortable:!0,sort:(t,e)=>parseInt(t,20)-parseInt(e,20)},{name:"groupName",required:!0,label:"群名称",align:"left",field:t=>t.groupName,format:t=>""+t},{name:"deadline",align:"right",label:"截至日期",field:"deadline",sortable:!0}],data:[],isAdd:!1,addGid:123456,duration:30}},methods:{updateGroup(){this.$axios.get("/get/group?password="+o["a"].getItem("password")).then(t=>{this.data=t.data,console.log(t.data)})},modifySelect(){this.$q.dialog({title:"修改",message:"请填写群组增加(减少)授权时长",prompt:{model:"",isValid:t=>t.length>0,type:"number"},cancel:!0,persistent:!0}).onOk(t=>{this.selected.forEach(e=>{this.modifyItem(e,t)}),this.updateGroup(),this.message("成功","修改完毕!")})},modifyItem(t,e){this.$axios.post("update/group?gid="+t.gid+"&duration="+e+"&password="+o["a"].getItem("password"))},message(t,e){this.$q.dialog({title:t,message:e,html:!0})},onSubmit(){this.$axios.post("add/group?gid="+this.addGid+"&duration="+this.duration+"&password="+o["a"].getItem("password")).then(t=>{t&&(this.message("成功","添加完成!"),this.isAdd=!1,this.updateGroup())}).catch(t=>{console.log(t)})},broadcastSelect(){this.$q.dialog({title:"请填写推送语",prompt:{model:"您的机器人即将到期,请及时续费!",type:"text"},cancel:!0}).onOk(t=>{this.selected.forEach(e=>{this.broadcastItem(e,t)})})},broadcastItem(t,e){this.$axios.post("/notify/group?gid="+t.gid+"&msg="+e+"&password="+o["a"].getItem("password")).then(e=>{"failed"===e.data?this.$q.notify({message:"群"+t.gid+"推送失败!",position:"top",color:"purple"}):this.$q.notify({message:"群"+t.gid+"推送成功!",position:"top",color:"green"})}).catch(()=>{this.$q.notify({message:"群"+t.gid+"推送失败!",position:"top",color:"purple"})})},gunSelect(){this.$q.dialog({title:"确认",message:"确定退出群聊吗?",cancel:!0}).onOk(t=>{this.selected.forEach(t=>{this.gunItem(t)}),this.updateGroup()})},gunItem(t){this.$axios.post("/gun/group?gid="+t.gid+"&password="+o["a"].getItem("password")).then(e=>{"failed"===e.data?this.$q.notify({message:"群"+t.gid+"退出失败!",position:"top",color:"purple"}):this.$q.notify({message:"群"+t.gid+"退出成功!",position:"top",color:"green"})}).catch(()=>{this.$q.notify({message:"群"+t.gid+"推送失败!",position:"top",color:"purple"})})}},created(){this.updateGroup()}},n=l,r=(a("2db1"),a("2877")),d=a("9989"),c=a("eaac"),p=a("2c91"),u=a("27f9"),m=a("0016"),g=a("9c40"),f=a("24e8"),h=a("f09f"),b=a("0378"),q=a("eebe"),y=a.n(q),x=Object(r["a"])(n,s,i,!1,null,null,null);e["default"]=x.exports;y()(x,"components",{QPage:d["a"],QTable:c["a"],QSpace:p["a"],QInput:u["a"],QIcon:m["a"],QBtn:g["a"],QDialog:f["a"],QCard:h["a"],QForm:b["a"]})}}]);
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # authMS
2 |
3 | 适用于HoshinoBot v2的授权插件, 可控制cqhttp的事件过滤器. 大部分功能以天枢授权为蓝本而开发. 本插件另有带有web服务的页面, 如果需要请按照注释开启web. 本项目主体框架由[wdvxdr1123](https://github.com/wdvxdr1123)构建, [火龙](https://github.com/xhl6666)添加了一些重要功能.
4 |
5 |
6 | 本授权系统的开发调试过程均以[go-cqhttp](https://github.com/Mrs4s/go-cqhttp)进行, 请使用v0.9.24-fix2以上版本的[go-cqhttp](https://github.com/Mrs4s/go-cqhttp); 不支持v1.0.0以上版本的[go-cqhttp](https://github.com/Mrs4s/go-cqhttp).
7 |
8 | v0.2.0及之后使用事件过滤器来进行授权控制, [cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai)暂时不支持此特性, 如果使用[cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai)请从release下载v0.1.5-fix1版本, 请使用v0.2.2.5以上版本的cqhttp-mirai, 否则Bot进群等部分功能将不可用.
9 |
10 | 本项目使用GPL-3.0协议, 您使用本插件所造成的一切财产损失或其他形式损失, 均与开发者无关, 开发者不承担任何相关责任.
11 |
12 |
13 | 相关链接:
14 | 本项目地址: https://github.com/pcrbot/authMS
15 |
16 | HoshinoBot项目地址: https://github.com/Ice-Cirno/HoshinoBot
17 |
18 | ## 特点
19 | * **无GUI** ,不依赖Native环境, 依托于nonebot架构, 对不同的操作系统友好
20 | * 自定义新群试用
21 | * 自定义到期后是否自动退群
22 | * 支持自定义入群发言, 退群发言
23 | * 自定义每日检查频率(最低为每日一次), 可手动快速检查
24 | * 支持无卡密操作, 直接对一个群的授权进行修改/清零
25 | * 支持批量操作(网页/私聊)
26 | * 支持手动提醒群续费(网页)
27 | * 可自动接受好友请求
28 | * 自动检查人数
29 | * 卡密部分自定义
30 | * 授权检查范围广, 支持以下情形:
31 | * 被拉近50人以下的群(因为默认不需要被邀请者同意)
32 | * 超过50人群, 需要机器人同意的群
33 | * 机器人掉线期间被邀请加入的群
34 | * 维护者手动登录机器人帐号加入的群
35 |
36 | 以上情况加入的群聊, 均会收到授权系统的自动检测, 并可自定义退群或开启试用等操作。
37 |
38 | ## 指令示例
39 | **注意, 以下指令中的空格均不可省略**
40 | ### 仅限超级管理员私聊的指令
41 | * 【生成卡密 31*5】生成5张31天的卡密
42 | * 【卡密列表】查看已有卡密的信息,后跟数字来查看对应页数
43 | * 【授权列表】查看所有授权群的信息,后跟数字来查看对应页数
44 | * 【管理员帮助】查看管理员指令
45 | * 【快速检查】立刻检查群的授权, 检查方式与定时任务一样
46 | * 【刷新事件过滤器】手动刷新事件过滤器
47 |
48 | ### 仅限超级管理员的指令
49 | * 【变更授权 123456789+5】为群123456789增加5天授权, 也可以是减
50 | * 【转移授权 123456*987654】将群123456的剩余时间转移至群987654
51 | * 【授权状态】查询此机器人授权信息的统计, 仅限超级管理员
52 | * 【清除授权 987654】清除群987654的全部授权, 并自动退群(如果配置了的话)
53 | * 【退群 987654】命令退出群聊987654, 但并不清除剩余授权时间
54 | * 【变更所有授权 3】为所有已有授权的群增加3天授权时间
55 | * 【不检查人数 987654】不检查群987654的人数是否超标, 直接在群聊中发送则不必附加群号
56 | * 【不检查授权 987654】不检查群987654的授权是否过期, 直接在群聊中发送则不必附加群号
57 | * 【添加白名单 987654】不检查群987654的授权是否过期以及人数是否超标, 直接在群聊中发送则不必附加群号
58 | * 【移除白名单 987654】将群987654从白名单中移出
59 | * 【全部白名单】查询全部白名单信息
60 |
61 |
62 | ### 通用指令
63 | * 【检验卡密 abcdefghijklemop】检查卡密的有效性
64 | * 【充值 abcdefghijklemop】群聊使用,为本群充值
65 | * 【充值 abcdefghijklemop*123456789】为群123456789充值
66 | * 【充值帮助】查看充值帮助内容
67 |
68 | ## 开始使用
69 |
70 | 1. 在HoshinoBot的modules目录下克隆本项目:
71 | ```
72 | git clone https://github.com/pcrbot/authMS.git
73 | ```
74 | 2. 安装依赖, 如下载过慢建议清华镜像:
75 | ```
76 | pip install -r requirements.txt
77 | ```
78 | 3. 以`filter.json`放入go-cqhttp目录下.
79 | 4. 在HoshinoBot统一配置目录下保存配置信息,命名为`authMS.py`, 已提供配置样板`authMS.py.example`, 按照注释修改为您需要的配置. 请注意, 必须配置事件过滤器路径.
80 |
81 |
82 | ## 其他
83 | * 日志功能并非Hoshino内置的日志记录, 而是作为单独文件记录. 默认保存位置`log/authMS.log`, 默认只记录卡密使用, 加群退群被踢.
84 | * 支持本机多个机器人数据互通, 详情参考`authMS.py.example`中的注释, SQLite是一个本地化的数据库, 因此不支持网络, 配置目录请注意使用斜杠`/`.
85 |
86 | * 如果您是初次使用authMS, 且希望配置为到期自动退群, 建议保持默认`ENABLE_AUTH`为0, 待完成全部现有群授权后, 再修改.
87 | ## 贡献
88 | [GitHub@wdvxdr1123](https://github.com/wdvxdr1123)
89 |
90 | [GitHub@xhl6699](https://github.com/xhl6666)
91 |
92 | [GitHub@var](https://github.com//var-mixer)
93 |
94 | ## 更新日志
95 |
96 |
97 | ### v0.2.1
98 | 更新时间:2020/9/27
99 |
100 | 此版本主要进行风控规避[#16](https://github.com/pcrbot/authMS/issues/16)
101 | * 配置文件新增以下参数:
102 | * `BEGIN`, 用以自定义卡密开头
103 | * 功能变更与调整:
104 | * 发言与退群之间间隔增大, 并限速调用
105 | * 人数检查纳入日志的记录范围
106 | * 卡密开始部分支持自定义, 总长度不变, 例如PCRkPTNeGFilybVn
107 | * 进群之后不会立刻退群, 而是等待下一次自动检查任务时退群
108 |
109 |
110 | ### v0.2.0
111 | 更新时间:2020/9/15
112 |
113 | 此版本主要针对事件过滤器重构
114 | * 配置文件新增以下参数:
115 | * `EVENT_FILTER`, 事件过滤器配置文件目录.
116 | * 功能变更与调整:
117 | * 直接内置了编译好的vue文件
118 | * 指令变更:
119 | * 新增管理员指令【刷新事件过滤器】
120 |
121 | ### v0.1.5
122 | 更新时间:2020/9/9
123 |
124 | 此版本主要新增日志、人数限制、白名单系统、以及网页授权管理
125 | * 配置文件新增以下参数:
126 | * `DEBUG`, 默认关闭, 开启后会详细记录所有的日志.
127 | * `LOG`, 默认开启, 记录重要日志信息, 如进群退群卡密使用
128 | * `LEAVE_AFTER_DAYS`, 在授权到期X天后再退群(仅当配置`AUTO_LEAVE`为True时有效)
129 | * `MAX_GROUP_NUM`, 最大允许的群人数
130 | * 功能变更与调整:
131 | * 新增人数检查功能, 在群人数超标时可以根据配置自动退群或者发送提醒
132 | * 新增白名单功能, 有三种类型, 不检查授权、不检查人数和全部不检查(注意查询授权出来的的结果不会变, 只是不会自动退群或停止响应消息)
133 | * 更新网页授权管理(by [wdvxdr1123](https://github.com/wdvxdr1123))
134 | * 退群统一由`util.gun_group()`处理, 退群时将会发送退群原因
135 | * 修改配置文件中一些参数的默认值
136 | * 指令变更:
137 | * 移除无效指令【帮助充值】
138 | * 新增管理员指令【不检查人数】【不检查授权】【添加白名单】【全部白名单】【移除白名单】
139 | * 错误修复:
140 | * 修复管理员帮助无法查看的问题
141 | * 修复web授权管理的漏洞, POST请求没有鉴权(by [wdvxdr1123](https://github.com/wdvxdr1123))
142 |
143 |
144 |
145 |
146 |
147 | ### v0.1.4
148 | 更新时间:2020/8/26
149 | * 配置文件新增以下参数:
150 | * `ENABLE_WEB`, 是否同时启用web管理(之前需在`__init__.py`中配置)
151 | * `PASSWORD`, web管理密码(之前需在`web_server.py`配置)
152 | * `REG_HELP_GROUP`, 群聊充值帮助文本
153 | * `REG_HELP_PRIVATE`, 私聊充值帮助文本
154 | * `ADMIN_HELP`, 给管理员的帮助文本
155 | * `FRIEND_APPROVE`, 是否自动接受加好友的请求
156 | * 文件结构调整
157 | * 新增指令【快速检查】【充值帮助】【管理员帮助】
158 | * 新增特性: 处理加好友事件
159 | * 新增特性: 与管理后台分离的激活界面(感谢[A-kirami](https://github.com/A-kirami)
160 |
161 | ### v0.1.3
162 | 更新时间:2020/8/22
163 | * 添加`nonebot_plugin.py.example`, 可以实现对yobot的授权控制(虽然早就有了)
164 | * 配置文件新增以下参数:
165 | * `ENABLE_AUTH`, 授权系统自动检查/退群总开关
166 | * `FREQUENCY`, 控制授权系统自动检查的频率
167 | * `GROUPS_IN_PAGE`, 控制每页显示的群的条数
168 | * `CARDS_IN_PAGE`, 控制每页显示的卡密条数
169 | * 移除配置项目:`ALLOW_PRIVETE_CHECK`
170 | * 优化获取全部授权列表时的参数, 大幅降低API调用次数(尤其是在群较多时)
171 |
172 | ### v0.1.2
173 | 更新时间:2020/8/18
174 |
175 | > 本次更新合并了[HoshinoAuthorizeSystem](https://github.com/wdvxdr1123/HoshinoAuthorizeSystem)的功能
176 |
177 | * 新增退群,广播功能
178 | * 现在获取已授权群的时时候,会返回群名了
179 | * 使用指令所需权限不足时均增加了提示
180 | * 精简了转移授权部分的代码
181 |
182 | ### v0.1.1
183 | 更新时间:2020/8/18
184 | * 修正[#1](https://github.com/pcrbot/authMS/issues/1), 在设置新群使用天数为0时, 自动退群设置不起作用而直接退群
185 | * 新增指令, 为所有已有授权的群修改时间
186 |
187 | ### v0.1.0
188 | 更新时间:2020/8/15
189 | * 初版发布
190 |
--------------------------------------------------------------------------------
/vue/js/app.64de2849.js:
--------------------------------------------------------------------------------
1 | (function(e){function t(t){for(var r,o,u=t[0],l=t[1],c=t[2],s=0,p=[];sPromise.all([n.e(0),n.e(4)]).then(n.bind(null,"713b")),children:[{path:"",component:()=>Promise.all([n.e(0),n.e(3)]).then(n.bind(null,"8b24"))},{path:"login",component:()=>Promise.all([n.e(0),n.e(8)]).then(n.bind(null,"013f"))},{path:"regkey",component:()=>Promise.all([n.e(0),n.e(2)]).then(n.bind(null,"ad61")),meta:{requireAuth:!0}},{path:"activate",component:()=>Promise.all([n.e(0),n.e(5)]).then(n.bind(null,"7aa6"))},{path:"group",component:()=>Promise.all([n.e(0),n.e(7)]).then(n.bind(null,"d684")),meta:{requireAuth:!0}}]}];w.push({path:"*",component:()=>Promise.all([n.e(0),n.e(6)]).then(n.bind(null,"e51e"))});var P=w;a["a"].use(y["a"]);var j=function(){const e=new y["a"]({scrollBehavior:()=>({x:0,y:0}),routes:P,mode:"hash",base:""});return e.beforeEach((e,t,n)=>{e.meta.requireAuth&&!c["a"].getItem("login")?n({path:"/login",query:{to:e.path}}):n()}),e},O=function(){return S.apply(this,arguments)};function S(){return S=o()((function*(){const e="function"===typeof j?yield j({Vue:a["a"]}):j,t={router:e,render:e=>e(v),el:"#q-app"};return{app:t,router:e}})),S.apply(this,arguments)}var x=n("bc3a"),E=n.n(x),_=n("4bde"),q=n("2a19");E.a.interceptors.request.use(e=>(e.baseURL="api/",e.headers["Content-Type"]||(e.headers["Content-Type"]="application/json"),e)),E.a.interceptors.response.use(e=>Promise.resolve(e),e=>{switch(e.response.status){case 401:window.location.href="/#/logout"}return q["a"].create({message:`[${e.response.statusText}]${e.response.data}`,position:"top",color:"negative"}),Promise.reject(e)});var A=Object(_["boot"])(({Vue:e})=>{e.prototype.$axios=E.a});const C="";function T(){return k.apply(this,arguments)}function k(){return k=o()((function*(){const{app:e,router:t}=yield O();let n=!1;const r=e=>{n=!0;const r=Object(e)===e?t.resolve(e).route.fullPath:e;window.location.href=r},o=window.location.href.replace(window.location.origin,""),i=[A];for(let l=0;!1===n&&lPromise.all([n.e(0),n.e(5)]).then(n.bind(null,"713b")),children:[{path:"",component:()=>Promise.all([n.e(0),n.e(4)]).then(n.bind(null,"8b24"))},{path:"login",component:()=>Promise.all([n.e(0),n.e(8)]).then(n.bind(null,"013f"))},{path:"regkey",component:()=>Promise.all([n.e(0),n.e(3)]).then(n.bind(null,"ad61")),meta:{requireAuth:!0}},{path:"activate",component:()=>Promise.all([n.e(0),n.e(6)]).then(n.bind(null,"7aa6"))},{path:"group",component:()=>Promise.all([n.e(0),n.e(2)]).then(n.bind(null,"d684")),meta:{requireAuth:!0}}]}];P.push({path:"*",component:()=>Promise.all([n.e(0),n.e(7)]).then(n.bind(null,"e51e"))});var j=P;a["a"].use(w["a"]);var O=function(){const e=new w["a"]({scrollBehavior:()=>({x:0,y:0}),routes:j,mode:"hash",base:""});return e.beforeEach((e,t,n)=>{e.meta.requireAuth&&!c["a"].getItem("login")?n({path:"/login",query:{to:e.path}}):n()}),e},S=function(){return x.apply(this,arguments)};function x(){return x=o()((function*(){const e="function"===typeof O?yield O({Vue:a["a"]}):O,t={router:e,render:e=>e(y),el:"#q-app"};return{app:t,router:e}})),x.apply(this,arguments)}var E=n("bc3a"),_=n.n(E),q=n("4bde");_.a.interceptors.request.use(e=>(e.baseURL="api/",e.headers["Content-Type"]||(e.headers["Content-Type"]="application/json"),e)),_.a.interceptors.response.use(e=>Promise.resolve(e),e=>{switch(e.response.status){case 401:window.location.href="/#/logout"}return f["a"].create({message:`[${e.response.statusText}]${e.response.data}`,position:"top",color:"negative"}),Promise.reject(e)});var A=Object(q["boot"])(({Vue:e})=>{e.prototype.$axios=_.a});const C="";function T(){return k.apply(this,arguments)}function k(){return k=o()((function*(){const{app:e,router:t}=yield S();let n=!1;const r=e=>{n=!0;const r=Object(e)===e?t.resolve(e).route.fullPath:e;window.location.href=r},o=window.location.href.replace(window.location.origin,""),i=[A];for(let l=0;!1===n&&lPromise.all([n.e(0),n.e(5)]).then(n.bind(null,"713b")),children:[{path:"",component:()=>Promise.all([n.e(0),n.e(4)]).then(n.bind(null,"8b24"))},{path:"login",component:()=>Promise.all([n.e(0),n.e(8)]).then(n.bind(null,"013f"))},{path:"regkey",component:()=>Promise.all([n.e(0),n.e(3)]).then(n.bind(null,"ad61")),meta:{requireAuth:!0}},{path:"activate",component:()=>Promise.all([n.e(0),n.e(6)]).then(n.bind(null,"7aa6"))},{path:"group",component:()=>Promise.all([n.e(0),n.e(2)]).then(n.bind(null,"d684")),meta:{requireAuth:!0}}]}];P.push({path:"*",component:()=>Promise.all([n.e(0),n.e(7)]).then(n.bind(null,"e51e"))});var j=P;a["a"].use(w["a"]);var O=function(){const e=new w["a"]({scrollBehavior:()=>({x:0,y:0}),routes:j,mode:"hash",base:""});return e.beforeEach((e,t,n)=>{e.meta.requireAuth&&!c["a"].getItem("login")?n({path:"/login",query:{to:e.path}}):n()}),e},S=function(){return x.apply(this,arguments)};function x(){return x=o()((function*(){const e="function"===typeof O?yield O({Vue:a["a"]}):O,t={router:e,render:e=>e(y),el:"#q-app"};return{app:t,router:e}})),x.apply(this,arguments)}var E=n("bc3a"),_=n.n(E),q=n("4bde");_.a.interceptors.request.use(e=>(e.baseURL="api/",e.headers["Content-Type"]||(e.headers["Content-Type"]="application/json"),e)),_.a.interceptors.response.use(e=>Promise.resolve(e),e=>{switch(e.response.status){case 401:window.location.href="/#/logout"}return f["a"].create({message:`[${e.response.statusText}]${e.response.data}`,position:"top",color:"negative"}),Promise.reject(e)});var A=Object(q["boot"])(({Vue:e})=>{e.prototype.$axios=_.a});const C="";function T(){return k.apply(this,arguments)}function k(){return k=o()((function*(){const{app:e,router:t}=yield S();let n=!1;const r=e=>{n=!0;const r=Object(e)===e?t.resolve(e).route.fullPath:e;window.location.href=r},o=window.location.href.replace(window.location.origin,""),i=[A];for(let l=0;!1===n&&l pages_all:
61 | await session.finish(f'没有那么多页, 当前共有卡密共{length}条, 共{pages_all}页')
62 | if page <= 0:
63 | await session.finish('请输入正确的页码')
64 |
65 | if not length:
66 | await session.finish('无可用卡密信息')
67 |
68 | msg = '======卡密列表======\n'
69 | i = 0
70 | for items in key_list:
71 | i = i + 1
72 | if i < (page-1)*cards_in_page+1 or i > page*cards_in_page:
73 | continue
74 | msg += '卡密:' + items['key'] + '\n时长:' + str(items['duration']) + '天\n'
75 | msg += f'第{page}页, 共{pages_all}页\n发送卡密列表+页码以查询其他页'
76 | await session.send(msg)
77 |
78 |
79 | @on_command('充值', only_to_me=False)
80 | async def reg_group_chat(session: CommandSession):
81 |
82 | key = session.get('key', prompt="请输入卡密(直接发送)")
83 | if len(key) != 16:
84 | session.finish('卡密错误,请检查后重新开始。')
85 | days = util.query_key(key)
86 | if days == 0:
87 | session.finish("卡密无效,请检查后重新开始。")
88 | if session.event.detail_type == 'private':
89 | # 私聊充值
90 | end = "\n如果我尚未加入您的群聊,您可以邀请我进群~"
91 | gid = session.get('group_id', prompt=f"卡密有效,时长{days}天。\n请输入群号(直接发送)")
92 | if not gid.isdigit():
93 | session.finish("群号错误,请重新开始。")
94 | else:
95 | end = ""
96 | gid = session.event.group_id
97 |
98 | if gid is None:
99 | return
100 | if session.event.detail_type == 'private':
101 | sid = session.event.self_id
102 | group_name = await util.get_group_name(sid, gid)
103 | msg = f"""[CQ:image,file={get_group_acatar_url(int(gid))}]\n
104 | 群号:{gid}
105 | 群名:{group_name}
106 | ※请确认以上信息后,回复“确认”来完成本次操作。
107 | ※回复其他内容会终止。
108 | """
109 | ensure = session.get("ensure", prompt=msg)
110 | if ensure != "确认":
111 | session.finish("已取消本次充值")
112 | days = util.query_key(key)
113 | result = await util.reg_group(gid, key)
114 | print(result)
115 | if not result:
116 | # 充值失败
117 | msg = '卡密无效, 请检查是否有误或已被使用, 如果无此类问题请联系发卡方'
118 | else:
119 | nickname = await util.get_nickname(user_id=session.event.user_id)
120 | log_info = f'{nickname}({session.event.user_id})使用了卡密{key}\n为群{gid}成功充值{days}天'
121 | util.log(log_info, 'card_use')
122 | await util.notify_master(log_info)
123 | msg = await util.process_group_msg(gid, result, '充值成功\n', end)
124 | session.finish(msg)
125 |
126 |
127 | @on_command('检验卡密',aliases=('检查卡密'), only_to_me=False)
128 | async def check_card_chat(session):
129 | if not session.current_arg:
130 | await session.finish('检验卡密请发送“检验卡密 卡密”哦~')
131 | else:
132 | origin = session.current_arg.strip()
133 | pattern = re.compile(r'^(\w{16})$')
134 | m = pattern.match(origin)
135 | if m is None:
136 | await session.finish('格式输错了啦憨批!请按照“检验卡密 卡密”进行输入!')
137 | key = m.group(1)
138 | if duration := util.query_key(key):
139 | util.log(f'{session.event.user_id}检查卡密{key},有效期{duration}天')
140 | await session.finish(f'该卡密有效!\n授权时长:{duration}天')
141 | else:
142 | util.log(f'{session.event.user_id}检查卡密{key},无效')
143 | await session.finish(f'该卡密无效!')
144 |
145 |
146 | @on_command('查询授权', only_to_me=False)
147 | async def auth_query_chat(session):
148 | uid = session.event.user_id
149 | if not session.current_arg:
150 | # 无参,检查群聊与否
151 | if session.event.detail_type == 'private':
152 | # 私聊禁止无参数查询授权
153 | await session.finish('私聊查询授权请发送“查询授权 群号”来进行指定群的授权查询(请注意空格)')
154 | return
155 | else:
156 | # 群聊,获取gid
157 | gid = session.event.group_id
158 | else:
159 | # 有参数,检查权限
160 | if uid not in SUPERUSERS:
161 | await session.finish('抱歉,您的权限不足')
162 | return
163 | else:
164 | # 权限为超级管理员
165 | gid = session.current_arg.strip()
166 | if not gid.isdigit():
167 | await session.finish('请输入正确的群号')
168 | return
169 |
170 | result = util.check_group(gid)
171 | if not result:
172 | msg = '此群未获得授权'
173 | else:
174 | msg = await util.process_group_msg(gid, result, title='授权查询结果\n')
175 | await session.finish(msg)
176 |
--------------------------------------------------------------------------------
/group.py:
--------------------------------------------------------------------------------
1 | from nonebot import on_request, on_notice, on_command, NoticeSession
2 | from datetime import datetime
3 |
4 | import pytz
5 | import asyncio
6 | import hoshino, nonebot
7 |
8 | from .constant import config, key_dict, group_dict, trial_list, config
9 | from . import util
10 |
11 | tz = pytz.timezone('Asia/Shanghai')
12 |
13 |
14 | @on_request('group')
15 | async def approve_group_invite(session):
16 | '''
17 | 自动处理入群邀请 \n
18 | 由于腾讯憨批, 现在被邀请加入50人以下的群不需要验证, 因此此条目只适用于50人以上的邀请的情况, 50人以下请参见下一条函数\n
19 | 请注意, 应当移除其他@on_request('group.add')或者其他入群管理,以防止冲突, 例如移除botmanager下的join_approve \n
20 |
21 | 拒绝入群条件: \n
22 | 1. 新群且试用期设置为0 \n
23 | 2. 群授权已过期(新群试用也视为有授权, 期间内可自由加入)
24 |
25 | v0.1.1后新增, 配置ENABLE_AUTH为假的时候, 不检查授权
26 | '''
27 |
28 | if not config.ENABLE_AUTH:
29 | # 配置ENABLE_AUTH为0, 则授权系统不起作用, 不会自动通过加群邀请
30 | return
31 |
32 | if session.event.sub_type != 'invite':
33 | return
34 | gid = session.event.group_id
35 | ev = session.event
36 |
37 | new_group_auth = await util.new_group_check(gid)
38 | if new_group_auth == 'expired' or new_group_auth == 'no trial':
39 | await session.bot.set_group_add_request(flag=ev.flag,
40 | sub_type=ev.sub_type,
41 | approve=False,
42 | reason='此群无授权, 请联系维护组')
43 | util.log(f'接受到加群邀请,群号{gid}授权状态{new_group_auth}, 拒绝加入', 'group_add')
44 | elif new_group_auth == 'authed' or new_group_auth == 'trial':
45 | await session.bot.set_group_add_request(flag=ev.flag,
46 | sub_type=ev.sub_type,
47 | approve=True)
48 | util.log(f'接受到加群邀请,群号{gid}授权状态{new_group_auth}, 同意加入', 'group_add')
49 | else:
50 | pass
51 |
52 |
53 | @on_notice('group_increase')
54 | async def approve_group_invite_auto(session):
55 | '''
56 | 被邀请加入50人以下群时会自动接受, 此时获得的事件类型为通知而非邀请 \n
57 | 无法处理拒绝入群的邀请, 应当使用退群(如果开启了自动退群的话)
58 | '''
59 | self_ids = session.bot._wsr_api_clients.keys()
60 | for item in self_ids:
61 | sid = item
62 | gid = session.event.group_id
63 | if int(session.event.user_id) != int(sid):
64 | # 入群的人不是自己
65 | return
66 | rt = await check_number(gid)
67 |
68 | if rt == 'overflow':
69 | # 人数超标不自动试用, 考虑到风控, 也不会立刻退群, 而是在下一次自动检查时退群
70 | new_group_auth = 'no trial'
71 | else:
72 | new_group_auth = await util.new_group_check(gid)
73 | if new_group_auth == 'expired' or new_group_auth == 'no trial':
74 | await util.notify_group(group_id=gid, txt='本群无授权或授权或已过期, 如果您已有卡密可发送[充值帮助]来获取充值')
75 | util.log(f'成功加入群{gid}中,该群授权状态{new_group_auth}, 将在下次计划任务时自动退出', 'group_leave')
76 | hoshino.logger.info(f'成功加入群{gid}中,该群授权状态{new_group_auth}, 将在下次计划任务时自动退出')
77 | elif new_group_auth == 'authed' or new_group_auth == 'trial':
78 | await asyncio.sleep(5) # 别发太快了
79 | # 避免重复try
80 | await util.notify_group(group_id=gid, txt=config.NEW_GROUP_MSG)
81 | util.log(f'成功加入群{gid}中,该群授权状态{new_group_auth}', 'group_add')
82 | hoshino.logger.info(f'成功加入群{gid}中,该群授权状态{new_group_auth}')
83 |
84 |
85 | @on_notice('group_decrease.kick_me')
86 | async def kick_me_alert(session: NoticeSession):
87 | '''
88 | 被踢出同样记录
89 | '''
90 | group_id = session.event.group_id
91 | operator_id = session.event.operator_id
92 | util.log(f'被{operator_id}踢出群{group_id}', 'group_kick')
93 |
94 |
95 | @on_command('退群', only_to_me=False)
96 | async def group_leave_chat(session):
97 | '''
98 | 退群, 并不影响授权, 清除授权请试用清除授权命令
99 | '''
100 | if session.event.user_id not in hoshino.config.SUPERUSERS:
101 | await session.finish('只有主人才能让我退群哦')
102 | return
103 | gid = int(session.current_arg.strip())
104 | await session.send('正在褪裙...')
105 |
106 | rt_code = await util.gun_group(group_id=gid, reason='管理员操作')
107 | if rt_code == True:
108 | await session.send(f'已成功退出群{gid}')
109 | util.log(f'已成功退出群{gid}', 'group_leave')
110 | else:
111 | await session.send(f'退出群{gid}时发生错误')
112 | util.log(f'退出群{gid}时发生错误', 'group_leave')
113 |
114 |
115 | @on_command('快速检查', only_to_me=True)
116 | async def quick_check_chat(session):
117 | '''
118 | 立即执行一次检查, 内容与定时任务一样
119 | '''
120 | if session.event.user_id not in hoshino.config.SUPERUSERS:
121 | return
122 | await check_auth()
123 | await session.finish('检查完成')
124 |
125 |
126 | async def check_auth():
127 | '''
128 | 检查所有已加入群的授权状态, 和人数
129 | '''
130 | bot = nonebot.get_bot()
131 |
132 | # 该函数会独立地检查一次所有群的人数是否超标
133 | await check_number()
134 |
135 | group_info_all = await util.get_group_list_all()
136 | for group in group_info_all:
137 | gid = group['group_id']
138 | if gid in group_dict:
139 | util.log(f'正在检查群{gid}的授权......')
140 | # 此群已有授权或曾有授权, 检查是否过期
141 | time_left = group_dict[gid] - datetime.now()
142 | days_left = time_left.days
143 |
144 | rt_code = util.allowlist(gid)
145 | if rt_code == 'no_check' or rt_code == 'no_auth_check':
146 | # 在白名单, 并不会影响过期事件
147 | continue
148 |
149 | if time_left.total_seconds() <= 0:
150 | # 已过期, 检查是否在白名单中
151 |
152 | if config.AUTO_LEAVE and time_left.total_seconds() < -config.LEAVE_AFTER_DAYS * 86400:
153 | # 自动退群且已过期超过LEAVE_AFTER_DAYS天, 如果设置LEAVE_AFTER_DAYS为0则立刻退群
154 | await util.gun_group(group_id=gid, reason='授权过期')
155 | util.log(f'群{gid}授权过期,已自动退群', 'group_leave')
156 | else:
157 | # 不自动退群, 只发消息提醒
158 | t_sp = '【提醒】本群授权已过期\n'
159 | e_sp = '如果需要续费请联系机器人维护'
160 | gname_sp = group['group_name']
161 | msg_expired = await util.process_group_msg(gid=gid, expiration=group_dict[gid], title=t_sp,
162 | end=e_sp, group_name_sp=gname_sp)
163 | try:
164 | await bot.send_group_msg(group_id=gid, message=msg_expired)
165 | except Exception as e:
166 | util.log(f'向群{gid}发送过期提醒时发生错误{type(e)}')
167 | group_dict.pop(gid)
168 | await util.flush_group()
169 | if days_left < config.REMIND_BRFORE_EXPIRED and days_left >= 0:
170 | # 将要过期
171 | msg_remind = await util.process_group_msg(
172 | gid=gid,
173 | expiration=group_dict[gid],
174 | title=f'【提醒】本群的授权已不足{days_left + 1}天\n',
175 | end='\n如果需要续费请联系机器人维护',
176 | group_name_sp=group['group_name'])
177 | try:
178 | await bot.send_group_msg(group_id=gid,
179 | message=msg_remind)
180 | except Exception as e:
181 | hoshino.logger.error(f'向群{gid}发生到期提醒消息时发生错误{type(e)}')
182 | util.log(f'群{gid}的授权不足{days_left + 1}天')
183 |
184 | elif gid not in trial_list:
185 | # 正常情况下, 无论是50人以上群还是50人以下群, 都需要经过机器人同意或检查
186 | # 但是!如果机器人掉线期间被拉进群试用, 将无法处理试用, 因此有了这部分憨批代码
187 |
188 | if not config.NEW_GROUP_DAYS and config.AUTO_LEAVE:
189 | # 无新群试用机制,直接退群
190 | await util.gun_group(group_id=gid, reason='无授权')
191 | util.log(f'发现无记录而被拉入的新群{gid}, 已退出此群', 'group_leave')
192 | else:
193 | await util.new_group_check(gid)
194 | util.log(f'发现无记录而被拉入的新群{gid}, 已开始试用', 'group_add')
195 |
196 |
197 | async def check_number(group_id=0):
198 | '''
199 | 检查所有群的成员数量是否符合要求, 当传入group_id时则检查传入的群
200 | '''
201 | if group_id == 0:
202 | gnums = await util.get_group_info(info_type='member_count')
203 | else:
204 | __gid = group_id
205 | gnums = await util.get_group_info(group_ids=__gid, info_type='member_count')
206 | for gid in gnums:
207 | if gnums[gid] > config.MAX_GROUP_NUM:
208 | # 人数超过, 检测是否在白名单
209 | rt_code = util.allowlist(gid)
210 | if rt_code == 'not in' or rt_code == 'no_check_auth':
211 | util.log(f'群{gid}人数超过设定值, 当前人数{gnums[gid]}, 白名单状态{rt_code}', 'number_check')
212 | if group_id == 0:
213 | # 检查全部群的情况, 可以自动退出
214 | if config.AUTO_LEAVE:
215 | await util.gun_group(group_id=gid, reason='群人数超过管理员设定的最大值')
216 | else:
217 | await util.notify_group(group_id=gid, txt='群人数超过管理员设定的最大值, 请联系管理员')
218 | else:
219 | # 检查单个群的情况, 只通知而不自动退出, 等到下次计划任务时再退出
220 | await util.notify_group(group_id=gid, txt='群人数超过管理员设定的最大值, 请联系管理员')
221 | return 'overflow'
222 | return None
223 |
--------------------------------------------------------------------------------
/admin.py:
--------------------------------------------------------------------------------
1 | from .util import notify_group
2 | from nonebot import on_command
3 | from math import ceil
4 |
5 | import hoshino
6 | import re
7 |
8 | from . import util
9 | from .constant import config
10 |
11 |
12 |
13 | @on_command('变更所有授权', aliases=('批量变更', '批量授权'), only_to_me=False)
14 | async def add_time_all_chat(session):
15 | '''
16 | 为所有已有授权的群增加授权x天, 可用于维护补偿时间等场景
17 | '''
18 | if session.event.user_id not in hoshino.config.SUPERUSERS:
19 | util.log(f'{session.event.user_id}尝试批量授权, 已拒绝')
20 | await session.finish('只有主人才能批量授权哦')
21 | return
22 | if not session.current_arg:
23 | await session.finish('请发送需要为所有群增加或减少的长, 例如“变更所有授权 7”')
24 |
25 | days = int(session.current_arg.strip())
26 |
27 | authed_group_list = await util.get_authed_group_list()
28 | for ginfo in authed_group_list:
29 | await util.change_authed_time(ginfo['gid'], days)
30 | util.log(f'已为所有群授权增加{days}天')
31 | await session.finish(f'已为所有群授权增加{days}天')
32 |
33 |
34 | @on_command('授权列表', aliases=('查看授权列表', '查看全部授权', '查询全部授权'), only_to_me=True)
35 | async def group_list_chat(session):
36 | '''
37 | 此指令获得的是, 所有已经获得授权的群, 其中一些群可能Bot并没有加入 \n
38 | 分页显示, 请在authMS.py中配置
39 | '''
40 | if session.event.user_id not in hoshino.config.SUPERUSERS:
41 | util.log(f'{session.event.user_id}尝试查看授权列表, 已拒绝')
42 | await session.finish('只有主人才能查看授权列表哦')
43 | return
44 | if session.event.detail_type == 'group':
45 | # 群聊查看授权列表你也是个小天才
46 | await session.finish('请超级管理员私聊机器人查看授权列表')
47 |
48 | if not session.current_arg.strip():
49 | # 无其他参数默认第一页
50 | page = 1
51 | else:
52 | page = int(session.current_arg.strip())
53 |
54 | msg = '======授权列表======\n'
55 |
56 | authed_group_list = await util.get_authed_group_list()
57 | length = len(authed_group_list)
58 |
59 | groups_in_page = config.GROUPS_IN_PAGE
60 | pages_all = ceil(length / groups_in_page) # 向上取整
61 | if page > pages_all:
62 | await session.finish(f'没有那么多页, 当前共有授权信息{length}条, 共{pages_all}页')
63 | if page <= 0:
64 | await session.finish('请输入正确的页码')
65 | i = 0
66 | for item in authed_group_list:
67 | i = i + 1
68 | if i < (page - 1) * groups_in_page + 1 or i > page * groups_in_page:
69 | continue
70 | gid = int(item['gid'])
71 | g_time = util.check_group(gid)
72 | msg_new = await util.process_group_msg(gid,
73 | g_time,
74 | title=f'第{i}条信息\n',
75 | end='\n\n',
76 | group_name_sp=item['groupName'])
77 | msg += msg_new
78 |
79 | msg += f'第{page}页, 共{pages_all}页\n发送查询授权+页码以查询其他页'
80 | await session.send(msg)
81 |
82 |
83 | @on_command('变更授权', aliases=('更改时间', '授权', '更改授权时间', '更新授权'), only_to_me=False)
84 | async def add_time_chat(session):
85 | origin = session.current_arg.strip()
86 | pattern = re.compile(r'^(\d{5,15})([+-]\d{1,5})$')
87 | m = pattern.match(origin)
88 | if m is None:
89 | await session.finish('请发送“授权 群号±时长”来进行指定群的授权, 时长最长为99999')
90 | gid = int(m.group(1))
91 | days = int(m.group(2))
92 |
93 | if session.event.user_id not in hoshino.config.SUPERUSERS:
94 | util.log(f'{session.event.user_id}尝试为群{gid}增加{days}天授权, 已拒绝')
95 | await session.finish('只有主人才能变更授权哦')
96 | return
97 |
98 | result = await util.change_authed_time(gid, days)
99 | msg = await util.process_group_msg(gid, result, title='变更成功, 变更后的群授权信息:\n')
100 | await notify_group(group_id=gid, txt=f'机器人管理员已为本群增加{days}天授权时长,可在群内发送【查询授权】来查看到期时间。')
101 | await session.finish(msg)
102 |
103 |
104 |
105 | @on_command('转移授权', only_to_me=False)
106 | async def group_change_chat(session):
107 | if not session.current_arg:
108 | await session.finish('请发送“转移授权 旧群群号*新群群号”来进行群授权转移')
109 | origin = session.current_arg.strip()
110 | pattern = re.compile(r'^(\d{5,15})\*(\d{5,15})$')
111 | m = pattern.match(origin)
112 | if m is None:
113 | await session.finish('格式错误或者群号错误XD\n请发送“转移授权 旧群群号*新群群号”来转移群授权时长\n如果新群已经授权,则会增加对应时长。')
114 | old_gid = int(m.group(1))
115 | new_gid = int(m.group(2))
116 |
117 | if session.event.user_id not in hoshino.config.SUPERUSERS:
118 | util.log(f'{session.event.user_id}尝试转移授权{old_gid}到{new_gid}, 已拒绝')
119 | session.finish('只有主人才能转移授权哦')
120 | return
121 |
122 | gtime_old = util.check_group(old_gid)
123 | if gtime_old == 0:
124 | await session.finish('旧群无授权, 不可进行转移')
125 | if old_gid == new_gid:
126 | await session.finish('宁搁这儿原地TP呢?')
127 |
128 | await util.transfer_group(old_gid, new_gid)
129 | gtime_new = util.check_group(new_gid)
130 | msg = await util.process_group_msg(new_gid,expiration=gtime_new, title=f'旧群{old_gid}授权已清空, 新群授权状态:\n')
131 | await notify_group(group_id=old_gid, txt=f'机器人管理员已转移本群授权时长至其他群。')
132 |
133 | await session.finish(msg)
134 |
135 | @on_command('授权状态', only_to_me=False)
136 | async def auth_status_chat(session):
137 | if session.event.user_id not in hoshino.config.SUPERUSERS:
138 | util.log(f'{session.event.user_id}尝试查看授权状态, 已拒绝')
139 | await session.finish('只有主人才能查看授权状态哦')
140 | return
141 | for sid in hoshino.get_self_ids():
142 | sgl = set(g['group_id']
143 | for g in await session.bot.get_group_list(self_id=sid))
144 | frl = set(f['user_id']
145 | for f in await session.bot.get_friend_list(self_id=sid))
146 | # 直接从service里抄了, 面向cv编程才是真
147 | gp_num = len(sgl)
148 | fr_num = len(frl)
149 | key_num = len(util.get_key_list())
150 | agp_num = len(await util.get_authed_group_list())
151 | msg = f'Bot账号:{sid}\n所在群数:{gp_num}\n好友数:{fr_num}\n授权群数:{agp_num}\n未使用卡密数:{key_num}'
152 | await session.send(msg)
153 |
154 |
155 | @on_command('清除授权', aliases=('删除授权', '移除授权', '移除群授权', '删除群授权'), only_to_me=True)
156 | async def remove_auth_chat(session):
157 | '''
158 | 完全移除一个群的授权 \n
159 | 不需要二次确认, 我寻思着你rm /* -rf的时候也没人让你二次确认啊 \n
160 | '''
161 | if not session.current_arg.strip():
162 | await session.finish('请输入正确的群号, 例如“清除授权 123456789”')
163 | gid = int(session.current_arg.strip())
164 | time_left = util.check_group(gid)
165 |
166 | if session.event.user_id not in hoshino.config.SUPERUSERS:
167 | util.log(f'{session.event.user_id}尝试为群{gid}清除授权, 已拒绝')
168 | await session.finish('只有主人才能清除授权哦')
169 |
170 | if not time_left:
171 | await session.finish('此群未获得授权')
172 | msg = await util.process_group_msg(gid=gid, expiration=time_left, title='已移除授权,原授权信息如下\n')
173 | await util.change_authed_time(gid=gid, operate='clear')
174 |
175 | if config.AUTO_LEAVE:
176 | await util.gun_group(group_id=gid, reason='机器人管理员移除授权')
177 | msg += '\n已尝试退出该群聊'
178 | await session.send(msg)
179 |
180 |
181 | @on_command('不检查人数', aliases=('设置人数白名单'), only_to_me=False)
182 | async def no_number_check_chat(session):
183 | '''
184 | 不检查一个群的人数是否超过人数限制, 在群聊中发送则为不检查本群
185 | '''
186 | if session.event.detail_type == 'group':
187 | gid = session.event.group_id
188 | elif session.event.detail_type == 'private':
189 | if not session.current_arg.strip():
190 | await session.finish('请输入正确的群号, 例如“不检查人数 123456789”')
191 | gid = int(session.current_arg.strip())
192 |
193 | uid = session.event.user_id
194 | if uid not in hoshino.config.SUPERUSERS:
195 | util.log(f'{uid}尝试为群{gid}清除设置不检查人数, 已拒绝')
196 | await session.finish('只有主人才能设置白名单')
197 | return
198 |
199 | util.allowlist(group_id=gid, operator='add', nocheck='no_number_check')
200 | util.log(f'管理员{uid}已将群{gid}添加至白名单, 类型为不检查人数')
201 | await notify_group(group_id=gid, txt='机器人管理员已添加本群为白名单,将不会检查本群人数是否超标。')
202 | await session.finish(f'已将群{gid}添加至白名单, 类型为不检查人数')
203 |
204 |
205 | @on_command('不检查授权', aliases=('设置授权白名单'), only_to_me=False)
206 | async def no_auth_check_chat(session):
207 | if session.event.detail_type == 'group':
208 | gid = session.event.group_id
209 | elif session.event.detail_type == 'private':
210 | if not session.current_arg.strip():
211 | await session.finish('请输入正确的群号, 例如“不检查授权 123456789”')
212 | gid = int(session.current_arg.strip())
213 |
214 | uid = session.event.user_id
215 | if uid not in hoshino.config.SUPERUSERS:
216 | util.log(f'{uid}尝试为群{gid}清除设置不检查授权, 已拒绝')
217 | await session.finish('只有主人才能设置白名单')
218 | return
219 | util.allowlist(group_id=gid, operator='add', nocheck='no_auth_check')
220 | util.log(f'已将群{gid}添加至白名单, 类型为不检查授权')
221 | await notify_group(group_id=gid, txt='机器人管理员已添加本群为白名单,将不会检查本群授权是否过期。')
222 | await session.finish(f'已将群{gid}添加至白名单, 类型为不检查授权')
223 |
224 |
225 | @on_command('添加白名单', only_to_me=False)
226 | async def no_check_chat(session):
227 | '''
228 | 最高级别白名单, 授权与人数都检查
229 | '''
230 | if session.event.detail_type == 'group':
231 | gid = session.event.group_id
232 | elif session.event.detail_type == 'private':
233 | if not session.current_arg.strip():
234 | await session.finish('请输入正确的群号, 例如“添加白名单 123456789”')
235 | gid = int(session.current_arg.strip())
236 |
237 | uid = session.event.user_id
238 | if uid not in hoshino.config.SUPERUSERS:
239 | util.log(f'{uid}尝试为群{gid}清除设置添加白名单, 已拒绝')
240 | await session.finish('只有主人才能设置白名单')
241 | return
242 |
243 | util.allowlist(group_id=gid, operator='add', nocheck='no_check')
244 | util.log(f'已将群{gid}添加至白名单, 类型为全部不检查')
245 | await notify_group(group_id=gid, txt='机器人管理员已添加本群为白名单,将不会检查本群授权以及人数。')
246 | await session.finish(f'已将群{gid}添加至白名单, 类型为全部不检查')
247 |
248 |
249 | @on_command('移除白名单', aliases=('删除白名单'))
250 | async def remove_allowlist_chat(session):
251 | if not session.current_arg.strip():
252 | await session.finish('请输入正确的群号, 例如“移除白名单 123456789”')
253 | gid = int(session.current_arg.strip())
254 | uid = session.event.user_id
255 |
256 | if uid not in hoshino.config.SUPERUSERS:
257 | util.log(f'{uid}尝试移除白名单{gid}, 已拒绝')
258 | await session.finish('只有主人才能移除白名单')
259 | return
260 |
261 | re_code = util.allowlist(group_id=gid, operator='remove')
262 | if re_code == 'not in':
263 | await session.finish(f'群{gid}不在白名单中')
264 | await notify_group(group_id=gid, txt='机器人管理员已移除本群的白名单资格')
265 | util.log(f'已将群{gid}移出白名单')
266 | await session.finish(f'已将群{gid}移出白名单')
267 |
268 |
269 | @on_command('全部白名单', aliases=('白名单列表', '所有白名单'))
270 | async def get_allowlist_chat(session):
271 | if session.event.user_id not in hoshino.config.SUPERUSERS:
272 | util.log(f'{session.event.user_id}尝试查看白名单, 已拒绝')
273 | await session.finish('只有主人才能查看白名单')
274 | return
275 |
276 | allow_list = util.get_list(list_type='allowlist')
277 |
278 | msg = '白名单信息\n'
279 | gids = list(allow_list.keys())
280 | gname_dir = await util.get_group_info(group_ids=gids, info_type='group_name')
281 | # 考虑到一般没有那么多白名单, 因此此处不做分页
282 | i = 1
283 | for gid in gname_dir:
284 | msg += f'第{i}条: 群号{gid}\n'
285 | gname = gname_dir[gid]
286 | gnocheck = allow_list[gid]
287 | msg += f'群名:{gname}\n类型:{gnocheck}\n\n'
288 | i = i+1
289 | session.finish(msg)
290 |
291 |
292 | @on_command('刷新事件过滤器')
293 | async def reload_ef(session):
294 | if session.event.user_id not in hoshino.config.SUPERUSERS:
295 | util.log(f'{session.event.user_id}刷新事件过滤器, 已拒绝')
296 | await session.finish('只有主人才能刷新事件过滤器')
297 | return
298 | await util.flush_group()
299 | await session.send("刷新成功!")
300 |
--------------------------------------------------------------------------------
/vue/img/quasar-logo-full.68ae1645.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
192 |
--------------------------------------------------------------------------------
/util.py:
--------------------------------------------------------------------------------
1 |
2 | from hoshino.config.__bot__ import SUPERUSERS
3 | from datetime import timedelta, datetime
4 | from math import ceil
5 |
6 | import random, string
7 | import os
8 | import time, json
9 | import asyncio
10 | import nonebot, hoshino
11 |
12 | from .constant import key_dict, group_dict, trial_list, config
13 |
14 |
15 |
16 | def generate_key():
17 | '''
18 | 生成16位卡密
19 | '''
20 | begin=config.BEGIN
21 | new_key = ''.join(random.sample(string.ascii_letters + string.digits, 16-len(begin)))
22 | while new_key in key_dict: # 防止生成重复的卡密, 不过概率太低了。。。
23 | new_key = ''.join(random.sample(string.ascii_letters + string.digits, 16-len(begin)))
24 | new_key = begin + new_key
25 | return new_key
26 |
27 |
28 | def add_key(duration):
29 | '''
30 | 卡密添加到数据库中
31 | '''
32 | new_key = generate_key()
33 | key_dict[new_key] = duration
34 | return new_key
35 |
36 |
37 | def get_key_list():
38 | '''
39 | 获取全部的卡密列表
40 | '''
41 | key_list = []
42 | for key, value in key_dict.iteritems():
43 | key_list.append({'key': key, 'duration': value})
44 | return key_list
45 |
46 |
47 | def del_key(key):
48 | '''
49 | 删除一张卡密, 成功返回True
50 | '''
51 | if key in key_dict:
52 | key_dict.pop(key)
53 | return True
54 | return False
55 |
56 |
57 | def update_key(key, duration):
58 | '''
59 | 更新一张卡密,成功返回True
60 | '''
61 | if duration <= 0:
62 | # 禁止将有效期更新为0, 因为检验卡密时以0为无效标志
63 | return False
64 | if key in key_dict:
65 | key_dict[key] = duration
66 | return True
67 | return False
68 |
69 |
70 | def query_key(key):
71 | '''
72 | 检查一张卡密, 有效则返回可以增加的授权时间, 无效则返回0
73 | '''
74 | if key in key_dict:
75 | return key_dict[key]
76 | else:
77 | return 0
78 |
79 |
80 | def check_group(gid):
81 | '''
82 | 检查一个群是否有授权, 如果有返回过期时间(datetime格式), 否则返回False.
83 | 注意无论Bot是否加入此群都会返回
84 | '''
85 | if gid in group_dict:
86 | return group_dict[gid]
87 | else:
88 | return 0
89 |
90 |
91 | async def reg_group(gid, key):
92 | '''
93 | 为一个群充值, 卡密无效则返回False, 否则返回剩余有效期(datatime格式)
94 | '''
95 | days = query_key(key)
96 | if days == 0:
97 | return False
98 | past_time = await change_authed_time(gid, days)
99 | del_key(key)
100 | return past_time
101 |
102 |
103 | async def change_authed_time(gid, time_change=0, operate=''):
104 | '''
105 | 不使用卡密, 而直接对一个群的授权时间进行操作
106 | '''
107 | if operate == 'clear':
108 | try:
109 | # 用try是因为可能会尝试给本来就无授权的群清空授权, 此种情况并不需要另外通知, 因为最终目的一致
110 | group_dict.pop(gid)
111 | except:
112 | pass
113 | await flush_group()
114 | return 0
115 |
116 | if gid in group_dict:
117 | group_dict[gid] = group_dict[gid] + timedelta(days=time_change)
118 | else:
119 | today = datetime.now()
120 | group_dict[gid] = today + timedelta(days=time_change)
121 | await flush_group()
122 | return group_dict[gid]
123 |
124 | async def get_nickname(user_id):
125 | '''
126 | 获取用户昵称
127 | '''
128 | uid = user_id
129 | user_info = await nonebot.get_bot().get_stranger_info(user_id=uid)
130 | return user_info['nickname']
131 |
132 |
133 | async def get_group_info(group_ids=0, info_type='group_name'):
134 | '''
135 | 1. 传入一个整型数字, 返回单个群指定信息, 格式为字典
136 | 2. 传入一个list, 内含多个群号(int), 返回一个字典, 键为群号, 值为指定信息
137 | 3. 不填入参数, 返回一个包含所有群号与指定信息的字典
138 | 无论获取多少群信息, 均只有一次API的开销, 传入未加入的群时, 将自动忽略
139 | info_type支持group_id, group_name, max_member_count, member_count
140 | '''
141 | group_info_all = await get_group_list_all()
142 | _gids = []
143 | _gnames = []
144 | # 获得的已加入群为列表形式, 处理为需要的字典形式
145 | for it in group_info_all:
146 | _gids.append(it['group_id'])
147 | _gnames.append(it[info_type])
148 | group_info_dir = dict(zip(_gids, _gnames))
149 |
150 | if group_ids == 0:
151 | return group_info_dir
152 | if type(group_ids) == int:
153 | # 转为列表
154 | group_ids = [group_ids]
155 | print(group_ids)
156 |
157 | for key in list(group_info_dir.keys()):
158 | if key not in group_ids:
159 | del group_info_dir[key]
160 | else:
161 | # TODO: group not joined
162 | pass
163 | return group_info_dir
164 |
165 |
166 | async def get_authed_group_list():
167 | '''
168 | 获取已授权的群
169 | '''
170 | authed_group_list = []
171 | group_name_dir = await get_group_info()
172 |
173 | for key, value in group_dict.iteritems():
174 | deadline = f'{value.year}年{value.month}月{value.day}日 {value.hour}时{value.minute}分'
175 | group_name = group_name_dir.get(int(key), '未知')
176 | authed_group_list.append({'gid': key, 'deadline': deadline, 'groupName': group_name})
177 | return authed_group_list
178 |
179 |
180 | async def get_group_list_all():
181 | '''
182 | 获取所有群, 无论授权与否, 返回为原始类型(列表)
183 | '''
184 | bot = nonebot.get_bot()
185 | self_ids = bot._wsr_api_clients.keys()
186 | for sid in self_ids:
187 | group_list = await bot.get_group_list(self_id=sid)
188 | return group_list
189 |
190 |
191 | async def process_group_msg(gid, expiration, title: str = '', end='', group_name_sp=''):
192 | '''
193 | 把查询消息处理为固定的格式 \n
194 | 第一行为额外提醒信息(例如充值成功)\n
195 | 群号:<群号> \n
196 | 群名:<群名> \n
197 | 授权到期:<到期时间> \n
198 | 部分情况下, 可以通过指定群组名的方式来减少对API的调用次数(我并不知道这个对性能是否有大影响)
199 | '''
200 | if group_name_sp == '':
201 | bot = nonebot.get_bot()
202 | self_ids = bot._wsr_api_clients.keys()
203 | for sid in self_ids:
204 | try:
205 | group_info = await bot.get_group_info(self_id=sid, group_id=gid)
206 | group_name = group_info['group_name']
207 | except:
208 | group_name = '未知(Bot未加入此群)'
209 | else:
210 | group_name = group_name_sp
211 | time_format = expiration.strftime("%Y-%m-%d %H:%S:%M")
212 | msg = title
213 | msg += f'群号:{gid}\n群名:{group_name}\n到期时间:{time_format}'
214 | msg += end
215 | return msg
216 |
217 |
218 | async def new_group_check(gid):
219 | '''
220 | 加入新群时检查此群是否符合条件,如果有试用期则会自动添加试用期授权时间, 同时添加试用标志
221 | '''
222 | if gid in group_dict:
223 | time_left = group_dict[gid] - datetime.now()
224 |
225 | if time_left.total_seconds() <= 0:
226 | # 群在列表但是授权过期, 从授权列表移除此群
227 | group_dict.pop(gid)
228 | await flush_group()
229 | return 'expired'
230 | await flush_group()
231 | return 'authed'
232 |
233 | if config.NEW_GROUP_DAYS <= 0 or gid in trial_list:
234 | return 'no trial'
235 | # 添加试用标记
236 | trial_list[gid] = 1
237 | # 添加试用天数
238 | await change_authed_time(gid=gid, time_change=config.NEW_GROUP_DAYS)
239 | return 'trial'
240 |
241 |
242 | async def transfer_group(old_gid, new_gid):
243 | '''
244 | 转移授权,新群如果已经有时长了则在现有时长上增加
245 | '''
246 | today = datetime.now()
247 | left_time = group_dict[old_gid] - today if old_gid in group_dict else timedelta(days=0)
248 | group_dict[new_gid] = left_time + (group_dict[new_gid] if new_gid in group_dict else today)
249 | group_dict.pop(old_gid)
250 | await flush_group()
251 |
252 | async def gun_group(group_id, reason='管理员操作'):
253 | '''
254 | 退出群聊, 同时会发送消息, 说明退群原因
255 | '''
256 | gid = group_id
257 | msg = config.GROUP_LEAVE_MSG
258 | msg += reason
259 | try:
260 | await nonebot.get_bot().send_group_msg(group_id=gid, message=msg)
261 | except Exception as e:
262 | hoshino.logger.error(f'向群{group_id}发送退群消息时发生错误{e}')
263 | await asyncio.sleep(2)
264 | try:
265 | await nonebot.get_bot().set_group_leave(group_id=gid)
266 | except nonebot.CQHttpError:
267 | return False
268 | return True
269 |
270 |
271 | async def notify_group(group_id, txt):
272 | '''
273 | 发送自定义提醒广播,顺带解决了HoshinoBot和Yobot的广播短板
274 | '''
275 | gid = group_id
276 | try:
277 | await nonebot.get_bot().send_group_msg(group_id=gid, message=txt)
278 | except nonebot.CQHttpError:
279 | return False
280 | return True
281 |
282 | async def notify_master(txt):
283 | '''
284 | 通知主人
285 | '''
286 | try:
287 | await nonebot.get_bot().send_private_msg(user_id=SUPERUSERS[0], message=txt)
288 | except nonebot.CQHttpError:
289 | return False
290 | return True
291 |
292 |
293 | def time_now():
294 | return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
295 |
296 |
297 | LOG_LIST = [
298 | 'card_use',
299 | 'group_add',
300 | 'group_kick',
301 | 'group_leave',
302 | 'friend_add',
303 | 'number_check'
304 | ]
305 |
306 |
307 | def log(info, log_type='debug'):
308 | '''
309 | 记录日志, 保存位置为HoshinoBot/logs/authMS.log
310 | '''
311 | if not config.LOG:
312 | return
313 | if not config.DEBUG and log_type not in LOG_LIST:
314 | # 非DEBUG模式, 非需要记录的信息
315 | return
316 |
317 | file_name = 'log/authMS.log'
318 | with open(file_name, 'a', encoding='utf-8') as l:
319 | l.writelines(f"[{time_now()}]")
320 | l.writelines(info)
321 | l.writelines('\n')
322 |
323 |
324 | def allowlist(group_id, operator='none', nocheck='no_number_check'):
325 | '''
326 | operator------
327 | none: 检查一个群或人是否在白名单中, 不在时返回值为not in
328 | add: 增加白名单
329 | del: 移除白名单
330 | clear: 清除所有白名单
331 | nocheck------
332 | no_number_check: 不检查人数
333 | no_auth_check: 不检查授权(永久有效)
334 | no_check: 全部不检查
335 | '''
336 |
337 | ALLOWLIST_PATH = os.path.expanduser('~/.hoshino/authMS/allowlist.json')
338 | if os.path.exists(ALLOWLIST_PATH):
339 | with open(ALLOWLIST_PATH, 'r', encoding='utf-8') as rf:
340 | try:
341 | allowlist = json.load(rf)
342 | except Exception as e:
343 | # 您写的json格式有错误?扬了就好了
344 | hoshino.logger.error(f'读取白名单列表时发生错误{type(e)}')
345 | allowlist = {}
346 | else:
347 | os.makedirs(os.path.expanduser('~/.hoshino/authMS'), exist_ok=True)
348 | allowlist = {}
349 |
350 | if operator == 'none':
351 | return allowlist.get(str(group_id), 'not in')
352 | elif operator == 'add':
353 | allowlist[str(group_id)] = nocheck
354 | hoshino.logger.error(f'已将群{group_id}添加到白名单,类型为{nocheck}')
355 | rt = 'ok'
356 | elif operator == 'remove':
357 | rt = allowlist.pop(str(group_id), 'not in')
358 | if rt != 'not in':
359 | hoshino.logger.error(f'已将群{group_id}移除白名单')
360 | rt = 'ok'
361 | else:
362 | hoshino.logger.error(f'群{group_id}移除不在白名单,移除失败')
363 | elif operator == 'clear':
364 | allowlist.clear()
365 | hoshino.logger.error(f'已清空所有白名单')
366 | rt = 'ok'
367 | else:
368 | return 'nothing to do'
369 |
370 | with open(ALLOWLIST_PATH, "w", encoding='utf-8') as sf:
371 | json.dump(allowlist, sf, indent=4, ensure_ascii=False)
372 |
373 | return rt
374 |
375 |
376 | # 这个没写完别看了-------------------------------------------
377 | async def set_block_list(group_id, operator_id, reason='no reason'):
378 | '''
379 | 将一个群添加到黑名单, 目前仅本地拉黑, 未来可能支持......算了不画饼了
380 | 可以重复拉黑, 以更新reason
381 |
382 | group_id: 要拉黑的群号
383 | operator_id: 操作人
384 | reason: 拉黑原因
385 |
386 | 以下信息将由api自动获取:
387 | group_info: 直接传入bot.get_group_info()返回的原始信息
388 | group_member_list: 直接传入bot.get_group_member_list()返回的群所有成员信息
389 | operator_name: 操作者昵称
390 | '''
391 |
392 | BLOCKLIST_PATH = os.path.expanduser('~/.hoshino/authMS/blocklist.json')
393 |
394 | # 读名单
395 | if os.path.exists(BLOCKLIST_PATH):
396 | with open(BLOCKLIST_PATH, 'r+', encoding='utf-8') as f:
397 | blocklist = json.load(f)
398 | else:
399 | os.makedirs(os.path.expanduser('~/.hoshino/authMS'), exist_ok=True)
400 | blocklist = {}
401 |
402 | bot = nonebot.get_bot
403 |
404 | # 在对应黑白名单中加入该群, 键值为群号
405 | temp_dir = {
406 | "time": int(time.time()), # 记录为时间戳形式, 便于处理
407 | "operator_id": int(operator_id),
408 | "operator_name": 'name'
409 | }
410 | pass
411 |
412 |
413 | def get_list(list_type='allowlist'):
414 | '''
415 | list_type可选blocklist和allowlist
416 | 保存位置: ~./.hoshino/authMS/blocklist.json和allowlist.json
417 | 为保持兼容性, 会将所有键值转化为int
418 | '''
419 | LIST_PATH = os.path.expanduser(f'~/.hoshino/authMS/{list_type}.json')
420 | if os.path.exists(LIST_PATH):
421 | with open(LIST_PATH, 'r', encoding='utf-8') as rf:
422 | try:
423 | ba_list = json.load(rf)
424 | except Exception as e:
425 | print(e)
426 | ba_list = {}
427 | else:
428 | ba_list = {}
429 |
430 | # 将键的str格式转换为int格式, 保持其他函数传参时的兼容性
431 | ba_new = {}
432 | for key in ba_list:
433 | ba_new[int(key)] = ba_list[key]
434 | return ba_new
435 |
436 |
437 | async def flush_group():
438 | with open(config.EVENT_FILTER, mode="r", encoding='utf-8') as f:
439 | fil = json.load(f)
440 | fil[".or"][0]["group_id"][".in"] = []
441 | group_list = []
442 | for key, val in group_dict.iteritems():
443 | group_list.append(key)
444 | fil[".or"][0]["group_id"][".in"] = group_list
445 | with open(config.EVENT_FILTER, mode="w", encoding='utf-8') as f:
446 | json.dump(fil, f, indent=4, ensure_ascii=False)
447 | await nonebot.get_bot().call_action("reload_event_filter")
448 |
--------------------------------------------------------------------------------
/activate/css/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 | main {
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 | width: 100%;
9 | height: 100%;
10 | display: -webkit-box;
11 | display: -ms-flexbox;
12 | display: flex;
13 | -webkit-box-pack: center;
14 | -ms-flex-pack: center;
15 | justify-content: center;
16 | -webkit-box-align: center;
17 | -ms-flex-align: center;
18 | align-items: center;
19 | background: #ffffff;
20 | }
21 | .form {
22 | display: -webkit-box;
23 | display: -ms-flexbox;
24 | display: flex;
25 | -webkit-box-align: center;
26 | -ms-flex-align: center;
27 | align-items: center;
28 | -webkit-box-pack: center;
29 | -ms-flex-pack: center;
30 | justify-content: center;
31 | position: relative;
32 | width: 400px;
33 | height: 400px;
34 | -ms-flex-negative: 0;
35 | flex-shrink: 0;
36 | padding: 20px;
37 | border-radius: 5px;
38 | }
39 | .form__loader {
40 | display: -webkit-box;
41 | display: -ms-flexbox;
42 | display: flex;
43 | position: absolute;
44 | left: 0;
45 | top: 0;
46 | height: 100%;
47 | width: 100%;
48 | -webkit-box-pack: center;
49 | -ms-flex-pack: center;
50 | justify-content: center;
51 | -webkit-box-align: center;
52 | -ms-flex-align: center;
53 | align-items: center;
54 | z-index: -4;
55 | -webkit-transition: all 0.5s ease;
56 | transition: all 0.5s ease;
57 | }
58 | .form__content {
59 | text-align: center;
60 | display: -webkit-box;
61 | display: -ms-flexbox;
62 | display: flex;
63 | -webkit-box-pack: center;
64 | -ms-flex-pack: center;
65 | justify-content: center;
66 | -webkit-box-orient: vertical;
67 | -webkit-box-direction: normal;
68 | -ms-flex-direction: column;
69 | flex-direction: column;
70 | position: relative;
71 | opacity: 0;
72 | -webkit-transform: translateY(10px);
73 | transform: translateY(10px);
74 | -webkit-transition: all 0.5s ease 0.7s;
75 | transition: all 0.5s ease 0.7s;
76 | width: 90%;
77 | }
78 | .form__cover {
79 | position: absolute;
80 | left: 0;
81 | top: 0;
82 | height: 100%;
83 | width: 100%;
84 | z-index: -4;
85 | border-radius: 7px;
86 | overflow: hidden;
87 | -webkit-transition: all 0.3s ease 0.8s;
88 | transition: all 0.3s ease 0.8s;
89 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
90 | }
91 | .form__cover:after {
92 | content: '';
93 | position: absolute;
94 | left: 0;
95 | top: 0;
96 | height: 100%;
97 | width: 100%;
98 | background: #ffffff;
99 | z-index: -4;
100 | border-radius: 50%;
101 | -webkit-transition: all 1.5s ease 0.3s;
102 | transition: all 1.5s ease 0.3s;
103 | -webkit-transform: scale(0);
104 | transform: scale(0);
105 | }
106 | .form__cover:before {
107 | content: '';
108 | position: absolute;
109 | left: 0;
110 | top: 0;
111 | height: 100%;
112 | width: 100%;
113 | background: white;
114 | z-index: -5;
115 | border-radius: 50%;
116 | -webkit-transition: all 0.5s ease;
117 | transition: all 0.5s ease;
118 | -webkit-transform: scale(0);
119 | transform: scale(0);
120 | }
121 | body.on-start .form__cover:before {
122 | -webkit-transform: scale(0.15);
123 | transform: scale(0.15);
124 | }
125 | body.document-loaded .form__loader {
126 | -webkit-transform: scale(0);
127 | transform: scale(0);
128 | opacity: 0;
129 | visibility: hidden;
130 | }
131 | body.document-loaded .form__content {
132 | opacity: 1;
133 | -webkit-transform: none;
134 | transform: none;
135 | }
136 | body.document-loaded .form__cover {
137 | box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
138 | }
139 | body.document-loaded .form__cover:after {
140 | -webkit-transform: scale(2);
141 | transform: scale(2);
142 | }
143 | body.document-loaded .form__cover:before {
144 | -webkit-transition: opacity 0.3s ease 0.8s, -webkit-transform 2s ease;
145 | transition: opacity 0.3s ease 0.8s, -webkit-transform 2s ease;
146 | transition: transform 2s ease, opacity 0.3s ease 0.8s;
147 | transition: transform 2s ease, opacity 0.3s ease 0.8s, -webkit-transform 2s ease;
148 | -webkit-transform: scale(2);
149 | transform: scale(2);
150 | opacity: 0;
151 | }
152 | h1 {
153 | font-size: 40px;
154 | margin: 15px 0 20px 0;
155 | letter-spacing: 0.05em;
156 | color: #000000;
157 | font-weight: 700;
158 | }
159 | .styled-button {
160 | -webkit-appearance: none;
161 | -webkit-user-select: none;
162 | cursor: pointer;
163 | font-size: 14px;
164 | width: 100%;
165 | padding: 20px;
166 | outline: none;
167 | background: none;
168 | position: relative;
169 | color: #ffffff;
170 | border-radius: 3px;
171 | margin-bottom: 25px;
172 | border: none;
173 | text-transform: uppercase;
174 | font-weight: 700;
175 | letter-spacing: 0.1em;
176 | background: #108cee;
177 | -webkit-transition: all 0.3s ease;
178 | transition: all 0.3s ease;
179 | overflow: hidden;
180 | }
181 | .styled-button__real-text-holder {
182 | position: relative;
183 | }
184 | .styled-button__real-text {
185 | color: transparent;
186 | display: inline-block;
187 | }
188 | .styled-button__text-holder {
189 | position: absolute;
190 | left: 0;
191 | top: 0;
192 | height: 100%;
193 | width: 100%;
194 | display: -webkit-box;
195 | display: -ms-flexbox;
196 | display: flex;
197 | -webkit-box-align: center;
198 | -ms-flex-align: center;
199 | align-items: center;
200 | -webkit-box-pack: center;
201 | -ms-flex-pack: center;
202 | justify-content: center;
203 | -webkit-transition: all 0.3s ease;
204 | transition: all 0.3s ease;
205 | }
206 | .styled-button__moving-block {
207 | -webkit-transition: all 0.3s ease;
208 | transition: all 0.3s ease;
209 | position: absolute;
210 | left: 0;
211 | top: 0;
212 | height: 100%;
213 | width: 100%;
214 | overflow: hidden;
215 | }
216 | .styled-button__moving-block.back {
217 | color: white;
218 | -webkit-transform: translateX(-100%);
219 | transform: translateX(-100%);
220 | }
221 | .styled-button__moving-block.back .styled-button__text-holder {
222 | -webkit-transform: translateX(100%);
223 | transform: translateX(100%);
224 | }
225 | .styled-button:hover,
226 | .styled-button:active {
227 | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
228 | background: #1198ff;
229 | }
230 | .styled-button:hover .face,
231 | .styled-button:active .face {
232 | -webkit-transform: translateX(100%);
233 | transform: translateX(100%);
234 | }
235 | .styled-button:hover .face .styled-button__text-holder,
236 | .styled-button:active .face .styled-button__text-holder {
237 | -webkit-transform: translateX(-100%);
238 | transform: translateX(-100%);
239 | }
240 | .styled-button:hover .back,
241 | .styled-button:active .back {
242 | -webkit-transform: translateX(0);
243 | transform: translateX(0);
244 | }
245 | .styled-button:hover .back .styled-button__text-holder,
246 | .styled-button:active .back .styled-button__text-holder {
247 | -webkit-transform: translateX(0);
248 | transform: translateX(0);
249 | }
250 | .styled-button:active {
251 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
252 | }
253 | .styled-input {
254 | width: 100%;
255 | position: relative;
256 | margin-bottom: 25px;
257 | border: 1px solid rgba(0, 0, 0, 0.4);
258 | border-radius: 3px;
259 | -webkit-transition: all 0.3s ease;
260 | transition: all 0.3s ease;
261 | }
262 | .styled-input__circle {
263 | position: absolute;
264 | left: 0;
265 | top: 0;
266 | width: 100%;
267 | height: 100%;
268 | z-index: -2;
269 | overflow: hidden;
270 | border-radius: 3px;
271 | }
272 | .styled-input__circle:after {
273 | content: '';
274 | position: absolute;
275 | left: 16.5px;
276 | top: 19px;
277 | height: 14px;
278 | width: 14px;
279 | z-index: -2;
280 | border-radius: 50%;
281 | background: #a0e2ff;
282 | box-shadow: 0 0 10px rgba(255, 255, 255, 0);
283 | -webkit-transition: opacity 1s ease, -webkit-transform 0.6s ease;
284 | transition: opacity 1s ease, -webkit-transform 0.6s ease;
285 | transition: transform 0.6s ease, opacity 1s ease;
286 | transition: transform 0.6s ease, opacity 1s ease, -webkit-transform 0.6s ease;
287 | }
288 | .styled-input__input {
289 | width: 100%;
290 | -webkit-appearance: none;
291 | font-size: 14px;
292 | outline: none;
293 | background: none;
294 | padding: 18px 15px;
295 | color: #000000;
296 | border: none;
297 | font-weight: 600;
298 | letter-spacing: 0.035em;
299 | }
300 | .styled-input__placeholder {
301 | position: absolute;
302 | left: 0;
303 | top: 0;
304 | width: 100%;
305 | height: 100%;
306 | display: -webkit-box;
307 | display: -ms-flexbox;
308 | display: flex;
309 | -webkit-box-align: center;
310 | -ms-flex-align: center;
311 | align-items: center;
312 | z-index: -1;
313 | padding-left: 45px;
314 | color: #000000;
315 | }
316 | .styled-input__placeholder-text {
317 | -webkit-perspective: 500px;
318 | perspective: 500px;
319 | display: inline-block;
320 | }
321 | .styled-input__placeholder-text .letter {
322 | display: inline-block;
323 | vertical-align: middle;
324 | position: relative;
325 | -webkit-animation: letterAnimOut 0.25s ease forwards;
326 | animation: letterAnimOut 0.25s ease forwards;
327 | /* text-shadow: 0 0 5px; */
328 | }
329 | .styled-input__placeholder-text .letter.active {
330 | -webkit-animation: letterAnimIn 0.25s ease forwards;
331 | animation: letterAnimIn 0.25s ease forwards;
332 | }
333 | .styled-input:hover {
334 | border-color: #108cee;
335 | }
336 | .styled-input.filled {
337 | border-color: #108cee;
338 | }
339 | .styled-input.filled .styled-input__circle:after {
340 | -webkit-transform: scale(37);
341 | transform: scale(37);
342 | opacity: 0;
343 | }
344 | @-webkit-keyframes letterAnimIn {
345 | 0% {
346 | -webkit-transform: translate(0, 0);
347 | transform: translate(0, 0);
348 | }
349 | 25% {
350 | -webkit-transform: translate(0, 10px);
351 | transform: translate(0, 10px);
352 | color: #9c9c9c;
353 | }
354 | 45% {
355 | -webkit-transform: translate(0, 10px);
356 | transform: translate(0, 10px);
357 | opacity: 0;
358 | color: #9c9c9c;
359 | }
360 | 55% {
361 | -webkit-transform: translate(0, 10px);
362 | transform: translate(0, 10px);
363 | opacity: 0;
364 | }
365 | 56% {
366 | -webkit-transform: translate(-30px, -27px);
367 | transform: translate(-30px, -27px);
368 | opacity: 0;
369 | color: #c9c9c9;
370 | }
371 | 76% {
372 | color: #c9c9c9;
373 | opacity: 1;
374 | -webkit-transform: translate(-30px, -27px);
375 | transform: translate(-30px, -27px);
376 | }
377 | 100% {
378 | -webkit-transform: translate(-30px, -27px);
379 | transform: translate(-30px, -27px);
380 | opacity: 1;
381 | }
382 | }
383 | @keyframes letterAnimIn {
384 | 0% {
385 | -webkit-transform: translate(0, 0);
386 | transform: translate(0, 0);
387 | }
388 | 25% {
389 | -webkit-transform: translate(0, 10px);
390 | transform: translate(0, 10px);
391 | color: #9c9c9c;
392 | }
393 | 45% {
394 | -webkit-transform: translate(0, 10px);
395 | transform: translate(0, 10px);
396 | opacity: 0;
397 | color: #9c9c9c;
398 | }
399 | 55% {
400 | -webkit-transform: translate(0, 10px);
401 | transform: translate(0, 10px);
402 | opacity: 0;
403 | }
404 | 56% {
405 | -webkit-transform: translate(-30px, -27px);
406 | transform: translate(-30px, -27px);
407 | opacity: 0;
408 | color: #c9c9c9;
409 | }
410 | 76% {
411 | color: #c9c9c9;
412 | opacity: 1;
413 | -webkit-transform: translate(-30px, -27px);
414 | transform: translate(-30px, -27px);
415 | }
416 | 100% {
417 | -webkit-transform: translate(-30px, -27px);
418 | transform: translate(-30px, -27px);
419 | opacity: 1;
420 | }
421 | }
422 | @-webkit-keyframes letterAnimOut {
423 | 0% {
424 | -webkit-transform: translate(-30px, -27px);
425 | transform: translate(-30px, -27px);
426 | opacity: 1;
427 | }
428 | 25% {
429 | -webkit-transform: translate(-30px, -40px);
430 | transform: translate(-30px, -40px);
431 | opacity: 0;
432 | }
433 | 45% {
434 | -webkit-transform: translate(0, 10px);
435 | transform: translate(0, 10px);
436 | opacity: 0;
437 | }
438 | 55% {
439 | -webkit-transform: translate(0, 10px);
440 | transform: translate(0, 10px);
441 | opacity: 0;
442 | color: #9c9c9c;
443 | }
444 | 56% {
445 | -webkit-transform: translate(0, 10px);
446 | transform: translate(0, 10px);
447 | color: #9c9c9c;
448 | }
449 | 100% {
450 | -webkit-transform: translate(0, 0);
451 | transform: translate(0, 0);
452 | }
453 | }
454 | @keyframes letterAnimOut {
455 | 0% {
456 | -webkit-transform: translate(-30px, -27px);
457 | transform: translate(-30px, -27px);
458 | opacity: 1;
459 | }
460 | 25% {
461 | -webkit-transform: translate(-30px, -40px);
462 | transform: translate(-30px, -40px);
463 | opacity: 0;
464 | }
465 | 45% {
466 | -webkit-transform: translate(0, 10px);
467 | transform: translate(0, 10px);
468 | opacity: 0;
469 | }
470 | 55% {
471 | -webkit-transform: translate(0, 10px);
472 | transform: translate(0, 10px);
473 | opacity: 0;
474 | color: #9c9c9c;
475 | }
476 | 56% {
477 | -webkit-transform: translate(0, 10px);
478 | transform: translate(0, 10px);
479 | color: #9c9c9c;
480 | }
481 | 100% {
482 | -webkit-transform: translate(0, 0);
483 | transform: translate(0, 0);
484 | }
485 | }
486 | .spinner {
487 | position: relative;
488 | margin: auto;
489 | width: 50px;
490 | height: 50px;
491 | -webkit-transition: all 0.2s ease 0s;
492 | transition: all 0.2s ease 0s;
493 | }
494 | .spinner__circular {
495 | -webkit-animation: rotate 1.5s linear infinite;
496 | animation: rotate 1.5s linear infinite;
497 | -webkit-animation-play-state: paused;
498 | animation-play-state: paused;
499 | -webkit-transform-origin: center center;
500 | transform-origin: center center;
501 | position: absolute;
502 | width: 100%;
503 | height: 100%;
504 | top: 0;
505 | left: 0;
506 | margin: auto;
507 | }
508 | .spinner__path {
509 | stroke-dasharray: 1, 200;
510 | stroke-dashoffset: 0;
511 | -webkit-animation: dash 1.3s ease forwards 0.5s;
512 | animation: dash 1.3s ease forwards 0.5s;
513 | opacity: 0;
514 | stroke-linecap: round;
515 | stroke: #108cee;
516 | -webkit-animation-play-state: running;
517 | animation-play-state: running;
518 | }
519 | @-webkit-keyframes dash {
520 | 0% {
521 | stroke-dasharray: 1, 200;
522 | stroke-dashoffset: 0;
523 | opacity: 0;
524 | }
525 | 50% {
526 | stroke-dasharray: 40, 200;
527 | opacity: 1;
528 | }
529 | 100% {
530 | stroke-dasharray: 125, 200;
531 | opacity: 1;
532 | }
533 | }
534 | @keyframes dash {
535 | 0% {
536 | stroke-dasharray: 1, 200;
537 | stroke-dashoffset: 0;
538 | opacity: 0;
539 | }
540 | 50% {
541 | stroke-dasharray: 40, 200;
542 | opacity: 1;
543 | }
544 | 100% {
545 | stroke-dasharray: 125, 200;
546 | opacity: 1;
547 | }
548 | }
549 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------