├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── resources ├── config.yml ├── lang.yml └── plugin.yml └── src └── moe └── feo └── bbstoper ├── BBSToper.java ├── CLI.java ├── Crawler.java ├── Message.java ├── Option.java ├── PAPIExpansion.java ├── Poster.java ├── Reminder.java ├── Reward.java ├── Util.java ├── gui ├── GUI.java ├── GUIManager.java └── IDListener.java └── sql ├── MySQLer.java ├── SQLManager.java ├── SQLer.java └── SQLiter.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse files 2 | .settings/ 3 | .classpath 4 | .project 5 | 6 | # IDEA files 7 | .idea/ 8 | *.iml 9 | 10 | # Target folder 11 | target/ 12 | 13 | dependency-reduced-pom.xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BBSToper许可协议 2 | 3 | 版权所有 (c) 2018-2021 R_Josef <460257626@qq.com> 4 | 5 | 授予任何获得此软件副本的人免费许可. 6 | 免费许可的权利包括: 使用, 修改, 再分发, 出售. 7 | 但需要满足以下条件: 8 | 1. 分发的副本中必须包含此版权声明与许可协议. 9 | 2. 出售时必须向告知买方, 此软件是免费开源软件. 10 | 11 | 此软件开源发布, 作者不提供任何明示或暗示的保证. 12 | 使用者需自行承担风险, 任何情况都不该向作者索要任何赔偿. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BBSToper 2 | 3 | 这是一个检测mcbbs服务器宣传贴顶帖后,玩家输入指令领取奖励的bukkit插件. 4 | 5 | 此插件的mcbbs页面:[https://www.mcbbs.net/thread-789082-1-1.html](https://www.mcbbs.net/thread-789082-1-1.html) 6 | 7 | 可用发行版: [https://github.com/R-Josef/BBSToper/releases](https://github.com/R-Josef/BBSToper/releases) 8 | 9 | ## 许可 10 | 11 | 本软件的许可请查看[LICENCE](https://github.com/R-Josef/BBSToper/blob/master/LICENSE)文件. 12 | 13 | ## 用到的库 14 | 15 | 1. [Jsoup](https://jsoup.org/) 16 | 2. [bStats](https://bstats.org/) 17 | 3. [PlaceHolderAPI](https://github.com/PlaceholderAPI/PlaceholderAPI) 18 | 19 | ## 构建 20 | 21 | 此项目采用maven构建, 提供了pom文件, clone此git库后可以使用maven进行构建. 22 | 23 | ## 使用方法 24 | 25 | 1. 获得一份构建好的jar文件, 请查看[https://github.com/R-Josef/BBSToper/releases](https://github.com/R-Josef/BBSToper/releases) 26 | 2. 将构建好的文件放入plugins文件夹 27 | 3. 前往mcbbs复制您的帖子id并替换掉配置文件中默认链接中的id 28 | 4. 重启/启动服务器 29 | 30 | ## 命令&权限 31 | 32 | **玩家默认拥有`bbstoper.user`权限** 33 | 34 | | bbstoper.user的子权限 | 35 | | --------------------- | 36 | | `bbstoper.binding` | 37 | | `bbstoper.reward` | 38 | 39 | **op默认拥有`bbstoper.admin`权限** 40 | 41 | | bbstoper.admin的子权限 | 42 | | ------------------------------ | 43 | | `bbstoper.testreward` | 44 | | `bbstoper.list` | 45 | | `bbstoper.top` | 46 | | `bbstoper.check` | 47 | | `bbstoper.delete` | 48 | | `bbstoper.reload` | 49 | | `bbstoper.bypassquerycooldown` | 50 | 51 | **/bbstoper /poster /bt 都是可用命令别名** 52 | 53 | | 命令 | 权限 | 描述 | 54 | | ---------------------------------- | ------------------------------ | --------------------------------------------- | 55 | | `/bbstoper` | 无需权限 | 显示箱子GUI | 56 | | `/bbstoper help` | 无需权限 | 显示帮助信息 | 57 | | `/bbstoper binding ` | `bbstoper.binding` | 绑定论坛账号, 注意这里是ID不是uid | 58 | | `/bbstoper reward` | `bbstoper.reward` | 领取奖励 | 59 | | `/bbstoper testreward [模式]` | `bbstoper.testreward` | 测试奖励, 模式: `normal` `incentive` `offday` | 60 | | `/bbstoper list <页数>` | `bbstoper.list` | 列出所有顶帖者 | 61 | | `/bbstoper top <页数>` | `bbstoper.top` | 按照顶贴次数列排名出所有已绑定玩家 | 62 | | 无 | `bbstoper.bypassquerycooldown` | 绕过查询冷却 | 63 | | `/bbstoper check bbsid <论坛ID>` | `bbstoper.check` | 查看一个论坛id的绑定者 | 64 | | `/bbstoper check player <玩家ID>` | `bbstoper.check` | 查看一个玩家绑定的论坛id | 65 | | `/bbstoper delete player <玩家ID>` | `bbstoper.delete` | 删除一个玩家的数据 | 66 | | `/bbstoper reload` | `bbstoper.reload` | 重载插件 | 67 | 68 | ## PlaceholderAPI 占位符 69 | 70 | 本插件提供了一些基于PlaceHolderAPI的占位符(Placeholders), 要想使用这些占位符就必须在服务端上同时运行了[PlaceholderAPI](https://github.com/PlaceholderAPI/PlaceholderAPI)插件. 71 | 72 | | 占位符 | 描述 | 73 | | --------------------- | -------------------------------------------------- | 74 | | %bbstoper_bbsid% | 当前玩家的MCBBS用户名 | 75 | | %bbstoper_posttimes% | 当前玩家的顶贴次数 | 76 | | %bbstoper_pageid% | 宣传贴的id | 77 | | %bbstoper_pageurl% | 宣传贴的链接 | 78 | | %bbstoper_lastpost% | 上一次被顶贴的时间 | 79 | | %bbstoper_top_<序号>% | 顶贴排行第"序号"个的顶贴信息, 例: %bbstoper_top_1% | 80 | 81 | 82 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | moe.feo 6 | BBSToper 7 | 3.6.8 8 | 9 | 10 | spigot-repo 11 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 12 | 13 | 14 | CodeMC 15 | https://repo.codemc.org/repository/maven-public 16 | 17 | 18 | placeholderapi 19 | https://repo.extendedclip.com/content/repositories/placeholderapi/ 20 | 21 | 22 | 23 | 24 | 25 | org.spigotmc 26 | spigot-api 27 | 1.12.2-R0.1-SNAPSHOT 28 | provided 29 | 30 | 31 | 32 | org.bukkit 33 | bukkit 34 | 1.12.2-R0.1-SNAPSHOT 35 | provided 36 | 37 | 38 | org.jsoup 39 | jsoup 40 | 1.15.3 41 | 42 | 43 | mysql 44 | mysql-connector-java 45 | 8.0.28 46 | 47 | 48 | org.xerial 49 | sqlite-jdbc 50 | 3.28.0 51 | 52 | 53 | me.clip 54 | placeholderapi 55 | 2.10.6 56 | provided 57 | 58 | 59 | org.bstats 60 | bstats-bukkit 61 | 1.5 62 | compile 63 | 64 | 65 | 66 | src/ 67 | 68 | 69 | resources 70 | 71 | 72 | ${project.basedir} 73 | 74 | LICENSE 75 | README.md 76 | 77 | META-INF 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-shade-plugin 84 | 3.0.0 85 | 86 | 87 | package 88 | 89 | shade 90 | 91 | 92 | 93 | 94 | *:* 95 | 96 | META-INF/CHANGES 97 | 98 | 99 | 100 | 101 | 102 | org.bstats 103 | moe.feo.bstats 104 | 105 | 106 | org.jsoup 107 | moe.feo.jsoup 108 | 109 | 110 | true 111 | 112 | 113 | org.jsoup 114 | org.bstats 115 | 116 | 117 | false 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | UTF-8 126 | UTF-8 127 | 1.8 128 | 1.8 129 | 1.8 130 | 131 | -------------------------------------------------------------------------------- /resources/config.yml: -------------------------------------------------------------------------------- 1 | ######################################### 2 | # # 3 | # 这里是BBSToper的配置文件 # 4 | # # 5 | ######################################### 6 | 7 | #项目地址: https://github.com/R-Josef/BBSToper 8 | #MCBBS下载地址: https://www.mcbbs.net/thread-789082-1-1.html 9 | #请定期检查这些网址以获取可用的更新 10 | #Copyright © 2018-2020 R_Josef 版权所有 11 | 12 | #DEBUG模式 13 | debug: false 14 | 15 | #数据库设置 16 | database: 17 | #表名前缀, 在sqlite和mysql都有效 18 | #可用两个单引号表示留空, 例如prefix: '' 19 | prefix: 'bt_' 20 | #定时自动断开并重新连接数据库 21 | #数据库使用长连接有可能导致速度越来越慢(我不确定) 22 | #可以尝试开启此选项, 单位秒, 设置为0则关闭 23 | timingreconnect: 0 24 | #可选mysql或sqlite 25 | type: 'sqlite' 26 | #mysql设置 27 | mysql: 28 | ip: 'localhost' 29 | port: '3306' 30 | database: 'databasename' 31 | user: 'username' 32 | password: 'password' 33 | ssl: false 34 | #sqlite设置 35 | sqlite: 36 | folder: '%PLUGIN_FOLDER%' 37 | database: 'bbstoper.db' 38 | 39 | mcbbs: 40 | #MCBBS链接(用于特殊情况,末尾一定要有斜杠“/”) 41 | link: "https://www.mcbbs.net/" 42 | #服务器宣传贴的帖子id 43 | url: '1034574' 44 | #顶帖列表/玩家排名每页的大小 45 | pagesize: 10 46 | #玩家多久能更换一次绑定的id, 单位天 47 | changeidcooldown: 30 48 | #玩家多久能使用一次奖励指令以及查询指令, 单位秒 49 | querycooldown: 30 50 | #如果一个玩家没有顶贴是否在加入时发送提示消息 51 | joinmessage: true 52 | 53 | # 代理设置 54 | proxy: 55 | # 启用代理 56 | enable: false 57 | # 代理ip 58 | ip: '127.0.0.1' 59 | # 代理端口 60 | port: 10809 61 | 62 | gui: 63 | #玩家排名的显示数量 64 | topplayers: 5 65 | #显示GUI中的头颅皮肤 66 | #获取头颅皮肤时有可能因为网络原因造成卡顿(我不确定) 67 | #如果在打开GUI时遇到主线程卡顿, 请尝试关闭此项 68 | displayheadskin: true 69 | #是否启用按键后输入绑定ID 70 | #如果某些插件导致玩家聊天事件消息被修改或者取消 71 | #那么插件将获取不到正确的输入,这时候请关闭此项 72 | usechatgetid: true 73 | #取消绑定的关键词 74 | #在点击了绑定按钮之后会通过监听聊天消息来获取bbs用户名 75 | #而输入这些关键词可以取消监听要绑定的id的操作 76 | cancelkeywords: 77 | - 'cancel' 78 | - '取消' 79 | 80 | #奖励设置 81 | reward: 82 | #自动奖励间隔,将在指定的间隔下循环访问宣传帖 83 | #如有玩家顶贴就将自动给该玩家奖励,但必须该玩家在线 84 | #单位秒,设置为0则关闭,此选项需要大量网络IO建议设置为30左右 85 | auto: 30 86 | #顶贴后领取奖励的有效期, 单位天 87 | period: 10 88 | #同一个玩家距离上次顶贴多少分钟后再次顶贴才算有效, 设置为0则一直有效 89 | interval: 10 90 | #一天能领取几次顶帖奖励 91 | times: 1 92 | #奖励的命令, %PLAYER%代表使用命令的玩家ID 93 | commands: 94 | - 'experience add %PLAYER% 1000' 95 | - 'eco give %PLAYER% 20' 96 | #激励奖励 97 | #当玩家在无人顶贴超过一定时间之后顶贴, 这些奖励命令会被执行 98 | incentivereward: 99 | #是否启用 100 | enable: false 101 | #是否为额外奖励(是否还会给与普通的奖励) 102 | extra: true 103 | #间隔时间, 单位为分钟 104 | #距离上一次有人顶贴多久之后会给与激励奖励 105 | period: 30 106 | #奖励命令 107 | commands: 108 | - 'effect give %PLAYER% haste 2' 109 | #休息日奖励 110 | #当玩家在设定好的休息日顶贴, 这些奖励命令会被执行 111 | offdayreward: 112 | #是否启用 113 | enable: false 114 | #是否为额外奖励(是否还会给与普通的奖励) 115 | #注意: 当"休息日奖励"与"激励奖励"的"额外奖励"选项都为false时 116 | #如果"休息日奖励"与"激励奖励"的条件同时满足, 将只有"休息日奖励"会被发放 117 | extra: true 118 | #哪些日期会应用于这项设置 119 | #表示一个星期中的某一天: SUNDAY(周日), MONDAY(周一), TUESDAY(周二) 120 | #WEDNESDAY(周三), THURSDAY(周四), FRIDAY(周五), SATURDAY(周六) 121 | #普通日期格式为MM-dd, 例如10-01表示十月一日 122 | offdays: 123 | - 'SATURDAY' 124 | - 'SUNDAY' 125 | - '10-01' 126 | - '05-01' 127 | #奖励命令 128 | commands: 129 | - 'effect give %PLAYER% speed 2' 130 | -------------------------------------------------------------------------------- /resources/lang.yml: -------------------------------------------------------------------------------- 1 | ######################################### 2 | # # 3 | # 这里是BBSToper的语言文件 # 4 | # # 5 | ######################################### 6 | 7 | #插件前缀 8 | prefix: '&e[&dBBSToper&e] ' 9 | 10 | #载入 11 | enable: '插件已启用!' 12 | reload: '&a配置已重载!' 13 | 14 | #数据库 15 | failedconnectsql: '连接数据库时出错!' 16 | 17 | #查询冷却 18 | querycooldown: '&4查询操作正在冷却, 请在 %COOLDOWN% 秒后重试.' 19 | 20 | #列表通用 21 | posternum: '&2顶帖数量&f' 22 | overpage: '&4您输入的页码超出了总页数!' 23 | posterid: '&2论坛ID&f' 24 | #顶贴列表 25 | postertime: '&2顶帖日期&f' 26 | pageinfo: '&2当前/总页数:&f%PAGE%/%TOTALPAGE%&2, 翻页:&7/bt list <页码>' 27 | noposter: '&4没有找到顶帖数据!' 28 | #顶贴者列表 29 | posterplayer: '&2玩家ID&f' 30 | postertotal: '&2顶贴者数量&f' 31 | pageinfotop: '&2当前/总页数:&f%PAGE%/%TOTALPAGE%&2, 翻页:&7/bt top <页码>' 32 | noplayer: '&4没有找到玩家!' 33 | 34 | #奖励 35 | notbound: '&4您还未绑定账号!' 36 | nopost: '&4您暂未有新的顶帖记录!' 37 | overtime: '&4很抱歉您每天只能领取 &e%REWARDTIMES% &4次奖励!' 38 | waitamin: '&4连续顶帖间隔必须大于一分钟!' 39 | intervaltooshort: '&4您在 &f%TIME% &4的顶贴过于频繁, 您必须在距离上一次顶贴 &f%INTERVAL% &4分钟后再次顶贴才算有效.' 40 | reward: '&b您在 &f%TIME% &b进行的顶帖已得到回报.' 41 | extrareward: '&b您还获得了: %EXTRA%.' 42 | rewardgived: '&a奖励发放完毕!' 43 | broadcast: '&b玩家 &e%PLAYER% &b刚刚领取了顶贴奖励, 大家也快来顶贴吧! ' 44 | 45 | #绑定 46 | enter: '&c请在聊天框输入您的论坛ID, 或输入下列关键词取消绑定: %KEYWORD%.' 47 | canceled: '&c绑定已取消.' 48 | repeat: '&c为防止输错ID, 请重复输入一次!' 49 | notsame: '&4抱歉, 两次输入不一致,请重新输入!' 50 | oncooldown: '&4抱歉您的账号正在绑定冷却中: &f%COOLDOWN% 天.' 51 | samebind: '&4已有相同的绑定,如果这是您的ID,请联系管理!' 52 | ownsamebind: '&4您已经绑定过相同ID!' 53 | bindingsuccess: '&a绑定成功!' 54 | 55 | #Check 56 | idowner: '&b这个论坛ID的绑定者为: &e%PLAYER% &f(%UUID%).' 57 | idnotfound: '&4未找到该论坛ID的记录.' 58 | ownerid: '&b这名玩家绑定的论坛ID为: &f%ID%.' 59 | ownernotfound: '&4未找到该玩家的记录.' 60 | 61 | #删除 62 | deletesuccess: '&c成功删除了该玩家的记录.' 63 | 64 | #其他 65 | nopermission: '&4对不起,您没有权限这么做!' 66 | invalid: '&4命令错误!' 67 | invalidnum: '&4无效的数字!' 68 | playercmd: '&4该命令必须由玩家执行!' 69 | pagenotvisible: '&4宣传帖目前处于不可视状态, 如果您看到这条消息请通知管理员!' 70 | none: '&4 无' 71 | failedgetweb: '获取网页时错误, 可能是由于网络波动!' 72 | #failedresolveweb中的信息可能在您的宣传帖被移入审核区时反复出现 73 | #您可以通过留空该项来禁用这项提示, 留空的格式应为-failedresolveweb: '' 74 | failedresolveweb: '处理网页信息失败, 宣传帖目前可能不可视或被删除!' 75 | faileduninstallmo: '未能按照预期卸载监听!' 76 | 77 | #箱子GUI界面 78 | gui: 79 | title: '%PREFIX%' 80 | frame: '&8我是边框' 81 | skull: '&e%PLAYER%&b的绑定数据' 82 | notbound: '&4未绑定MCBBS账号' 83 | clickbound: '&c点击绑定' 84 | clickrebound: '&c点击重新绑定' 85 | bbsid: '&2绑定账号: &e%BBSID%' 86 | posttimes: '&2顶贴次数: &e%TIMES%' 87 | rewards: '&b奖励信息' 88 | incentiverewards: '&b激励奖励' 89 | offdayrewards: '&b休息日奖励' 90 | clickget: '&a点击领取奖励' 91 | tops: '&b顶贴排行' 92 | pagestate: '§b宣传帖状态' 93 | pageid: '&2帖子ID: &f%PAGEID%' 94 | lastpost: '&2上一次顶贴: &f%TIME%' 95 | extrarewards: '&2现在顶贴可获得: %EXTRA%' 96 | pagenotvisible: '&c宣传帖目前处于不可视状态' 97 | clickopen: '&a点击获取链接' 98 | #这里是自定义GUI中的奖励信息, 留空则直接显示奖励命令 ([]表示留空) 99 | #如果您想自定义,那么应该按照如下的格式来写 100 | #rewardsinfo: 101 | #- '&a自定义奖励信息1' 102 | #- '&b自定义奖励信息2' 103 | #- '&c自定义奖励信息3' 104 | rewardsinfo: [] 105 | 106 | #这里是玩家点击GUI中mcbbs宣传帖状态图标后发送的消息 107 | clickposticon: 108 | - '&8&l======================================' 109 | - '&b点击打开本服MCBBS宣传帖链接:' 110 | - '&9%PAGE%' 111 | - '&8&l======================================' 112 | 113 | #提醒, 您可以在此添加更多消息 114 | info: 115 | - '&b本服MCBBS宣传帖链接:' 116 | - '&9%PAGE%' 117 | - '&b对宣传帖使用提升卡将获得丰厚奖励.' 118 | #额外奖励提醒 119 | extrainfo: '&b现在顶贴还可以获得 %EXTRA% 哦.' 120 | 121 | #帮助 122 | help: 123 | title: '&3&lBBSToper指令帮助' 124 | help: '&3显示帮助信息: &b/bt help' 125 | binding: '&3绑定论坛账号: &b/bt binding ' 126 | reward: '&3获取奖励: &b/bt reward' 127 | testreward: '&3测试奖励: &b/bt testreward [ normal | incentive | offday ]' 128 | list: "&3查看顶帖列表: &b/bt list [页码]" 129 | top: "&3查看所有顶贴玩家排名: &b/bt top [页码]" 130 | check: '&3检查: &b/bt check < bbsid | player > <值>' 131 | delete: '&3删除某玩家的数据: &b/bt delete <玩家游戏ID>' 132 | reload: '&3重载插件: &b/bt reload' -------------------------------------------------------------------------------- /resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: BBSToper 2 | main: moe.feo.bbstoper.BBSToper 3 | version: 3.6.8 4 | author: Fengshuai(R_Josef) 5 | website: https://www.mcbbs.net/thread-789082-1-1.html 6 | softdepend: [PlaceholderAPI] 7 | commands: 8 | bbstoper: 9 | description: 'BBSToper的主要指令.' 10 | aliases: [bt, poster] 11 | usage: | 12 | /bbstoper - 打开GUI菜单 13 | /bbstoper help - 显示帮助页面 14 | /bbstoper binding - 绑定论坛账号,注意这里是ID不是uid 15 | /bbstoper reward - 领取奖励 16 | /bbstoper testreward <奖励种类> - 测试奖励 17 | /bbstoper list <页数> - 列出6天以内所有顶帖者 18 | /bbstoper top - 按顶贴次数顺序列出所有顶贴者 19 | /bbstoper check bbsid <论坛ID> - 查看一个论坛id的绑定者 20 | /bbstoper check player <玩家ID> - 查看一个玩家绑定的论坛id 21 | /bbstoper delete player <玩家ID> - 删除一个玩家的数据 22 | /bbstoper reload - 重载插件 23 | permissions: 24 | bbstoper.admin: 25 | description: '管理员权限.' 26 | default: 'op' 27 | children: 28 | bbstoper.testreward: true 29 | bbstoper.list: true 30 | bbstoper.top: true 31 | bbstoper.reload: true 32 | bbstoper.check: true 33 | bbstoper.delete: true 34 | bbstoper.bypassquerycooldown: true 35 | bbstoper.user: 36 | description: '用户权限.' 37 | default: true 38 | children: 39 | bbstoper.binding: true 40 | bbstoper.reward: true 41 | bbstoper.binding: 42 | description: '绑定论坛账号.' 43 | default: true 44 | bbstoper.reward: 45 | description: '获取顶贴奖励.' 46 | default: true 47 | bbstoper.testreward: 48 | description: '测试顶贴奖励.' 49 | default: 'op' 50 | bbstoper.list: 51 | description: '列出顶贴列表.' 52 | default: 'op' 53 | bbstoper.top: 54 | description: '将所有玩家按照顶贴次数排序.' 55 | default: 'op' 56 | bbstoper.bypassquerycooldown: 57 | description: '绕过查询冷却.' 58 | default: 'op' 59 | bbstoper.check: 60 | description: '检查玩家ID和论坛ID.' 61 | default: 'op' 62 | bbstoper.delete: 63 | description: '删除一个玩家的数据.' 64 | default: 'op' 65 | bbstoper.reload: 66 | description: '重载插件.' 67 | default: 'op' 68 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/BBSToper.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import org.bstats.bukkit.Metrics; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.plugin.java.JavaPlugin; 6 | 7 | import moe.feo.bbstoper.gui.GUIManager; 8 | import moe.feo.bbstoper.sql.SQLManager; 9 | 10 | public class BBSToper extends JavaPlugin { 11 | private static BBSToper bbstoper; 12 | 13 | public static BBSToper getInstance() { 14 | return bbstoper; 15 | } 16 | 17 | @Override 18 | public void onEnable() { 19 | bbstoper = this; 20 | this.saveDefaultConfig(); 21 | Option.load(); 22 | Message.saveDefaultConfig(); 23 | Message.load(); 24 | SQLManager.initializeSQLer(); 25 | this.getCommand("bbstoper").setExecutor(CLI.getInstance()); 26 | this.getCommand("bbstoper").setTabCompleter(CLI.getInstance()); 27 | new Reminder(this); 28 | new GUIManager(this); 29 | SQLManager.startTimingReconnect(); 30 | Util.startAutoReward(); 31 | if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { 32 | new PAPIExpansion().register(); 33 | } 34 | new Metrics(this); 35 | this.getLogger().info(Message.ENABLE.getString()); 36 | } 37 | 38 | @Override 39 | public void onDisable() { 40 | Bukkit.getScheduler().cancelTasks(bbstoper); 41 | Thread thread = new Thread(new Runnable(){ 42 | @Override 43 | public void run() { 44 | Util.waitForAllTask();// 此方法会阻塞 45 | SQLManager.closeSQLer(); 46 | bbstoper = null; 47 | } 48 | }); 49 | thread.setDaemon(true); 50 | thread.start(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/CLI.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.ArrayList; 5 | import java.util.Date; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.UUID; 10 | 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.OfflinePlayer; 13 | import org.bukkit.command.Command; 14 | import org.bukkit.command.CommandSender; 15 | import org.bukkit.command.TabExecutor; 16 | import org.bukkit.entity.Player; 17 | import org.bukkit.scheduler.BukkitRunnable; 18 | 19 | import moe.feo.bbstoper.gui.GUI; 20 | import moe.feo.bbstoper.gui.IDListener; 21 | import moe.feo.bbstoper.sql.SQLManager; 22 | import moe.feo.bbstoper.sql.SQLer; 23 | 24 | public class CLI implements TabExecutor { 25 | 26 | private static SQLer sql; 27 | private Map cache = new HashMap<>();// 这个map是为了暂存玩家的绑定信息的 28 | private Map queryrecord = new HashMap<>();// 这个map是用于储存玩家上次查询顶贴记录的时间 29 | 30 | private static CLI cli = new CLI(); 31 | 32 | private CLI() { 33 | 34 | } 35 | 36 | public static CLI getInstance() { 37 | return cli; 38 | } 39 | 40 | @Override 41 | public List onTabComplete(CommandSender sender, Command cmd, String label, String[] args) { 42 | if (args.length == 1) { 43 | List list = new ArrayList(); 44 | String arg = args[0].toLowerCase(); 45 | if ("help".startsWith(arg)) list.add("help"); 46 | if ("reward".startsWith(arg) && sender.hasPermission("bbstoper.reward")) { 47 | list.add("reward"); 48 | } 49 | if ("testreward".startsWith(arg) && sender.hasPermission("bbstoper.testreward")) { 50 | list.add("testreward"); 51 | } 52 | if ("binding".startsWith(arg) && sender.hasPermission("bbstoper.binding")) { 53 | list.add("binding"); 54 | } 55 | if ("list".startsWith(arg) && sender.hasPermission("bbstoper.list")) { 56 | list.add("list"); 57 | } 58 | if ("top".startsWith(arg) && sender.hasPermission("bbstoper.top")) { 59 | list.add("top"); 60 | } 61 | if ("check".startsWith(arg) && sender.hasPermission("bbstoper.check")) { 62 | list.add("check"); 63 | } 64 | if ("delete".startsWith(arg) && sender.hasPermission("bbstoper.delete")) { 65 | list.add("delete"); 66 | } 67 | if ("reload".startsWith(arg) && sender.hasPermission("bbstoper.reload")) { 68 | list.add("reload"); 69 | } 70 | return list; 71 | } 72 | if (args.length == 2) { 73 | if (args[0].equalsIgnoreCase("check")) { 74 | List list = new ArrayList(); 75 | if (sender.hasPermission("bbstoper.check")) { 76 | list.add("bbsid"); 77 | list.add("player"); 78 | } 79 | return list; 80 | } 81 | } 82 | return null; 83 | } 84 | 85 | @Override 86 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 87 | new BukkitRunnable() { 88 | 89 | @Override 90 | public void run() { 91 | Util.addRunningTaskID(this.getTaskId()); 92 | task(); 93 | Util.removeRunningTaskID(this.getTaskId()); 94 | } 95 | 96 | public void task() { 97 | if (args.length == 0) {// 没有带参数 98 | if (sender instanceof Player) { 99 | Player player = (Player) sender; 100 | new GUI(player); 101 | } else { 102 | String[] args = { "help" }; 103 | onCommand(sender, cmd, label, args); 104 | } 105 | return; 106 | } 107 | Crawler crawler;// 爬虫 108 | switch (args[0].toLowerCase()) { 109 | case "help": { 110 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TITLE.getString()); 111 | if (sender.hasPermission("bbstoper.reward")) { 112 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_REWARD.getString()); 113 | } 114 | if (sender.hasPermission("bbstoper.testreward")) { 115 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TESTREWARD.getString()); 116 | } 117 | if (sender.hasPermission("bbstoper.binding")) { 118 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_BINDING.getString()); 119 | } 120 | if (sender.hasPermission("bbstoper.list")) { 121 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_LIST.getString()); 122 | } 123 | if (sender.hasPermission("bbstoper.top")) { 124 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TOP.getString()); 125 | } 126 | if (sender.hasPermission("bbstoper.check")) { 127 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_CHECK.getString()); 128 | } 129 | if (sender.hasPermission("bbstoper.delete")) { 130 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_DELETE.getString()); 131 | } 132 | if (sender.hasPermission("bbstoper.reload")) { 133 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_RELOAD.getString()); 134 | } 135 | return; 136 | } 137 | case "binding": { 138 | if (!(sender instanceof Player)) { 139 | sender.sendMessage(Message.PLAYERCMD.getString()); 140 | sender.sendMessage(Message.HELP_HELP.getString()); 141 | return; 142 | } 143 | if (!(sender.hasPermission("bbstoper.binding"))) { 144 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 145 | IDListener.unregister(sender); 146 | return; 147 | } 148 | if (args.length == 2) { 149 | Player player = Bukkit.getPlayer(sender.getName()); 150 | String uuid = player.getUniqueId().toString(); 151 | Poster poster = sql.getPoster(uuid); 152 | boolean isrecording = true; 153 | if (poster != null) { 154 | long cd = System.currentTimeMillis() - poster.getBinddate();// 已经过了的cd 155 | long settedcd = Option.MCBBS_CHANGEIDCOOLDOWN.getInt() * (long) 86400000;// 设置的cd 156 | if (cd < settedcd) {// 如果还在cd那么直接return; 157 | long leftcd = settedcd - cd;// 剩下的cd 158 | long leftcdtodays = leftcd / 86400000; 159 | sender.sendMessage(Message.PREFIX.getString() + Message.ONCOOLDOWN.getString() 160 | .replaceAll("%COOLDOWN%", String.valueOf(leftcdtodays))); 161 | IDListener.unregister(sender); 162 | return; 163 | } 164 | } else { 165 | poster = new Poster(); 166 | isrecording = false; 167 | } 168 | String ownersuuid = sql.bbsNameCheck(args[1]); 169 | if (ownersuuid == null) {// 没有人绑定过这个论坛id 170 | if (cache.get(uuid) != null && cache.get(uuid).equals(args[1])) { 171 | poster.setUuid(uuid); 172 | poster.setName(sender.getName()); 173 | poster.setBbsname(args[1]); 174 | poster.setBinddate(System.currentTimeMillis()); 175 | if (isrecording) { 176 | sql.updatePoster(poster); 177 | } else { 178 | sql.addPoster(poster); 179 | } 180 | cache.put(uuid, null);// 绑定成功, 清理这个键 181 | sender.sendMessage(Message.PREFIX.getString() + Message.BINDINGSUCCESS.getString()); 182 | IDListener.unregister(sender); 183 | } else if (cache.get(uuid) == null) { 184 | cache.put(uuid, args[1]); 185 | sender.sendMessage(Message.PREFIX.getString() + Message.REPEAT.getString()); 186 | } else { 187 | sender.sendMessage(Message.PREFIX.getString() + Message.NOTSAME.getString()); 188 | cache.put(uuid, null); 189 | IDListener.unregister(sender); 190 | } 191 | return; 192 | } else if (ownersuuid.equals(uuid)) {// 自己绑定了这个论坛id 193 | sender.sendMessage(Message.PREFIX.getString() + Message.OWNSAMEBIND.getString()); 194 | IDListener.unregister(sender); 195 | return; 196 | } else { 197 | sender.sendMessage(Message.PREFIX.getString() + Message.SAMEBIND.getString()); 198 | IDListener.unregister(sender); 199 | return; 200 | } 201 | } else { 202 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 203 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_BINDING.getString()); 204 | IDListener.unregister(sender); 205 | return; 206 | } 207 | } 208 | case "reward": { 209 | if (!(sender instanceof Player)) { 210 | sender.sendMessage(Message.PLAYERCMD.getString()); 211 | sender.sendMessage(Message.HELP_HELP.getString()); 212 | return; 213 | } 214 | if (!sender.hasPermission("bbstoper.reward")) { 215 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 216 | return; 217 | } 218 | Player player = Bukkit.getPlayer(sender.getName()); 219 | String uuid = player.getUniqueId().toString(); 220 | Poster poster = sql.getPoster(uuid); 221 | if (poster == null) {// 没有绑定 222 | sender.sendMessage(Message.PREFIX.getString() + Message.NOTBOUND.getString()); 223 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_BINDING.getString()); 224 | return; 225 | } 226 | if (!sender.hasPermission("bbstoper.bypassquerycooldown")) { 227 | double cooldown = getQueryCooldown(((Player) sender).getUniqueId()); 228 | if (cooldown > 0) { 229 | sender.sendMessage(Message.PREFIX.getString() + Message.QUERYCOOLDOWN.getString() 230 | .replaceAll("%COOLDOWN%", String.valueOf((int) cooldown))); 231 | return; 232 | } else { 233 | queryrecord.put(((Player) sender).getUniqueId(), System.currentTimeMillis()); 234 | } 235 | } 236 | crawler = new Crawler(); 237 | if (!crawler.visible) { 238 | sender.sendMessage(Message.PREFIX.getString() + Message.PAGENOTVISIBLE.getString()); 239 | return; 240 | } 241 | String bbsname = poster.getBbsname(); 242 | List cache = new ArrayList<>();// 这个缓存是用来判断玩家的顶贴粒度是否小于一分钟 243 | boolean issucceed = false; 244 | boolean isovertime = false; 245 | boolean iswaitamin = false; 246 | boolean havepost = false; 247 | for (int i = 0; i < crawler.ID.size(); i++) {// 对ID进行遍历 248 | if (crawler.ID.get(i).equalsIgnoreCase(bbsname)) {// 如果ID等于poster的论坛名字 249 | List topstates = poster.getTopStates(); 250 | for (String cachedtime : cache) {// 判断玩家的顶贴粒度是否小于一分钟了 251 | if (cachedtime.equals(crawler.Time.get(i))) {// 缓存里面有这次时间 252 | for (String topstate : topstates) {// 然后再去遍历数据库里面存的时间 253 | if (topstate.equals(crawler.Time.get(i))) {// 如果数据库里面的时间也等于这次的时间 254 | // 那就说明玩家肯定有两次同样时间的顶贴,说明玩家顶贴间隔小于一分钟 255 | iswaitamin = true;// 我们这里只会提醒玩家一次 256 | } 257 | } 258 | } 259 | } 260 | if (!topstates.contains(crawler.Time.get(i))) {// 如果数据库里没有这次顶贴的记录 261 | havepost = true; 262 | String datenow = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 263 | if (!datenow.equals(poster.getRewardbefore())) {// 如果上一次顶贴不是今天,置零 264 | poster.setRewardbefore(datenow); 265 | poster.setRewardtime(0); 266 | } 267 | if (poster.getRewardtime() < Option.REWARD_TIMES.getInt()) {// 奖励次数小于设定值 268 | new Reward((Player) sender, crawler, i).award(); 269 | sql.addTopState(poster.getBbsname(), crawler.Time.get(i)); 270 | poster.setRewardtime(poster.getRewardtime() + 1);// rewardtime次数加一 271 | issucceed = true; 272 | } else { 273 | isovertime = true; 274 | } 275 | } 276 | } 277 | } 278 | sql.updatePoster(poster);// 更新poster 279 | if (issucceed) { 280 | sender.sendMessage(Message.PREFIX.getString() + Message.REWARDGIVED.getString()); 281 | for (Player p :Bukkit.getOnlinePlayers()) {// 给有奖励权限且能看见此玩家(防止Vanish)的玩家广播 282 | if (!p.canSee((Player)sender)) continue; 283 | if (!p.hasPermission("bbstoper.reward")) continue; 284 | p.sendMessage(Message.BROADCAST.getString().replaceAll("%PLAYER%", player.getName())); 285 | } 286 | } 287 | if (isovertime) { 288 | int rewardtimes = Option.REWARD_TIMES.getInt(); 289 | sender.sendMessage(Message.PREFIX.getString() + Message.OVERTIME.getString() 290 | .replaceAll("%REWARDTIMES%", Integer.toString(rewardtimes))); 291 | } 292 | if (iswaitamin) { 293 | sender.sendMessage(Message.PREFIX.getString() + Message.WAITAMIN.getString()); 294 | } 295 | if (!havepost) { 296 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPOST.getString()); 297 | } 298 | 299 | break; 300 | } 301 | 302 | case "testreward": { 303 | if (!(sender instanceof Player)) { 304 | sender.sendMessage(Message.PLAYERCMD.getString()); 305 | sender.sendMessage(Message.HELP_HELP.getString()); 306 | return; 307 | } 308 | if (!sender.hasPermission("bbstoper.testreward")) { 309 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 310 | return; 311 | } 312 | String type; 313 | if (args.length == 1) { 314 | type = "NORMAL"; 315 | } else if (args.length == 2) { 316 | type = args[1].toUpperCase(); 317 | if (!(type.equals("NORMAL") || type.equals("INCENTIVE") || type.equals("OFFDAY"))) { 318 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 319 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TESTREWARD.getString()); 320 | return; 321 | } 322 | } else { 323 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 324 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TESTREWARD.getString()); 325 | return; 326 | } 327 | Player player = Bukkit.getPlayer(sender.getName()); 328 | new Reward(player, null, 0).testAward(type); 329 | sender.sendMessage(Message.PREFIX.getString() + Message.REWARDGIVED.getString()); 330 | break; 331 | } 332 | 333 | case "list": { 334 | if (!sender.hasPermission("bbstoper.list")) { 335 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 336 | return; 337 | } 338 | if (sender instanceof Player && !sender.hasPermission("bbstoper.bypassquerycooldown")) { 339 | double cooldown = getQueryCooldown(((Player) sender).getUniqueId()); 340 | if (cooldown > 0) { 341 | sender.sendMessage(Message.PREFIX.getString() + Message.QUERYCOOLDOWN.getString() 342 | .replaceAll("%COOLDOWN%", String.valueOf((int) cooldown))); 343 | return; 344 | } else { 345 | queryrecord.put(((Player) sender).getUniqueId(), System.currentTimeMillis()); 346 | } 347 | } 348 | int page = 1; 349 | if (args.length == 2) { 350 | for (char c : args[1].toCharArray()) {// 判断参数是否为数字 351 | if (!Character.isDigit(c)) { 352 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 353 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TOP.getString()); 354 | return; 355 | } 356 | } 357 | try { 358 | page = Integer.parseInt(args[1]); 359 | } catch (NumberFormatException e) { 360 | sender.sendMessage(Message.INVALIDNUM.getString()); 361 | return; 362 | } 363 | 364 | } else if (args.length > 2) { 365 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 366 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_LIST.getString()); 367 | return; 368 | } 369 | crawler = new Crawler(); 370 | if (!crawler.visible) { 371 | if (sender instanceof Player) { 372 | sender.sendMessage(Message.PREFIX.getString() + Message.PAGENOTVISIBLE.getString()); 373 | return; 374 | } else { 375 | return; 376 | } 377 | } 378 | int totalpage = (int) Math.ceil((double) crawler.ID.size() / Option.MCBBS_PAGESIZE.getInt()); 379 | if (page > totalpage) { 380 | sender.sendMessage(Message.PREFIX.getString() + Message.OVERPAGE.getString()); 381 | return; 382 | } 383 | List msglist = new ArrayList(); 384 | msglist.add(Message.PREFIX.getString() + Message.POSTERNUM.getString() + ":" + crawler.ID.size()); 385 | for (int i = (page - 1) * Option.MCBBS_PAGESIZE.getInt(); i < page 386 | * Option.MCBBS_PAGESIZE.getInt(); i++) { 387 | if (i >= crawler.ID.size()) 388 | break;// 当i不再小于顶贴人数,该停了 389 | msglist.add(Message.POSTERID.getString() + ":" + crawler.ID.get(i) + " " 390 | + Message.POSTERTIME.getString() + ":" + crawler.Time.get(i)); 391 | } 392 | if (msglist.size() == 1) 393 | msglist.add(Message.NOPOSTER.getString()); 394 | String pageinfo = Message.PAGEINFO.getString(); 395 | pageinfo = pageinfo.replaceAll("%PAGE%", Integer.toString(page)); 396 | pageinfo = pageinfo.replaceAll("%TOTALPAGE%", Integer.toString(totalpage)); 397 | msglist.add(Message.PREFIX.getString() + pageinfo); 398 | for (int i = 0; i < msglist.size(); i++) { 399 | sender.sendMessage(msglist.get(i)); 400 | } 401 | break; 402 | } 403 | case "top": { 404 | if (!sender.hasPermission("bbstoper.top")) { 405 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 406 | return; 407 | } 408 | if (sender instanceof Player && !sender.hasPermission("bbstoper.bypassquerycooldown")) { 409 | double cooldown = getQueryCooldown(((Player) sender).getUniqueId()); 410 | if (cooldown > 0) { 411 | sender.sendMessage(Message.PREFIX.getString() + Message.QUERYCOOLDOWN.getString() 412 | .replaceAll("%COOLDOWN%", String.valueOf((int) cooldown))); 413 | return; 414 | } else { 415 | queryrecord.put(((Player) sender).getUniqueId(), System.currentTimeMillis()); 416 | } 417 | } 418 | int page = 1; 419 | if (args.length == 2) { 420 | for (char c : args[1].toCharArray()) {// 判断参数是否为数字 421 | if (!Character.isDigit(c)) { 422 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 423 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TOP.getString()); 424 | return; 425 | } 426 | } 427 | try { 428 | page = Integer.parseInt(args[1]); 429 | } catch (NumberFormatException e) { 430 | sender.sendMessage(Message.INVALIDNUM.getString()); 431 | return; 432 | } 433 | } else if (args.length > 2) { 434 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 435 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_TOP.getString()); 436 | return; 437 | } 438 | List posterlist = sql.getTopPosters(); 439 | posterlist.addAll(sql.getNoCountPosters()); 440 | int totalpage = (int) Math.ceil((double) posterlist.size() / Option.MCBBS_PAGESIZE.getInt()); 441 | if (page > totalpage) { 442 | sender.sendMessage(Message.PREFIX.getString() + Message.OVERPAGE.getString()); 443 | return; 444 | } 445 | List msglist = new ArrayList(); 446 | msglist.add(Message.PREFIX.getString() + Message.POSTERTOTAL.getString() + ":" + posterlist.size()); 447 | for (int i = (page - 1) * Option.MCBBS_PAGESIZE.getInt(); i < page 448 | * Option.MCBBS_PAGESIZE.getInt(); i++) { 449 | if (i >= posterlist.size()) 450 | break;// 当i不再小于顶贴人数,该停了 451 | Poster poster = posterlist.get(i); 452 | msglist.add(Message.POSTERPLAYER.getString() + ":" + poster.getName() + " " 453 | + Message.POSTERID.getString() + ":" + poster.getBbsname() + " " 454 | + Message.POSTERNUM.getString() + ":" + poster.getCount()); 455 | } 456 | if (msglist.size() == 1) 457 | msglist.add(Message.NOPLAYER.getString()); 458 | String pageinfo = Message.PAGEINFOTOP.getString(); 459 | pageinfo = pageinfo.replaceAll("%PAGE%", Integer.toString(page)); 460 | pageinfo = pageinfo.replaceAll("%TOTALPAGE%", Integer.toString(totalpage)); 461 | msglist.add(Message.PREFIX.getString() + pageinfo); 462 | for (int i = 0; i < msglist.size(); i++) { 463 | sender.sendMessage(msglist.get(i)); 464 | } 465 | break; 466 | } 467 | case "reload": { 468 | if (!(sender.hasPermission("bbstoper.reload"))) { 469 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 470 | return; 471 | } 472 | BBSToper.getInstance().saveDefaultConfig(); 473 | Option.load(); 474 | Message.saveDefaultConfig(); 475 | Message.load(); 476 | SQLManager.initializeSQLer(); 477 | SQLManager.startTimingReconnect(); 478 | Util.startAutoReward(); 479 | sender.sendMessage(Message.PREFIX.getString() + Message.RELOAD.getString()); 480 | break; 481 | } 482 | case "check": { 483 | if (!(sender.hasPermission("bbstoper.check"))) { 484 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 485 | return; 486 | } 487 | if (args.length != 3) { 488 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 489 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_CHECK.getString()); 490 | return; 491 | } 492 | switch (args[1].toLowerCase()) { 493 | case "bbsid": { 494 | String owneruuid = sql.bbsNameCheck(args[2]); 495 | if (owneruuid == null) { 496 | sender.sendMessage(Message.PREFIX.getString() + Message.IDNOTFOUND.getString()); 497 | return; 498 | } 499 | OfflinePlayer owner = Bukkit.getOfflinePlayer(UUID.fromString(owneruuid)); 500 | String ownername = owner.getName(); 501 | sender.sendMessage(Message.PREFIX.getString() + Message.IDOWNER.getString() 502 | .replaceAll("%PLAYER%", ownername).replaceAll("%UUID%", owneruuid)); 503 | return; 504 | } 505 | case "player": { 506 | @SuppressWarnings("deprecation") 507 | UUID owneruuid = Bukkit.getOfflinePlayer(args[2]).getUniqueId(); 508 | Poster poster = sql.getPoster(owneruuid.toString()); 509 | if (poster == null) { 510 | sender.sendMessage(Message.PREFIX.getString() + Message.OWNERNOTFOUND.getString()); 511 | return; 512 | } 513 | String mcbbsname = poster.getBbsname(); 514 | sender.sendMessage( 515 | Message.PREFIX.getString() + Message.OWNERID.getString().replaceAll("%ID%", mcbbsname)); 516 | return; 517 | } 518 | } 519 | } 520 | case "delete": { 521 | if (!(sender.hasPermission("bbstoper.delete"))) { 522 | sender.sendMessage(Message.PREFIX.getString() + Message.NOPERMISSION.getString()); 523 | return; 524 | } 525 | if (args.length != 2) { 526 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 527 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_DELETE.getString()); 528 | return; 529 | } 530 | @SuppressWarnings("deprecation") 531 | UUID uuid = Bukkit.getOfflinePlayer(args[1]).getUniqueId(); 532 | Poster poster = sql.getPoster(uuid.toString()); 533 | if (poster == null) { 534 | sender.sendMessage(Message.PREFIX.getString() + Message.OWNERNOTFOUND.getString()); 535 | return; 536 | } 537 | sql.deletePoster(uuid.toString()); 538 | sender.sendMessage(Message.PREFIX.getString() + Message.DELETESUCCESS.getString()); 539 | return; 540 | } 541 | default: { 542 | sender.sendMessage(Message.PREFIX.getString() + Message.INVALID.getString()); 543 | sender.sendMessage(Message.PREFIX.getString() + Message.HELP_HELP.getString()); 544 | return; 545 | } 546 | } 547 | } 548 | }.runTaskAsynchronously(BBSToper.getInstance()); 549 | return true; 550 | } 551 | 552 | public static void setSQLer(SQLer sql) { 553 | CLI.sql = sql; 554 | } 555 | 556 | public Map getCache() { 557 | return this.cache; 558 | } 559 | 560 | public void recordQuery(UUID uuid, Long time) { 561 | queryrecord.put(uuid, time); 562 | } 563 | 564 | public double getQueryCooldown(UUID uuid) { 565 | int cooldown = Option.MCBBS_QUERYCOOLDOWN.getInt() * 1000; 566 | long now = System.currentTimeMillis(); 567 | Long before = queryrecord.get(uuid); 568 | if (before == null) { 569 | before = 0l; 570 | } 571 | return (cooldown - (now - before)) / (double) 1000; 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Crawler.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.io.IOException; 4 | import java.text.ParseException; 5 | import java.text.SimpleDateFormat; 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.OfflinePlayer; 13 | import org.bukkit.entity.Player; 14 | import org.jsoup.Jsoup; 15 | import org.jsoup.nodes.Document; 16 | import org.jsoup.nodes.Element; 17 | import org.jsoup.select.Elements; 18 | 19 | import moe.feo.bbstoper.sql.SQLer; 20 | 21 | public class Crawler { 22 | private static SQLer sql; 23 | public List ID = new ArrayList(); 24 | public List Time = new ArrayList(); 25 | public boolean visible = true; 26 | 27 | public Crawler() { 28 | resolveWebData(); 29 | kickExpiredData(); 30 | } 31 | 32 | public void resolveWebData() { 33 | String url = Option.MCBBS_LINK.getString() + "forum.php?mod=misc&action=viewthreadmod&tid=" + Option.MCBBS_URL.getString() 34 | + "&mobile=no"; 35 | Document doc = null; 36 | try { 37 | if (Option.PROXY_ENABLE.getBoolean() == true) { 38 | doc = Jsoup.connect(url).proxy(Option.PROXY_IP.getString(), Option.PROXY_PORT.getInt()).get(); 39 | } else { 40 | doc = Jsoup.connect(url).get(); 41 | } 42 | } catch (IOException e) { 43 | if (Option.DEBUG.getBoolean()) { 44 | e.printStackTrace(); // 这里经常会因为网络连接不顺畅而报错 45 | } 46 | BBSToper.getInstance().getLogger().warning(Message.FAILEDGETWEB.getString()); 47 | return;// 没抓到网页就不要继续了,会空指针 48 | } 49 | Elements listclass = doc.getElementsByClass("list");// 获取一个class名为list的元素的合集 50 | Element list = null; 51 | try { 52 | list = listclass.get(0);// mcbbs顶贴列表页面只会有一个list,直接使用即可 53 | } catch (IndexOutOfBoundsException e) { 54 | this.visible = false; 55 | String warn = Message.FAILEDRESOLVEWEB.getString(); 56 | if (!warn.isEmpty()) { 57 | BBSToper.getInstance().getLogger().warning(Message.FAILEDRESOLVEWEB.getString()); 58 | } 59 | return; 60 | } 61 | Element listbody = list.getElementsByTag("tbody").get(0);// tbody表示表的身体而不是表头 62 | for (Element rows : listbody.getElementsByTag("tr")) {// tr是表的一行 63 | Elements cells = rows.getElementsByTag("td");// td表示一行的单元格,cells为单元格的合集 64 | String action = cells.get(2).text(); 65 | if (!(action.equals("提升(提升卡)")||action.equals("提升(服务器/交易代理提升卡)"))) {// 这里过滤掉不是提升卡的操作 66 | continue; 67 | } 68 | Element idcell = cells.get(0);// 第一个单元格中包含有id 69 | String id = idcell.getElementsByTag("a").get(0).text(); 70 | Element timecell = cells.get(1);// 第二个单元格就是time了 71 | String time = ""; 72 | Element timespan = timecell.getElementsByTag("span").first();// time有两种,一种在span标签里面 73 | if (timespan != null) { 74 | time = timespan.attr("title");// attr用于获取元素的属性值,这个值就是我们要的time 75 | } else { 76 | time = timecell.text();// 6天过后的时间将直接被包含在单元格中 77 | } 78 | ID.add(id); 79 | Time.add(time); 80 | } 81 | } 82 | 83 | public void kickExpiredData() {// 剔除过期的数据 84 | // 注意mcbbs的日期格式,月份和天数都是非零开始,小时分钟是从零开始 85 | SimpleDateFormat sdfm = new SimpleDateFormat("yyyy-M-d HH:mm"); 86 | Date now = new Date(); 87 | long validtime = Option.REWARD_PERIOD.getInt() * 24 * 60 * 60 * 1000L;// 有效期 88 | Date expirydate = new Date(now.getTime() - validtime);// 过期时间,如果小于这个时间则表示过期 89 | for (int i = 0; i < Time.size(); i++) { 90 | Date date = null; 91 | try { 92 | date = sdfm.parse(Time.get(i)); 93 | } catch (ParseException e) { 94 | e.printStackTrace(); 95 | return; 96 | } 97 | if (date.before(expirydate)) {// 过期了 98 | Time.remove(i); 99 | ID.remove(i); 100 | i--;// 这里要吧序数往前退一个 101 | } 102 | } 103 | 104 | } 105 | 106 | public void activeReward() {// 主动给玩家发奖励 107 | for (int i = 0; i < ID.size(); i++) { 108 | String bbsname = ID.get(i); 109 | String time = Time.get(i); 110 | if (!sql.checkTopstate(bbsname, time)) {// 如果这个记录不存在于数据库中 111 | String uuid = sql.bbsNameCheck(bbsname); 112 | Poster poster = sql.getPoster(uuid); 113 | if (uuid != null) {// 这个玩家已经绑定,这时候就可以开始对玩家进行检测了 114 | OfflinePlayer player = Bukkit.getOfflinePlayer(UUID.fromString(uuid)); 115 | Player olplayer; 116 | if (player.isOnline()) {// 如果玩家在线 117 | olplayer = Bukkit.getPlayer(UUID.fromString(uuid)); 118 | if (!olplayer.hasPermission("bbstoper.reward")) { 119 | continue;// 没有奖励权限的跳过 120 | } 121 | } else {// 不在线就跳过 122 | continue; 123 | } 124 | String datenow = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 125 | if (!datenow.equals(poster.getRewardbefore())) {// 上次领奖的日期不是今天,直接将奖励次数清零 126 | poster.setRewardbefore(datenow);// 奖励日期设置为今天 127 | poster.setRewardtime(0); 128 | } 129 | if (poster.getRewardtime() >= Option.REWARD_TIMES.getInt()) { 130 | continue;// 如果领奖次数已经大于设定值了,那么跳出循环 131 | } 132 | // 这时候就可以给玩家发奖励了 133 | new Reward(olplayer, this, i).award(); 134 | sql.addTopState(bbsname, time); 135 | poster.setRewardtime(poster.getRewardtime() + 1); 136 | sql.updatePoster(poster);// 把poster储存起来 137 | for (Player p :Bukkit.getOnlinePlayers()) {// 给有奖励权限且能看见此玩家(防止Vanish)的玩家广播 138 | if (!p.canSee(olplayer)) continue; 139 | if (!p.hasPermission("bbstoper.reward")) continue; 140 | p.sendMessage(Message.BROADCAST.getString().replaceAll("%PLAYER%", player.getName())); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | public static void setSQLer(SQLer sql) { 148 | Crawler.sql = sql; 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Message.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.logging.Level; 8 | import java.util.stream.Collectors; 9 | 10 | import org.bukkit.ChatColor; 11 | import org.bukkit.configuration.file.FileConfiguration; 12 | import org.bukkit.configuration.file.YamlConfiguration; 13 | 14 | public enum Message { 15 | PREFIX("prefix"), 16 | ENABLE("enable"), 17 | RELOAD("reload"), 18 | FAILEDCONNECTSQL("failedconnectsql"), 19 | QUERYCOOLDOWN("querycooldown"), 20 | POSTERID("posterid"), 21 | POSTERNUM("posternum"), 22 | OVERPAGE("overpage"), 23 | NOPLAYER("noplayer"), 24 | POSTERTIME("postertime"), 25 | PAGEINFO("pageinfo"), 26 | NOPOSTER("noposter"), 27 | POSTERPLAYER("posterplayer"), 28 | POSTERTOTAL("postertotal"), 29 | PAGEINFOTOP("pageinfotop"), 30 | NOTBOUND("notbound"), 31 | NOPOST("nopost"), 32 | OVERTIME("overtime"), 33 | WAITAMIN("waitamin"), 34 | INTERVALTOOSHORT("intervaltooshort"), 35 | REWARD("reward"), 36 | EXTRAREWARD("extrareward"), 37 | REWARDGIVED("rewardgived"), 38 | BROADCAST("broadcast"), 39 | ENTER("enter"), 40 | CANCELED("canceled"), 41 | REPEAT("repeat"), 42 | NOTSAME("notsame"), 43 | ONCOOLDOWN("oncooldown"), 44 | SAMEBIND("samebind"), 45 | OWNSAMEBIND("ownsamebind"), 46 | BINDINGSUCCESS("bindingsuccess"), 47 | IDOWNER("idowner"), 48 | IDNOTFOUND("idnotfound"), 49 | OWNERID("ownerid"), 50 | OWNERNOTFOUND("ownernotfound"), 51 | NOPERMISSION("nopermission"), 52 | INVALID("invalid"), 53 | INVALIDNUM("invalidnum"), 54 | PLAYERCMD("playercmd"), 55 | PAGENOTVISIBLE("pagenotvisible"), 56 | NONE("none"), 57 | FAILEDGETWEB("failedgetweb"), 58 | FAILEDRESOLVEWEB("failedresolveweb"), 59 | FAILEDUNINSTALLMO("faileduninstallmo"), 60 | GUI_TITLE("gui.title"), 61 | GUI_FRAME("gui.frame"), 62 | GUI_SKULL("gui.skull"), 63 | GUI_NOTBOUND("gui.notbound"), 64 | GUI_CLICKBOUND("gui.clickbound"), 65 | GUI_CLICKREBOUND("gui.clickrebound"), 66 | GUI_BBSID("gui.bbsid"), 67 | GUI_POSTTIMES("gui.posttimes"), 68 | GUI_REWARDS("gui.rewards"), 69 | GUI_INCENTIVEREWARDS("gui.incentiverewards"), 70 | GUI_OFFDAYREWARDS("gui.offdayrewards"), 71 | GUI_CLICKGET("gui.clickget"), 72 | GUI_TOPS("gui.tops"), 73 | GUI_PAGESTATE("gui.pagestate"), 74 | GUI_PAGEID("gui.pageid"), 75 | GUI_LASTPOST("gui.lastpost"), 76 | GUI_EXTRAREWARDS("gui.extrarewards"), 77 | GUI_PAGENOTVISIBLE("gui.pagenotvisible"), 78 | GUI_CLICKOPEN("gui.clickopen"), 79 | GUI_REWARDSINFO("gui.rewardsinfo"), 80 | CLICKPOSTICON("clickposticon"), 81 | DELETESUCCESS("deletesuccess"), 82 | INFO("info"), 83 | EXTRAINFO("extrainfo"), 84 | HELP_TITLE("help.title"), 85 | HELP_HELP("help.help"), 86 | HELP_BINDING("help.binding"), 87 | HELP_REWARD("help.reward"), 88 | HELP_TESTREWARD("help.testreward"), 89 | HELP_LIST("help.list"), 90 | HELP_TOP("help.top"), 91 | HELP_CHECK("help.check"), 92 | HELP_DELETE("help.delete"), 93 | HELP_RELOAD("help.reload"); 94 | 95 | public String path; 96 | 97 | private static FileConfiguration messageConfig; 98 | private static File messageFile; 99 | private String cacheString; // 缓存内容 100 | private List cacheStringList; // 缓存内容 101 | 102 | Message(String path) { 103 | this.path = path; 104 | } 105 | 106 | public static void load() {// 加载与重载 107 | if (messageFile == null) { 108 | messageFile = new File(BBSToper.getInstance().getDataFolder(), "lang.yml"); 109 | } 110 | messageConfig = YamlConfiguration.loadConfiguration(messageFile);// 加载配置 111 | try (Reader reader = new InputStreamReader(BBSToper.getInstance().getResource("lang.yml"), 112 | StandardCharsets.UTF_8)) {// 读取默认配置 113 | YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(reader); 114 | messageConfig.setDefaults(defConfig);// 设置默认 115 | } catch (IOException ioe) { 116 | BBSToper.getInstance().getLogger().log(Level.SEVERE, "读取默认语言文件时出错!", ioe); 117 | } 118 | // 删除缓存 119 | for (Message m : values()) { 120 | m.cacheString = null; 121 | m.cacheStringList = null; 122 | } 123 | } 124 | 125 | public static void saveDefaultConfig() { 126 | if (messageFile == null) { 127 | messageFile = new File(BBSToper.getInstance().getDataFolder(), "lang.yml"); 128 | } 129 | if (!messageFile.exists()) { 130 | BBSToper.getInstance().saveResource("lang.yml", false); 131 | } 132 | } 133 | 134 | public String getString() { 135 | if (cacheString != null) 136 | return cacheString; 137 | return cacheString = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(path)); 138 | } 139 | 140 | public List getStringList() { 141 | if (cacheStringList != null) 142 | return cacheStringList; 143 | return cacheStringList = Collections.unmodifiableList(// 禁止修改 144 | messageConfig.getStringList(path).stream().map(msg -> ChatColor.translateAlternateColorCodes('&', msg)) 145 | .collect(Collectors.toList())); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Option.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.Reader; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.List; 9 | import java.util.logging.Level; 10 | 11 | import org.bukkit.configuration.file.FileConfiguration; 12 | import org.bukkit.configuration.file.YamlConfiguration; 13 | 14 | public enum Option { 15 | DEBUG("debug"), 16 | DATABASE_TYPE("database.type"), 17 | DATABASE_PREFIX("database.prefix"), 18 | DATABASE_TIMINGRECONNECT("timingreconnect"), 19 | DATABASE_MYSQL_IP("database.mysql.ip"), 20 | DATABASE_MYSQL_PORT("database.mysql.port"), 21 | DATABASE_MYSQL_DATABASE("database.mysql.database"), 22 | DATABASE_MYSQL_USER("database.mysql.user"), 23 | DATABASE_MYSQL_PASSWORD("database.mysql.password"), 24 | DATABASE_MYSQL_SSL("database.mysql.ssl"), 25 | DATABASE_SQLITE_FOLDER("database.sqlite.folder"), 26 | DATABASE_SQLITE_DATABASE("database.sqlite.database"), 27 | MCBBS_LINK("mcbbs.link"), 28 | MCBBS_URL("mcbbs.url"), MCBBS_PAGESIZE("mcbbs.pagesize"), 29 | MCBBS_CHANGEIDCOOLDOWN("mcbbs.changeidcooldown"), 30 | MCBBS_QUERYCOOLDOWN("mcbbs.querycooldown"), 31 | MCBBS_JOINMESSAGE("mcbbs.joinmessage"), 32 | PROXY_ENABLE("proxy.enable"), 33 | PROXY_IP("proxy.ip"), 34 | PROXY_PORT("proxy.port"), 35 | GUI_TOPPLAYERS("gui.topplayers"), 36 | GUI_DISPLAYHEADSKIN("gui.displayheadskin"), 37 | GUI_USECHATGETID("gui.usechatgetid"), 38 | GUI_CANCELKEYWORDS("gui.cancelkeywords"), 39 | REWARD_AUTO("reward.auto"), 40 | REWARD_PERIOD("reward.period"), 41 | REWARD_INTERVAL("reward.interval"), 42 | REWARD_TIMES("reward.times"), 43 | REWARD_COMMANDS("reward.commands"), 44 | REWARD_INCENTIVEREWARD_ENABLE("reward.incentivereward.enable"), 45 | REWARD_INCENTIVEREWARD_EXTRA("reward.incentivereward.extra"), 46 | REWARD_INCENTIVEREWARD_PERIOD("reward.incentivereward.period"), 47 | REWARD_INCENTIVEREWARD_COMMANDS("reward.incentivereward.commands"), 48 | REWARD_OFFDAYREWARD_ENABLE("reward.offdayreward.enable"), 49 | REWARD_OFFDAYREWARD_EXTRA("reward.offdayreward.extra"), 50 | REWARD_OFFDAYREWARD_OFFDAYS("reward.offdayreward.offdays"), 51 | REWARD_OFFDAYREWARD_COMMANDS("reward.offdayreward.commands"); 52 | 53 | private static File file; 54 | private static FileConfiguration config; 55 | private String path; 56 | 57 | private Option(String path) { 58 | this.path = path; 59 | } 60 | 61 | public static void load() { 62 | if (file == null) { 63 | file = new File(BBSToper.getInstance().getDataFolder(), "config.yml"); 64 | } 65 | config = YamlConfiguration.loadConfiguration(file);// 用这个方法加载配置可以解决编码问题 66 | try (Reader reader = new InputStreamReader(BBSToper.getInstance().getResource("config.yml"), 67 | StandardCharsets.UTF_8)) {// 读取默认配置 68 | YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(reader); 69 | config.setDefaults(defConfig);// 设置默认 70 | } catch (IOException ioe) { 71 | BBSToper.getInstance().getLogger().log(Level.SEVERE, "读取默认配置文件时出错!", ioe); 72 | } 73 | } 74 | 75 | public String getString() { 76 | return config.getString(path); 77 | } 78 | 79 | public List getStringList() { 80 | return config.getStringList(path); 81 | } 82 | 83 | public boolean getBoolean() { 84 | return config.getBoolean(path); 85 | } 86 | 87 | public int getInt() { 88 | return config.getInt(path); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/PAPIExpansion.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.util.List; 4 | import java.util.regex.Pattern; 5 | 6 | import org.bukkit.entity.Player; 7 | 8 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 9 | import moe.feo.bbstoper.sql.SQLer; 10 | 11 | public class PAPIExpansion extends PlaceholderExpansion { 12 | 13 | private static SQLer sql; 14 | 15 | private String author; 16 | private String identifier; 17 | private String version; 18 | 19 | PAPIExpansion() { 20 | this.author = BBSToper.getInstance().getDescription().getAuthors().toString(); 21 | this.identifier = BBSToper.getInstance().getDescription().getName().toLowerCase(); 22 | this.version = BBSToper.getInstance().getDescription().getVersion(); 23 | } 24 | 25 | public static void setSQLer(SQLer sql) { 26 | PAPIExpansion.sql = sql; 27 | } 28 | 29 | // 因为是插件包含的类, PAPI不能重载这个拓展 30 | // 这个方法的重写是必须的 31 | @Override 32 | public boolean persist() { 33 | return true; 34 | } 35 | 36 | // 因为是插件包含的类, 不需要检查这个 37 | @Override 38 | public boolean canRegister() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public String getAuthor() { 44 | return author; 45 | } 46 | 47 | @Override 48 | public String getIdentifier() { 49 | return identifier; 50 | } 51 | 52 | @Override 53 | public String getVersion() { 54 | return version; 55 | } 56 | 57 | @Override 58 | public String onPlaceholderRequest(Player player, String identifier) { 59 | Poster poster; 60 | if (player != null) {// 有玩家 61 | poster = sql.getPoster(player.getUniqueId().toString()); 62 | if (identifier.equals("bbsid")) {// BBS用户名 63 | if (poster == null) { 64 | return Message.GUI_NOTBOUND.getString(); 65 | } else { 66 | return poster.getBbsname(); 67 | } 68 | } 69 | if (identifier.equals("posttimes")) {// 顶贴次数 70 | if (poster == null) { 71 | return Message.GUI_NOTBOUND.getString(); 72 | } else { 73 | return String.valueOf(poster.getTopStates().size()); 74 | } 75 | } 76 | } 77 | if (identifier.equals("pageid")) {// 宣传贴id 78 | return Option.MCBBS_URL.getString(); 79 | } 80 | if (identifier.equals("pageurl")) {// 宣传贴url 81 | return Option.MCBBS_LINK.getString() + "thread-" + Option.MCBBS_URL.getString() + "-1-1.html"; 82 | } 83 | if (identifier.equals("lastpost")) {// 上一次顶贴时间 84 | Crawler crawler = new Crawler(); 85 | if (crawler.visible) {// 如果帖子可视,就获取帖子最近一次顶贴 86 | if (crawler.Time.size() > 0) { // 如果从没有人顶帖,就以“----”代替上次顶帖时间(原来不加判断直接get会报索引范围错误) 87 | return crawler.Time.get(0); 88 | } else { 89 | return "----"; 90 | } 91 | } else { 92 | return Message.GUI_PAGENOTVISIBLE.getString();// 帖子不可视 93 | } 94 | } 95 | if (identifier.equals("extrarewards")) { 96 | String extra = Util.getExtraReward(new Crawler()); 97 | if (extra == null) { 98 | extra = Message.NONE.getString(); 99 | } 100 | return extra; 101 | } 102 | String pattern = "^top_[1-9]\\d*$";// top_正整数的正则表达式 103 | if (Pattern.matches(pattern, identifier)) {// 如果匹配这种格式 104 | int rank = Integer.parseInt(identifier.split("_")[1]); 105 | int index = rank - 1; 106 | List listposter = sql.getTopPosters(); 107 | if (index < listposter.size()) { 108 | return Message.POSTERPLAYER.getString() + ":" + listposter.get(index).getName() + " " 109 | + Message.POSTERID.getString() + ":" + listposter.get(index).getBbsname() + " " 110 | + Message.POSTERNUM.getString() + ":" + listposter.get(index).getCount(); 111 | } 112 | } 113 | return null; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Poster.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.util.List; 4 | 5 | import moe.feo.bbstoper.sql.SQLer; 6 | 7 | public class Poster { 8 | 9 | public static SQLer sql; 10 | 11 | private String uuid = "";// 顶贴者的uuid 12 | private String name = "";// 顶贴者id 13 | private String bbsname = "";// 顶贴者bbs用户名 14 | private long binddate = 0;// 绑定bbs用户名的时间 15 | private String rewardbefore = "";// 上一次获取奖励的时间 16 | private int rewardtime = 0;// 上次一领取了多少奖励 17 | private int count = 0;// 总计的顶贴次数 (不一定有数据) 18 | 19 | public static void setSQLer(SQLer sql) { 20 | Poster.sql = sql; 21 | } 22 | 23 | public String getUuid() { 24 | return uuid; 25 | } 26 | 27 | public void setUuid(String uuid) { 28 | this.uuid = uuid; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | public String getBbsname() { 40 | return bbsname; 41 | } 42 | 43 | public void setBbsname(String bbsname) { 44 | this.bbsname = bbsname; 45 | } 46 | 47 | public long getBinddate() { 48 | return binddate; 49 | } 50 | 51 | public void setBinddate(long binddate) { 52 | this.binddate = binddate; 53 | } 54 | 55 | public String getRewardbefore() { 56 | return rewardbefore; 57 | } 58 | 59 | public void setRewardbefore(String rewardbefore) { 60 | this.rewardbefore = rewardbefore; 61 | } 62 | 63 | public int getRewardtime() { 64 | return rewardtime; 65 | } 66 | 67 | public void setRewardtime(int rewardtime) { 68 | this.rewardtime = rewardtime; 69 | } 70 | 71 | public int getCount() { 72 | return count; 73 | } 74 | 75 | public void setCount(int count) { 76 | this.count = count; 77 | } 78 | 79 | public List getTopStates() { 80 | return sql.getTopStatesFromPoster(this); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Reminder.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.ArrayList; 5 | import java.util.Date; 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.EventPriority; 11 | import org.bukkit.event.Listener; 12 | import org.bukkit.event.player.PlayerJoinEvent; 13 | import org.bukkit.plugin.Plugin; 14 | import org.bukkit.scheduler.BukkitRunnable; 15 | 16 | import moe.feo.bbstoper.sql.SQLer; 17 | 18 | public class Reminder implements Listener { 19 | 20 | private static SQLer sql; 21 | 22 | public Reminder(Plugin plugin) { 23 | plugin.getServer().getPluginManager().registerEvents(this, plugin); 24 | } 25 | 26 | @EventHandler(priority = EventPriority.LOWEST) 27 | public void onPlayerJoin(PlayerJoinEvent event) { 28 | if (!Option.MCBBS_JOINMESSAGE.getBoolean()) {// 如果设置了不提示消息则直接返回 29 | return; 30 | } 31 | new BukkitRunnable() {// 这里由于牵涉到数据库IO, 主线程执行可能会卡顿,所以改成异步 32 | @Override 33 | public void run() { 34 | Util.addRunningTaskID(this.getTaskId()); 35 | task(); 36 | Util.removeRunningTaskID(this.getTaskId()); 37 | } 38 | 39 | public void task() { 40 | boolean isbinded = true;// 是否绑定 41 | boolean isposted = true;// 是否有顶贴者 42 | UUID uuid = event.getPlayer().getUniqueId(); 43 | Poster poster = sql.getPoster(uuid.toString()); 44 | String datenow = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 45 | if (poster == null) {// 玩家未绑定 46 | isbinded = false; 47 | isposted = false; 48 | } else if (!datenow.equals(poster.getRewardbefore())) {// 玩家上一次顶贴不是今天 49 | isposted = false; 50 | } 51 | if (!isposted) {// 没有顶贴 52 | List list = new ArrayList();// 提示的信息 53 | list.addAll(Message.INFO.getStringList()); 54 | Crawler crawler = new Crawler(); 55 | String extra = Util.getExtraReward(crawler); 56 | if (extra != null) {// 说明有额外奖励信息 57 | list.add(Message.EXTRAINFO.getString().replaceAll("%EXTRA%", extra)); 58 | } 59 | String url = Option.MCBBS_LINK.getString() + "thread-" + Option.MCBBS_URL.getString() + "-1-1.html"; 60 | for (String msg : list) { 61 | event.getPlayer().sendMessage(Message.PREFIX.getString() + msg.replaceAll("%PAGE%", url)); 62 | } 63 | } 64 | if (!isbinded) {// 没有绑定 65 | event.getPlayer().sendMessage(Message.PREFIX.getString() + Message.HELP_BINDING.getString()); 66 | } 67 | } 68 | }.runTaskAsynchronously(BBSToper.getInstance()); 69 | } 70 | 71 | public static void setSQLer(SQLer sql) { 72 | Reminder.sql = sql; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Reward.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.ArrayList; 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | import java.util.List; 9 | import java.util.regex.Pattern; 10 | 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.entity.Player; 13 | 14 | public class Reward { 15 | private Player player; // 发放奖励的对象 16 | private Crawler crawler; // 一个爬虫对象 17 | private int index; // 要发放奖励的那条记录的序号 18 | 19 | // current指需要判断的时间, before指上一个顶贴的时间 20 | public static boolean canIncentiveReward(Calendar current, Calendar before) { 21 | boolean result = false; 22 | if (Option.REWARD_INCENTIVEREWARD_ENABLE.getBoolean() == true) {// 开启了激励奖励 23 | Calendar copyofcurrent = (Calendar) before.clone();// 一个上次顶贴时间的副本 24 | copyofcurrent.add(Calendar.MINUTE, Option.REWARD_INCENTIVEREWARD_PERIOD.getInt());// 加上设定好的激励时间 25 | if (copyofcurrent.before(current)) {// 如果这个时间已经处于"当前领奖的记录"之前 26 | result = true; 27 | } 28 | } 29 | return result; 30 | } 31 | 32 | // current指需要判断的时间 33 | public static boolean canOffDayReward(Calendar current) { 34 | boolean result = false; 35 | if (Option.REWARD_OFFDAYREWARD_ENABLE.getBoolean() == true) {// 开启了休息日奖励 36 | for (String day : Option.REWARD_OFFDAYREWARD_OFFDAYS.getStringList()) { 37 | Pattern upcasepattern = Pattern.compile("^[A-Z]+$");// 全大写英文字符串 38 | Pattern datepattern = Pattern.compile("^\\d{2}-\\d{2}$");// 00-00格式的字符串 39 | if (upcasepattern.matcher(day).matches()) {// 如果是全大写英文字符 40 | Class clazz = Calendar.class; 41 | int dayofweek = 0; 42 | try { 43 | // https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html 44 | // 根据Calendar终态静态变量将字符串转换成星期 45 | dayofweek = clazz.getField(day).getInt(null); 46 | 47 | } catch (IllegalArgumentException e) {// 非法参数 48 | e.printStackTrace(); 49 | } catch (IllegalAccessException e) {// 非法访问 50 | e.printStackTrace(); 51 | } catch (NoSuchFieldException e) {// 没有这个属性 52 | e.printStackTrace(); 53 | } catch (SecurityException e) {// 安全 54 | e.printStackTrace(); 55 | } 56 | int dayofweekcurrent = current.get(Calendar.DAY_OF_WEEK);// 当前领奖的记录是星期几 57 | if (dayofweekcurrent == dayofweek) {// 如果当前就是设定的日子 58 | result = true; 59 | } 60 | } else if (datepattern.matcher(day).matches()) {// 如果是00-00这种字符串 61 | SimpleDateFormat offdayformat = new SimpleDateFormat("MM-dd"); 62 | Calendar offdaycalendar = Calendar.getInstance();// 设定的假日日期(1970年的...) 63 | try { 64 | Date offdaydate = offdayformat.parse(day); 65 | offdaycalendar.setTime(offdaydate); 66 | } catch (ParseException e) { 67 | e.printStackTrace(); 68 | } 69 | // 如果当前领奖的记录的月份和日号与设定值一样 70 | if (current.get(Calendar.MONTH) == offdaycalendar.get(Calendar.MONTH) 71 | && current.get(Calendar.DAY_OF_MONTH) == offdaycalendar.get(Calendar.DAY_OF_MONTH)) { 72 | result = true; 73 | } 74 | } 75 | } 76 | } 77 | return result; 78 | } 79 | 80 | public boolean isIntervalTooShort(Calendar thispost, int index) {// 判断顶贴间隔是否过短 81 | SimpleDateFormat bbsformat = new SimpleDateFormat("yyyy-M-d HH:mm");// mcbbs的日期格式 82 | Date thispostdate = thispost.getTime(); 83 | int x = index + 1;// 从下一个顶贴记录开始遍历 84 | while (true) { 85 | Date lastdate = null; 86 | // 当记录已经全部遍历完,就不用再继续了 87 | if (x >= crawler.Time.size()) { 88 | break; 89 | } 90 | try { 91 | lastdate = bbsformat.parse(crawler.Time.get(x)); 92 | } catch (ParseException e) { 93 | e.printStackTrace(); 94 | } // 遍历再上一次的顶贴时间 95 | // 当这次遍历到的时间减去当前领奖的时间已经大于设定的时间了,就不用继续遍历了 96 | if ((thispostdate.getTime() - lastdate.getTime()) / (1000 * 60) > Option.REWARD_INTERVAL.getInt()) { 97 | break; 98 | } 99 | // 当遍历到和这次领奖记录的bbsid一样的记录,说明这个人顶贴间隔小于设定时间了 100 | if (crawler.ID.get(x).equals(crawler.ID.get(index))) { 101 | return true; 102 | } 103 | x++; 104 | } 105 | return false; 106 | } 107 | 108 | public Reward(Player player, Crawler crawler, int index) { 109 | this.player = player; 110 | this.crawler = crawler; 111 | this.index = index; 112 | } 113 | 114 | public void award() { 115 | List cmds = new ArrayList(); 116 | boolean incentive = false;// 是否符合激励奖励条件 117 | boolean offday = false;// 是否符合休息日奖励条件 118 | boolean normal = true;// 是否发放普通奖励 119 | SimpleDateFormat bbsformat = new SimpleDateFormat("yyyy-M-d HH:mm");// mcbbs的日期格式 120 | Calendar thispost = Calendar.getInstance();// "当前领奖的记录"的时间 121 | try { 122 | Date thipostdate = bbsformat.parse(crawler.Time.get(index)); 123 | thispost.setTime(thipostdate); 124 | } catch (ParseException e) { 125 | e.printStackTrace(); 126 | } 127 | // 如果顶贴间隔短于设定值则不进行操作 128 | if (Option.REWARD_INTERVAL.getInt() > 0 && isIntervalTooShort(thispost, index)) { 129 | player.sendMessage(Message.PREFIX.getString() + Message.INTERVALTOOSHORT.getString() 130 | .replaceAll("%TIME%", crawler.Time.get(index)).replaceAll("%INTERVAL%", Option.REWARD_INTERVAL.getString())); 131 | return; 132 | } 133 | Calendar lastpost = Calendar.getInstance();// 上一次顶贴的时间 134 | if (crawler.Time.size() > index + 1) {// 如果有这一次之前的顶贴记录 135 | try { 136 | Date lastpostdate = bbsformat.parse(crawler.Time.get(index + 1));// 同理这里应该获取之前那一条记录 137 | lastpost.setTime(lastpostdate); 138 | } catch (ParseException e) { 139 | e.printStackTrace(); 140 | } 141 | } else { 142 | lastpost.setTime(new Date(0));// 没人顶过贴, 将时间设置为1970 143 | } 144 | if (canIncentiveReward(thispost, lastpost)) { 145 | incentive = true; 146 | } 147 | if (canOffDayReward(thispost)) { 148 | offday = true; 149 | } 150 | String extra = null; 151 | if (incentive) {// 如果激励奖励条件达成 152 | // 如果休息日奖励也达成了, 并且激励奖励和休息日奖励都不是额外奖励, 不会发放激励奖励(只会发放休息日奖励) 153 | if (!(offday && Option.REWARD_INCENTIVEREWARD_EXTRA.getBoolean() == false 154 | && Option.REWARD_OFFDAYREWARD_EXTRA.getBoolean() == false)) { 155 | cmds.addAll(Option.REWARD_INCENTIVEREWARD_COMMANDS.getStringList()); 156 | extra = new String(Message.GUI_INCENTIVEREWARDS.getString()); 157 | } 158 | if (Option.REWARD_INCENTIVEREWARD_EXTRA.getBoolean() == false) { 159 | // 如果这不是额外奖励, 则普通奖励不会发放 160 | normal = false; 161 | } 162 | } 163 | if (offday) {// 如果休息日奖励条件达成 164 | cmds.addAll(Option.REWARD_OFFDAYREWARD_COMMANDS.getStringList()); 165 | if (extra == null) { 166 | extra = new String(Message.GUI_OFFDAYREWARDS.getString()); 167 | } else { 168 | extra = extra + "+" + Message.GUI_OFFDAYREWARDS.getString(); 169 | } 170 | if (Option.REWARD_OFFDAYREWARD_EXTRA.getBoolean() == false) { 171 | // 如果这不是额外奖励, 则普通奖励不会发放 172 | normal = false; 173 | } 174 | } 175 | if (normal) { 176 | cmds.addAll(Option.REWARD_COMMANDS.getStringList()); 177 | } 178 | // 让主线程执行 179 | Bukkit.getScheduler().runTask(BBSToper.getInstance(), new Runnable() { 180 | @Override 181 | public void run() { 182 | for (String cmd : cmds) { 183 | cmd = cmd.replaceAll("%PLAYER%", player.getName()); 184 | Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd); 185 | } 186 | } 187 | }); 188 | // 给玩家发个消息表示祝贺 189 | player.sendMessage( 190 | Message.PREFIX.getString() + Message.REWARD.getString().replaceAll("%TIME%", crawler.Time.get(index))); 191 | if (extra != null) { 192 | player.sendMessage( 193 | Message.PREFIX.getString() + Message.EXTRAREWARD.getString().replaceAll("%EXTRA%", extra)); 194 | } 195 | } 196 | 197 | public void testAward(String type) { 198 | List cmds = new ArrayList<>(); 199 | switch (type) { 200 | case "NORMAL": { 201 | cmds.addAll(Option.REWARD_COMMANDS.getStringList()); 202 | break; 203 | } 204 | case "INCENTIVE": { 205 | cmds.addAll(Option.REWARD_INCENTIVEREWARD_COMMANDS.getStringList()); 206 | break; 207 | } 208 | case "OFFDAY": { 209 | cmds.addAll(Option.REWARD_OFFDAYREWARD_COMMANDS.getStringList()); 210 | break; 211 | } 212 | } 213 | // 让主线程执行 214 | Bukkit.getScheduler().runTask(BBSToper.getInstance(), new Runnable() { 215 | @Override 216 | public void run() { 217 | for (String cmd : cmds) { 218 | cmd = cmd.replaceAll("%PLAYER%", player.getName()); 219 | Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd); 220 | } 221 | } 222 | }); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/Util.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.ArrayList; 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | import java.util.concurrent.TimeoutException; 9 | 10 | import org.bukkit.scheduler.BukkitRunnable; 11 | import org.bukkit.scheduler.BukkitTask; 12 | 13 | public class Util { 14 | 15 | private static BukkitTask autorewardtask; 16 | private static ArrayList runningtaskidlist = new ArrayList(); 17 | 18 | public static void startAutoReward() {// 自动奖励的方法 19 | if (autorewardtask != null) {// 任务对象不为空 20 | boolean taskcancelled;// 是否已经取消 21 | try { 22 | taskcancelled = autorewardtask.isCancelled(); 23 | } catch (NoSuchMethodError e) {// 1.7.10还没有这个方法 24 | taskcancelled = false;// 默认就当这个任务没有取消 25 | } 26 | if (!taskcancelled) {// 如果任务还被取消 27 | autorewardtask.cancel();// 将之前的任务取消 28 | } 29 | } 30 | int period = Option.REWARD_AUTO.getInt() * 20; 31 | if (period > 0) { 32 | autorewardtask = new BukkitRunnable() {// 自动奖励,异步执行 33 | @Override 34 | public void run() { 35 | addRunningTaskID(this.getTaskId()); 36 | task(); 37 | removeRunningTaskID(this.getTaskId()); 38 | } 39 | 40 | public void task() { 41 | Crawler crawler = new Crawler(); 42 | if (!crawler.visible) 43 | return; 44 | crawler.activeReward(); 45 | } 46 | }.runTaskTimerAsynchronously(BBSToper.getInstance(), 0, period); 47 | } 48 | } 49 | 50 | public static void waitForAllTask() {// 此方法会阻塞直到所有此插件创建的线程结束 51 | int count = 0; 52 | try { 53 | while (!runningtaskidlist.isEmpty()) {// 当list非空,阻塞线程100毫秒后再判断一次 54 | if (count > 30000) {// 超过30秒没有关闭就算超时 55 | throw new TimeoutException(); 56 | } 57 | Thread.sleep(100); 58 | count = count + 100; 59 | } 60 | } catch (InterruptedException | TimeoutException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | public static void addRunningTaskID(int i) { 66 | if (!runningtaskidlist.contains(i)) 67 | runningtaskidlist.add(i); 68 | } 69 | 70 | public static void removeRunningTaskID(int i) { 71 | if (runningtaskidlist.contains(i)) 72 | runningtaskidlist.remove((Integer) i); 73 | } 74 | 75 | public static String getExtraReward(Crawler crawler) {// 获取会获得的额外奖励(可为空) 76 | boolean incentive = false;// 是否符合激励奖励条件 77 | boolean offday = false;// 是否符合休息日奖励条件 78 | Calendar current = Calendar.getInstance();// 当前时间 79 | Calendar lastpost = Calendar.getInstance();// 上一次顶贴的时间 80 | if (crawler.Time.size() > 0) {// 如果有顶贴记录的话 81 | SimpleDateFormat bbsformat = new SimpleDateFormat("yyyy-M-d HH:mm");// mcbbs的日期格式 82 | Date lastpostdate = null; 83 | try { 84 | lastpostdate = bbsformat.parse(crawler.Time.get(0)); 85 | } catch (ParseException e) { 86 | e.printStackTrace(); 87 | } 88 | lastpost.setTime(lastpostdate); 89 | } 90 | if (Reward.canIncentiveReward(current, lastpost)) { 91 | incentive = true; 92 | } 93 | if (Reward.canOffDayReward(current)) { 94 | offday = true; 95 | } 96 | String extra = null; 97 | if (incentive) { 98 | // 如果休息日奖励也达成了, 并且激励奖励和休息日奖励都不是额外奖励, 不会发放激励奖励(只会发放休息日奖励) 99 | if (!(offday && Option.REWARD_INCENTIVEREWARD_EXTRA.getBoolean() == false 100 | && Option.REWARD_OFFDAYREWARD_EXTRA.getBoolean() == false)) { 101 | extra = new String(Message.GUI_INCENTIVEREWARDS.getString()); 102 | } 103 | } 104 | if (offday) { 105 | if (extra == null) { 106 | extra = new String(Message.GUI_OFFDAYREWARDS.getString()); 107 | } else { 108 | extra = extra + "+" + Message.GUI_OFFDAYREWARDS.getString(); 109 | } 110 | } 111 | return extra; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/gui/GUI.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper.gui; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.Material; 8 | import org.bukkit.SkullType; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.inventory.InventoryType; 11 | import org.bukkit.inventory.Inventory; 12 | import org.bukkit.inventory.InventoryHolder; 13 | import org.bukkit.inventory.ItemStack; 14 | import org.bukkit.inventory.meta.ItemMeta; 15 | import org.bukkit.inventory.meta.SkullMeta; 16 | 17 | import moe.feo.bbstoper.BBSToper; 18 | import moe.feo.bbstoper.Crawler; 19 | import moe.feo.bbstoper.Message; 20 | import moe.feo.bbstoper.Option; 21 | import moe.feo.bbstoper.Poster; 22 | import moe.feo.bbstoper.Util; 23 | import moe.feo.bbstoper.sql.SQLer; 24 | 25 | public class GUI { 26 | 27 | private static SQLer sql; 28 | private Inventory inv; 29 | 30 | public static String getTitle() {// 获取插件的gui标题必须用此方法,因为用户可能会修改gui标题 31 | String title = Message.GUI_TITLE.getString().replaceAll("%PREFIX%", Message.PREFIX.getString()); 32 | return title; 33 | } 34 | 35 | public GUI(Player player) { 36 | createGui(player); 37 | Bukkit.getScheduler().runTask(BBSToper.getInstance(), () -> player.openInventory(inv)); 38 | } 39 | 40 | class BBSToperGUIHolder implements InventoryHolder {// 定义一个Holder用于识别此插件的GUI 41 | @Override 42 | public Inventory getInventory() { 43 | return getGui(); 44 | } 45 | } 46 | 47 | @SuppressWarnings("deprecation") 48 | public void createGui(Player player) { 49 | InventoryHolder holder = new BBSToperGUIHolder(); 50 | this.setGui(Bukkit.createInventory(holder, InventoryType.CHEST, getTitle())); 51 | for (int i = 0; i < inv.getSize(); i++) {// 设置边框 52 | if (i > 9 && i < 17) 53 | continue; 54 | inv.setItem(i, getRandomPane()); 55 | } 56 | ItemStack skull; 57 | try { 58 | skull = new ItemStack(Material.SKULL_ITEM, 1, (short) SkullType.PLAYER.ordinal()); 59 | } catch (NoSuchFieldError e) {// 某些高版本服务端不兼容旧版写法 60 | skull = new ItemStack(Material.getMaterial("PLAYER_HEAD"), 1); 61 | } 62 | SkullMeta skullmeta = (SkullMeta) skull.getItemMeta();// 玩家头颅 63 | if (Option.GUI_DISPLAYHEADSKIN.getBoolean()) {// 如果开启了头颅显示,才会设置头颅的所有者 64 | try { 65 | skullmeta.setOwningPlayer(player); 66 | } catch (NoSuchMethodError e) {// 这里为了照顾低版本 67 | skullmeta.setOwner(player.getName()); 68 | } 69 | } 70 | skullmeta.setDisplayName(Message.GUI_SKULL.getString().replaceAll("%PLAYER%", player.getName())); 71 | List skulllores = new ArrayList(); 72 | Poster poster = sql.getPoster(player.getUniqueId().toString()); 73 | if (poster == null) { 74 | skulllores.add(Message.GUI_NOTBOUND.getString()); 75 | skulllores.add(Message.GUI_CLICKBOUND.getString()); 76 | } else { 77 | skulllores.add(Message.GUI_BBSID.getString().replaceAll("%BBSID%", poster.getBbsname())); 78 | skulllores.add(Message.GUI_POSTTIMES.getString().replaceAll("%TIMES%", "" + poster.getTopStates().size())); 79 | skulllores.add(Message.GUI_CLICKREBOUND.getString()); 80 | } 81 | skullmeta.setLore(skulllores); 82 | skull.setItemMeta(skullmeta); 83 | inv.setItem(12, skull); 84 | ItemStack sunflower; 85 | try { 86 | sunflower = new ItemStack(Material.DOUBLE_PLANT); 87 | } catch (NoSuchFieldError e) {// 某些高版本服务端不兼容旧版写法 88 | sunflower = new ItemStack(Material.getMaterial("SUNFLOWER")); 89 | } 90 | ItemMeta sunflowermeta = sunflower.getItemMeta(); 91 | sunflowermeta.setDisplayName(Message.GUI_REWARDS.getString()); 92 | List sunflowerlores = new ArrayList(Message.GUI_REWARDSINFO.getStringList());// 自定义奖励信息 93 | if (sunflowerlores.isEmpty()) {// 如果没有自定义奖励信息 94 | sunflowerlores.addAll(Option.REWARD_COMMANDS.getStringList());// 直接显示命令 95 | if (Option.REWARD_INCENTIVEREWARD_ENABLE.getBoolean()) { 96 | sunflowerlores.add(Message.GUI_INCENTIVEREWARDS.getString());// 激励奖励 97 | sunflowerlores.addAll(Option.REWARD_INCENTIVEREWARD_COMMANDS.getStringList());// 激励奖励命令 98 | } 99 | if (Option.REWARD_OFFDAYREWARD_ENABLE.getBoolean()) { 100 | sunflowerlores.add(Message.GUI_OFFDAYREWARDS.getString());// 休息日奖励 101 | sunflowerlores.addAll(Option.REWARD_OFFDAYREWARD_COMMANDS.getStringList()); // 休息日奖励命令 102 | } 103 | } 104 | sunflowerlores.add(Message.GUI_CLICKGET.getString()); 105 | sunflowermeta.setLore(sunflowerlores); 106 | sunflower.setItemMeta(sunflowermeta); 107 | inv.setItem(13, sunflower); 108 | ItemStack star = new ItemStack(Material.NETHER_STAR); 109 | ItemMeta starmeta = star.getItemMeta(); 110 | starmeta.setDisplayName(Message.GUI_TOPS.getString()); 111 | List starlores = new ArrayList(); 112 | List listposter = sql.getTopPosters(); 113 | for (int i = 0; i < listposter.size(); i++) { 114 | if (i >= Option.GUI_TOPPLAYERS.getInt()) 115 | break; 116 | starlores.add(Message.POSTERPLAYER.getString() + ":" + listposter.get(i).getName() + " " 117 | + Message.POSTERID.getString() + ":" + listposter.get(i).getBbsname() + " " 118 | + Message.POSTERNUM.getString() + ":" + listposter.get(i).getCount()); 119 | } 120 | starmeta.setLore(starlores); 121 | star.setItemMeta(starmeta); 122 | inv.setItem(14, star); 123 | ItemStack compass = new ItemStack(Material.COMPASS); 124 | ItemMeta compassmeta = compass.getItemMeta(); 125 | compassmeta.setDisplayName(Message.GUI_PAGESTATE.getString()); 126 | List compasslores = new ArrayList(); 127 | compasslores.add(Message.GUI_PAGEID.getString().replaceAll("%PAGEID%", Option.MCBBS_URL.getString())); 128 | Crawler crawler = new Crawler(); 129 | if (crawler.visible) {// 如果帖子可视,就获取帖子最近一次顶贴 130 | if (crawler.Time.size() > 0) { // 如果从没有人顶帖,就以“----”代替上次顶帖时间(原来不加判断直接get会报索引范围错误) 131 | compasslores.add(Message.GUI_LASTPOST.getString().replaceAll("%TIME%", crawler.Time.get(0))); 132 | } else { 133 | compasslores.add(Message.GUI_LASTPOST.getString().replaceAll("%TIME%", "----")); 134 | } 135 | } else { 136 | compasslores.add(Message.GUI_PAGENOTVISIBLE.getString()); 137 | } 138 | String extra = Util.getExtraReward(crawler); 139 | if (extra != null) { 140 | String extrarewards = Message.GUI_EXTRAREWARDS.getString().replaceAll("%EXTRA%", extra); 141 | compasslores.add(extrarewards); 142 | } 143 | compasslores.add(Message.GUI_CLICKOPEN.getString()); 144 | compassmeta.setLore(compasslores); 145 | compass.setItemMeta(compassmeta); 146 | inv.setItem(22, compass); 147 | } 148 | 149 | public ItemStack getRandomPane() {// 获取随机一种颜色的玻璃板 150 | short data = (short)(Math.random()* 16);// 这会随机取出0-15的数据值 151 | while (data == 8) {// 8号亮灰色染色玻璃板根本没有颜色 152 | data = (short)(Math.random()* 16); 153 | } 154 | ItemStack frame; 155 | try { 156 | frame = new ItemStack(Material.STAINED_GLASS_PANE, 1, data); 157 | 158 | } catch (NoSuchFieldError e) {// 某些高版本服务端不兼容旧版写法 159 | String[] glasspanes = {"WHITE_STAINED_GLASS_PANE", "ORANGE_STAINED_GLASS_PANE", "MAGENTA_STAINED_GLASS_PANE", 160 | "LIGHT_BLUE_STAINED_GLASS_PANE", "YELLOW_STAINED_GLASS_PANE", "LIME_STAINED_GLASS_PANE", "PINK_STAINED_GLASS_PANE", 161 | "GRAY_STAINED_GLASS_PANE", "LIGHT_GRAY_STAINED_GLASS_PANE", "CYAN_STAINED_GLASS_PANE", "PURPLE_STAINED_GLASS_PANE", 162 | "BLUE_STAINED_GLASS_PANE", "BROWN_STAINED_GLASS_PANE", "GREEN_STAINED_GLASS_PANE", "RED_STAINED_GLASS_PANE", 163 | "BLACK_STAINED_GLASS_PANE"}; 164 | frame = new ItemStack(Material.getMaterial(glasspanes[data]), 1); 165 | } 166 | ItemMeta framemeta = frame.getItemMeta(); 167 | framemeta.setDisplayName(Message.GUI_FRAME.getString()); 168 | frame.setItemMeta(framemeta); 169 | return frame; 170 | } 171 | 172 | public Inventory getGui() { 173 | return inv; 174 | } 175 | 176 | public void setGui(Inventory inv) { 177 | this.inv = inv; 178 | } 179 | 180 | public static void setSQLer(SQLer sql) { 181 | GUI.sql = sql; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/gui/GUIManager.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper.gui; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.Listener; 6 | import org.bukkit.event.inventory.InventoryClickEvent; 7 | import org.bukkit.inventory.InventoryHolder; 8 | import org.bukkit.plugin.Plugin; 9 | 10 | import moe.feo.bbstoper.CLI; 11 | import moe.feo.bbstoper.Message; 12 | import moe.feo.bbstoper.Option; 13 | 14 | import java.util.Arrays; 15 | import java.util.UUID; 16 | 17 | public class GUIManager implements Listener { 18 | 19 | public GUIManager(Plugin plugin) { 20 | plugin.getServer().getPluginManager().registerEvents(this, plugin); 21 | } 22 | 23 | @EventHandler 24 | public void onInventoryClick(InventoryClickEvent event) { 25 | if (!(event.getWhoClicked() instanceof Player)) 26 | return;// 如果不是玩家操作的,返回 27 | Player player = (Player) event.getWhoClicked(); 28 | InventoryHolder holder = player.getOpenInventory().getTopInventory().getHolder(); 29 | if(holder instanceof GUI.BBSToperGUIHolder) {// 确认操作的是此插件的GUI 30 | event.setCancelled(true); 31 | if (event.getRawSlot() == 12) {// 点击绑定 32 | if (Option.GUI_USECHATGETID.getBoolean() == true) { 33 | player.closeInventory(); 34 | UUID uid = player.getUniqueId(); 35 | synchronized (IDListener.lock) { // 线程锁防止异步错位修改 36 | IDListener rglistener = IDListener.map.get(uid); 37 | // 如果这个玩家没有一个监听器 38 | if (rglistener == null) { 39 | new IDListener(player.getUniqueId()).register();// 为此玩家创建一个监听器 40 | String keywords = Arrays.toString(Option.GUI_CANCELKEYWORDS.getStringList().toArray()); 41 | player.sendMessage(Message.PREFIX.getString() + Message.ENTER.getString().replaceAll("%KEYWORD%", keywords)); 42 | } 43 | } 44 | } 45 | if (Option.GUI_USECHATGETID.getBoolean() == false) { 46 | player.closeInventory(); 47 | player.sendMessage(Message.PREFIX.getString() + Message.HELP_BINDING.getString()); 48 | } 49 | } 50 | if (event.getRawSlot() == 13) { 51 | player.closeInventory(); 52 | String[] args = { "reward" }; 53 | CLI.getInstance().onCommand(player, null, null, args); 54 | } 55 | if (event.getRawSlot() == 22) {// 获取链接 56 | player.closeInventory(); 57 | for (String msg : Message.CLICKPOSTICON.getStringList()) { 58 | String url = Option.MCBBS_LINK.getString() + "thread-" + Option.MCBBS_URL.getString() + "-1-1.html"; 59 | player.sendMessage(msg.replaceAll("%PAGE%", url)); 60 | } 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/gui/IDListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Module Copyright (c) 2018-2019 Karlatemp. All rights reserved. 3 | * Reserved.FileName: IDListener.java@author: karlatemp@vip.qq.com: 19-9-17 下午1:39@version: 2.0 4 | * Only the following methods: 5 | * Module name: com.maddyhome.idea.copyright.pattern.ModuleInfo@646505a3 6 | * Module Methods: 7 | * void execute(Listener, Event); 8 | * void (UUID); 9 | * void callEvent(Event); 10 | * void register(); 11 | * void unregister(); 12 | * void unregister(UUID); 13 | * void unregister(CommandSender); 14 | * void register(); 15 | */ 16 | 17 | package moe.feo.bbstoper.gui; 18 | 19 | import java.util.*; 20 | import org.bukkit.command.CommandSender; 21 | import org.bukkit.entity.Player; 22 | import org.bukkit.event.Event; 23 | import org.bukkit.event.EventException; 24 | import org.bukkit.event.EventPriority; 25 | import org.bukkit.event.Listener; 26 | import org.bukkit.event.player.AsyncPlayerChatEvent; 27 | import org.bukkit.plugin.EventExecutor; 28 | import org.bukkit.plugin.RegisteredListener; 29 | import org.bukkit.scheduler.BukkitRunnable; 30 | 31 | import moe.feo.bbstoper.BBSToper; 32 | import moe.feo.bbstoper.CLI; 33 | import moe.feo.bbstoper.Message; 34 | import moe.feo.bbstoper.Option; 35 | import moe.feo.bbstoper.Util; 36 | 37 | public class IDListener extends RegisteredListener implements Listener, EventExecutor { 38 | public static final Object lock = new Object(); // 线程锁 39 | public static final Map map = new HashMap<>(); 40 | 41 | private UUID uid; 42 | private boolean state; 43 | 44 | @Override 45 | public void execute(Listener listener, Event event) throws EventException { 46 | callEvent(event); 47 | } 48 | 49 | @Override 50 | public void callEvent(Event event) throws EventException { 51 | if (event instanceof AsyncPlayerChatEvent) { 52 | onPlayerChat((AsyncPlayerChatEvent) event); 53 | } 54 | } 55 | 56 | public IDListener(UUID uuid) {// 在构造函数中初始化RegisteredListener和UUID 57 | super(null, null, EventPriority.HIGH, BBSToper.getInstance(), false); 58 | this.uid = uuid; 59 | this.state = false; 60 | } 61 | 62 | public static void unregister(UUID uniqueId) { 63 | synchronized (lock) { 64 | Optional.ofNullable(map.get(uniqueId)).ifPresent(IDListener::unregister); 65 | } 66 | } 67 | 68 | public static void unregister(CommandSender sender) { 69 | if (sender instanceof Player) { 70 | unregister(((Player) sender).getUniqueId()); 71 | } 72 | } 73 | 74 | public void unregister() { 75 | synchronized (lock) { 76 | AsyncPlayerChatEvent.getHandlerList().unregister((RegisteredListener) this); 77 | if (!map.remove(uid, this)) { 78 | BBSToper.getInstance().getLogger().warning(Message.FAILEDUNINSTALLMO.getString()); 79 | } 80 | } 81 | } 82 | 83 | public void register() { 84 | for (RegisteredListener lis : AsyncPlayerChatEvent.getHandlerList().getRegisteredListeners()) { 85 | if (lis == this) 86 | return; // 如果已经注册就取消注册 87 | } 88 | synchronized (lock) { 89 | IDListener old = map.put(uid, this); 90 | if (old != null && old != this) 91 | old.unregister();// 防止遗留 92 | } 93 | new BukkitRunnable() { 94 | @Override 95 | public void run() { 96 | Util.addRunningTaskID(this.getTaskId()); 97 | unregister(uid); 98 | Util.removeRunningTaskID(this.getTaskId()); 99 | } 100 | }.runTaskLater(BBSToper.getInstance(), 2 * 60 * 20);// 如果这个监听器还存在,那么将在2分钟后被取消 101 | AsyncPlayerChatEvent.getHandlerList().register(this); 102 | } 103 | 104 | public UUID getUid() {// 获取玩家名 105 | return this.uid; 106 | } 107 | 108 | public void onPlayerChat(AsyncPlayerChatEvent event) {// 处理事件 109 | if (!event.getPlayer().getUniqueId().equals(uid)) 110 | return; 111 | Player player = event.getPlayer(); 112 | String msg = event.getMessage(); 113 | event.setCancelled(true); 114 | List cancelkeywords = Option.GUI_CANCELKEYWORDS.getStringList();// 取消绑定关键词 115 | if (cancelkeywords.contains(msg)) {// 如果关键词中包含这次输入的消息 116 | unregister();// 取消监听事件 117 | CLI.getInstance().getCache().put(player.getUniqueId().toString(), null);// 清理这个键 118 | player.sendMessage(Message.PREFIX.getString() + Message.CANCELED.getString()); 119 | return; 120 | } 121 | List list = new ArrayList<>(Arrays.asList(msg.split("\\s+"))); 122 | list.add(0, "binding"); 123 | String[] args = list.toArray(new String[0]); 124 | CLI.getInstance().onCommand(player, null, null, args); 125 | if (state) {// state为true说明这是第二次进入这个方法 126 | unregister(); 127 | } else {// state为false说明是第一次进入 128 | state = true; 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/sql/MySQLer.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper.sql; 2 | 3 | import moe.feo.bbstoper.BBSToper; 4 | import moe.feo.bbstoper.Message; 5 | import moe.feo.bbstoper.Option; 6 | 7 | import java.sql.Connection; 8 | import java.sql.DriverManager; 9 | import java.sql.SQLException; 10 | import java.sql.Statement; 11 | import java.util.logging.Level; 12 | 13 | public class MySQLer extends SQLer { 14 | 15 | private final static MySQLer sqler = new MySQLer(); 16 | private Connection conn; 17 | 18 | private MySQLer() { 19 | 20 | } 21 | 22 | public static MySQLer getInstance() { 23 | return sqler; 24 | } 25 | 26 | @Override 27 | protected Connection getConnection() { 28 | return this.conn; 29 | } 30 | 31 | @Override 32 | protected void closeConnection() { 33 | try { 34 | if (!conn.isClosed()) {// 如果连接没有关闭,则将关闭这个连接 35 | conn.close(); 36 | } 37 | } catch (SQLException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | public String getUrl() {// 获取数据库url 43 | Boolean ssl = Option.DATABASE_MYSQL_SSL.getBoolean(); 44 | String url = "jdbc:mysql://" + Option.DATABASE_MYSQL_IP.getString() + ":" 45 | + Option.DATABASE_MYSQL_PORT.getString() + "/" + Option.DATABASE_MYSQL_DATABASE.getString() + "?useSSL=" 46 | + ssl.toString() + "&serverTimezone=UTC" + "&autoReconnect=true" + "&allowPublicKeyRetrieval=true" + "&characterEncoding=utf8"; 47 | return url; 48 | } 49 | 50 | @Override 51 | protected void load() { 52 | connect(); 53 | createTablePosters(); 54 | createTableTopStates(); 55 | } 56 | 57 | protected void connect() { 58 | String driver = "com.mysql.jdbc.Driver"; 59 | String user = Option.DATABASE_MYSQL_USER.getString(); 60 | String password = Option.DATABASE_MYSQL_PASSWORD.getString(); 61 | try { 62 | Class.forName(driver); 63 | this.conn = DriverManager.getConnection(getUrl(), user, password); 64 | } catch (ClassNotFoundException | SQLException e) { 65 | BBSToper.getInstance().getLogger().log(Level.WARNING, Message.FAILEDCONNECTSQL.getString(), e); 66 | } 67 | } 68 | 69 | protected void createTablePosters() { 70 | String sql = String.format( 71 | "CREATE TABLE IF NOT EXISTS `%s` ( `uuid` char(36) NOT NULL, `name` varchar(255) NOT NULL, `bbsname` varchar(255) NOT NULL, `binddate` bigint(0) NOT NULL, `rewardbefore` char(10) NOT NULL, `rewardtimes` int(0) NOT NULL, PRIMARY KEY (`uuid`) ) CHARACTER SET utf8 COLLATE utf8_unicode_ci;", 72 | getTableName("posters")); 73 | try (Statement stmt = conn.createStatement()) { 74 | stmt.execute(sql); 75 | } catch (SQLException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | 80 | protected void createTableTopStates() { 81 | String sql = String.format( 82 | "CREATE TABLE IF NOT EXISTS `%s` ( `id` int(0) NOT NULL AUTO_INCREMENT, `bbsname` varchar(255) NOT NULL, `time` varchar(16) NOT NULL, PRIMARY KEY (`id`) ) CHARACTER SET utf8 COLLATE utf8_unicode_ci;", 83 | getTableName("topstates")); 84 | try (Statement stmt = conn.createStatement()) { 85 | stmt.execute(sql); 86 | } catch (SQLException e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/sql/SQLManager.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper.sql; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.scheduler.BukkitRunnable; 5 | import org.bukkit.scheduler.BukkitTask; 6 | 7 | import moe.feo.bbstoper.BBSToper; 8 | import moe.feo.bbstoper.CLI; 9 | import moe.feo.bbstoper.Crawler; 10 | import moe.feo.bbstoper.Option; 11 | import moe.feo.bbstoper.PAPIExpansion; 12 | import moe.feo.bbstoper.Poster; 13 | import moe.feo.bbstoper.Reminder; 14 | import moe.feo.bbstoper.Util; 15 | import moe.feo.bbstoper.gui.GUI; 16 | 17 | public class SQLManager { 18 | public static SQLer sql; 19 | private static BukkitTask timingreconnecttask; 20 | 21 | public static void initializeSQLer() {// 初始化或重载数据库 22 | SQLer.writelock.lock(); 23 | try { 24 | if (sql != null) { 25 | sql.closeConnection();// 此方法会在已经建立过连接的情况下关闭连接 26 | } 27 | if (Option.DATABASE_TYPE.getString().equalsIgnoreCase("mysql")) { 28 | sql = MySQLer.getInstance(); 29 | } else if (Option.DATABASE_TYPE.getString().equalsIgnoreCase("sqlite")) { 30 | sql = SQLiter.getInstance(); 31 | } 32 | sql.load(); 33 | CLI.setSQLer(sql); 34 | GUI.setSQLer(sql); 35 | Crawler.setSQLer(sql); 36 | Poster.setSQLer(sql); 37 | Reminder.setSQLer(sql); 38 | if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { 39 | PAPIExpansion.setSQLer(sql); 40 | } 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } finally { 44 | SQLer.writelock.unlock(); 45 | } 46 | } 47 | 48 | public static void closeSQLer() {// 关闭数据库 49 | sql.closeConnection(); 50 | sql = null; 51 | } 52 | 53 | public static void startTimingReconnect() {// 自动重连数据库的方法 54 | if (timingreconnecttask != null && !timingreconnecttask.isCancelled()) {// 将之前的任务取消(如果存在) 55 | timingreconnecttask.cancel(); 56 | } 57 | int period = Option.DATABASE_TIMINGRECONNECT.getInt() * 20; 58 | if (period > 0) { 59 | timingreconnecttask = new BukkitRunnable() { 60 | @Override 61 | public void run() { 62 | Util.addRunningTaskID(this.getTaskId()); 63 | initializeSQLer();// 重载数据库 64 | Util.removeRunningTaskID(this.getTaskId()); 65 | } 66 | }.runTaskTimerAsynchronously(BBSToper.getInstance(), period, period); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/sql/SQLer.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper.sql; 2 | 3 | import moe.feo.bbstoper.Option; 4 | import moe.feo.bbstoper.Poster; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | import java.sql.ResultSet; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.locks.Lock; 12 | import java.util.concurrent.locks.ReadWriteLock; 13 | import java.util.concurrent.locks.ReentrantReadWriteLock; 14 | 15 | public abstract class SQLer { 16 | 17 | public static final ReadWriteLock lock = new ReentrantReadWriteLock(); 18 | public static final Lock readlock = lock.readLock(); 19 | public static final Lock writelock = lock.writeLock(); 20 | 21 | public String getTableName(String name) {// 获取数据表应有的名字 22 | return Option.DATABASE_PREFIX.getString() + name; 23 | } 24 | 25 | public void addPoster(Poster poster) { 26 | readlock.lock(); 27 | String sql = String.format( 28 | "INSERT INTO `%s` (`uuid`, `name`, `bbsname`, `binddate`, `rewardbefore`, `rewardtimes`) VALUES (?, ?, ?, ?, ?, ?);", 29 | getTableName("posters")); 30 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 31 | pstmt.setString(1, poster.getUuid()); 32 | pstmt.setString(2, poster.getName()); 33 | pstmt.setString(3, poster.getBbsname()); 34 | pstmt.setLong(4, poster.getBinddate()); 35 | pstmt.setString(5, poster.getRewardbefore()); 36 | pstmt.setInt(6, poster.getRewardtime()); 37 | pstmt.executeUpdate(); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } finally { 41 | readlock.unlock(); 42 | } 43 | } 44 | 45 | public void updatePoster(Poster poster) { 46 | readlock.lock(); 47 | String sql = String.format( 48 | "UPDATE `%s` SET `name`=?, `bbsname`=?, `binddate`=?, `rewardbefore`=?, `rewardtimes`=? WHERE `uuid`=?;", 49 | getTableName("posters")); 50 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 51 | pstmt.setString(1, poster.getName()); 52 | pstmt.setString(2, poster.getBbsname()); 53 | pstmt.setLong(3, poster.getBinddate()); 54 | pstmt.setString(4, poster.getRewardbefore()); 55 | pstmt.setInt(5, poster.getRewardtime()); 56 | pstmt.setString(6, poster.getUuid()); 57 | pstmt.executeUpdate(); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } finally { 61 | readlock.unlock(); 62 | } 63 | } 64 | 65 | public void addTopState(String mcbbsname, String time) { // 记录一个顶贴 66 | readlock.lock(); 67 | String sql = String.format("INSERT INTO `%s` (`bbsname`, `time`) VALUES (?, ?);", getTableName("topstates")); 68 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 69 | pstmt.setString(1, mcbbsname); 70 | pstmt.setString(2, time); 71 | pstmt.executeUpdate(); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } finally { 75 | readlock.unlock(); 76 | } 77 | } 78 | 79 | public Poster getPoster(String uuid) {// 返回一个顶贴者 80 | readlock.lock(); 81 | String sql = String.format("SELECT * from `%s` WHERE `uuid`=?;", getTableName("posters")); 82 | Poster poster = null; 83 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 84 | pstmt.setString(1, uuid); 85 | try (ResultSet rs = pstmt.executeQuery();) { 86 | try { 87 | if (rs.isClosed()) 88 | return poster; 89 | } catch (AbstractMethodError e) { 90 | } 91 | 92 | if (rs.next()) { 93 | poster = new Poster(); 94 | poster.setUuid(rs.getString("uuid")); 95 | poster.setName(rs.getString("name")); 96 | poster.setBbsname(rs.getString("bbsname")); 97 | poster.setBinddate(rs.getLong("binddate")); 98 | poster.setRewardbefore(rs.getString("rewardbefore")); 99 | poster.setRewardtime(rs.getInt("rewardtimes")); 100 | } 101 | } 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } finally { 105 | readlock.unlock(); 106 | } 107 | return poster; 108 | } 109 | 110 | public List getTopStatesFromPoster(Poster poster) {// 返回一个顶贴者的顶贴列表 111 | readlock.lock(); 112 | List list = new ArrayList(); 113 | String sql = String.format("SELECT `time` from `%s` WHERE `bbsname`=?;", getTableName("topstates")); 114 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 115 | pstmt.setString(1, poster.getBbsname()); 116 | try (ResultSet rs = pstmt.executeQuery();) { 117 | try { 118 | if (rs.isClosed()) 119 | return list; 120 | } catch (AbstractMethodError e) { 121 | } 122 | 123 | while (rs.next()) { 124 | list.add(rs.getString("time")); 125 | } 126 | } 127 | } catch (Exception e) { 128 | e.printStackTrace(); 129 | } finally { 130 | readlock.unlock(); 131 | } 132 | return list; 133 | } 134 | 135 | public String bbsNameCheck(String bbsname) {// 检查这个bbsname并返回一个uuid 136 | readlock.lock(); 137 | String sql = String.format("SELECT `uuid` from `%s` WHERE `bbsname`=?;", getTableName("posters")); 138 | String uuid = null; 139 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 140 | pstmt.setString(1, bbsname); 141 | try (ResultSet rs = pstmt.executeQuery();) { 142 | try { 143 | if (rs.isClosed())// 如果查询是空的sqlite就会把结果关闭 144 | return uuid; 145 | } catch (AbstractMethodError e) {// 低版本没有这个特性 146 | } 147 | 148 | if (rs.next()) {// 但是mysql却会返回一个空结果集 149 | uuid = rs.getString("uuid"); 150 | } 151 | } 152 | } catch (Exception e) { 153 | e.printStackTrace(); 154 | } finally { 155 | readlock.unlock(); 156 | } 157 | return uuid; 158 | } 159 | 160 | public boolean checkTopstate(String bbsname, String time) {// 查询是否存在这条记录,如果存在返回true,不存在返回false 161 | readlock.lock(); 162 | String sql = String.format("SELECT * FROM `%s` WHERE `bbsname`=? AND `time`=? LIMIT 1;", 163 | getTableName("topstates")); 164 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql);) { 165 | pstmt.setString(1, bbsname); 166 | pstmt.setString(2, time); 167 | try (ResultSet rs = pstmt.executeQuery()) { 168 | try { 169 | if (rs.isClosed()) {// sqlite会关闭这个结果 170 | return false; 171 | } 172 | } catch (AbstractMethodError e) {// 但是低版本使用这个方法会报错 173 | } 174 | 175 | if (!rs.next()) {// mysql会返回一个空结果集,里面什么都没有 176 | return false; 177 | } 178 | } 179 | } catch (Exception e) { 180 | e.printStackTrace(); 181 | } finally { 182 | readlock.unlock(); 183 | } 184 | return true; 185 | } 186 | 187 | public List getTopPosters() {// 按排名返回poster,并给poster写上count属性,不会返回没有顶过贴的玩家 188 | readlock.lock(); 189 | String sql = String.format("SELECT bbsname,COUNT(*) FROM `%s` GROUP BY bbsname ORDER BY COUNT(*) DESC;", 190 | getTableName("topstates")); 191 | List list = new ArrayList(); 192 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql); 193 | ResultSet rs = pstmt.executeQuery();) { 194 | 195 | while (rs.next()) { 196 | String uuid = bbsNameCheck(rs.getString("bbsname")); 197 | Poster poster = getPoster(uuid); 198 | if (poster == null) continue; 199 | poster.setCount(rs.getInt("COUNT(*)")); 200 | list.add(poster); 201 | } 202 | return list; 203 | } catch (Exception e) { 204 | e.printStackTrace(); 205 | } finally { 206 | readlock.unlock(); 207 | } 208 | return null; 209 | } 210 | 211 | public List getNoCountPosters() {// 由于上面的方法只会返回有顶贴的玩家 212 | readlock.lock(); 213 | String sql = String.format("SELECT * FROM `%s` WHERE `rewardbefore`='';", getTableName("posters")); 214 | List posterlist = new ArrayList(); 215 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql); 216 | ResultSet rs = pstmt.executeQuery();) { 217 | while (rs.next()) { 218 | Poster poster = new Poster(); 219 | poster.setUuid(rs.getString("uuid")); 220 | poster.setName(rs.getString("name")); 221 | poster.setBbsname(rs.getString("bbsname")); 222 | poster.setBinddate(rs.getLong("binddate")); 223 | poster.setRewardbefore(rs.getString("rewardbefore")); 224 | poster.setRewardtime(rs.getInt("rewardtimes")); 225 | poster.setCount(0); 226 | posterlist.add(poster); 227 | } 228 | return posterlist; 229 | } catch (Exception e) { 230 | e.printStackTrace(); 231 | } finally { 232 | readlock.unlock(); 233 | } 234 | return null; 235 | } 236 | 237 | public void deletePoster(String uuid) { 238 | readlock.lock(); 239 | String sql = String.format("DELETE FROM `%s` WHERE `uuid`=?;", getTableName("posters")); 240 | try (PreparedStatement pstmt = getConnection().prepareStatement(sql); 241 | ) { 242 | pstmt.setString(1, uuid); 243 | pstmt.executeUpdate(); 244 | } catch (Exception e) { 245 | e.printStackTrace(); 246 | } finally { 247 | readlock.unlock(); 248 | } 249 | } 250 | 251 | // 获取当前sql的连接 252 | protected abstract Connection getConnection(); 253 | 254 | // 关闭sql连接 255 | protected abstract void closeConnection(); 256 | 257 | // 加载,插件启动时调用 258 | protected abstract void load(); 259 | 260 | } 261 | -------------------------------------------------------------------------------- /src/moe/feo/bbstoper/sql/SQLiter.java: -------------------------------------------------------------------------------- 1 | package moe.feo.bbstoper.sql; 2 | 3 | import moe.feo.bbstoper.BBSToper; 4 | import moe.feo.bbstoper.Message; 5 | import moe.feo.bbstoper.Option; 6 | 7 | import java.io.File; 8 | import java.sql.Connection; 9 | import java.sql.DriverManager; 10 | import java.sql.SQLException; 11 | import java.sql.Statement; 12 | import java.util.logging.Level; 13 | 14 | public class SQLiter extends SQLer { 15 | 16 | private final static SQLiter sqler = new SQLiter(); 17 | private Connection conn; 18 | 19 | private SQLiter() { 20 | 21 | } 22 | 23 | public static SQLiter getInstance() { 24 | return sqler; 25 | } 26 | 27 | @Override 28 | protected Connection getConnection() { 29 | return this.conn; 30 | } 31 | 32 | @Override 33 | protected void closeConnection() { 34 | try { 35 | if (!conn.isClosed()) {// 如果连接没有关闭,则将关闭这个连接 36 | conn.close(); 37 | } 38 | } catch (SQLException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | 43 | public String getUrl() {// 获取数据库url 44 | String folder = BBSToper.getInstance().getDataFolder().getPath();// 获取插件文件夹 45 | String path = Option.DATABASE_SQLITE_FOLDER.getString().replaceAll("%PLUGIN_FOLDER%", "%s"); 46 | String url = "jdbc:sqlite:" + path + File.separator + Option.DATABASE_SQLITE_DATABASE.getString(); 47 | String finalurl = String.format(url, folder);// 替换占位符 48 | return finalurl; 49 | } 50 | 51 | @Override 52 | protected void load() { 53 | connect(); 54 | createTablePosters(); 55 | createTableTopStates(); 56 | } 57 | 58 | protected void connect() { 59 | String driver = "org.sqlite.JDBC"; 60 | try { 61 | Class.forName(driver); 62 | this.conn = DriverManager.getConnection(getUrl()); 63 | } catch (ClassNotFoundException | SQLException e) { 64 | BBSToper.getInstance().getLogger().log(Level.WARNING, Message.FAILEDCONNECTSQL.getString(), e); 65 | } 66 | } 67 | 68 | protected void createTablePosters() { 69 | String sql = String.format( 70 | "CREATE TABLE IF NOT EXISTS `%s` ( `uuid` char(36) NOT NULL, `name` varchar(255) NOT NULL, `bbsname` varchar(255) NOT NULL COLLATE NOCASE, `binddate` bigint(0) NOT NULL, `rewardbefore` char(10) NOT NULL, `rewardtimes` int(0) NOT NULL, PRIMARY KEY (`uuid`) );", 71 | getTableName("posters")); 72 | try (Statement stmt = conn.createStatement();) { 73 | stmt.execute(sql); 74 | } catch (SQLException e) { 75 | e.printStackTrace(); 76 | } 77 | } 78 | 79 | protected void createTableTopStates() { 80 | String sql = String.format( 81 | "CREATE TABLE IF NOT EXISTS `%s` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `bbsname` varchar(255) NOT NULL COLLATE NOCASE, `time` varchar(16) NOT NULL);", 82 | getTableName("topstates")); 83 | try (Statement stmt = conn.createStatement();) { 84 | stmt.execute(sql); 85 | } catch (SQLException e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | 90 | } 91 | --------------------------------------------------------------------------------