Repository: zsxsoft/danmu-server
Branch: master
Commit: 1b030926221e
Files: 56
Total size: 111.2 KB
Directory structure:
gitextract_ll54n030/
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── Dockerfile
├── README.md
├── app.js
├── config.js
├── docker/
│ ├── create_db.sh
│ ├── create_mysql_admin_user.sh
│ └── run.sh
├── jsconfig.json
├── package.json
└── src/
├── controllers/
│ ├── DanmuController.js
│ └── UserController.js
├── extensions/
│ ├── audit/
│ │ ├── Audit.js
│ │ ├── audit.html
│ │ └── index.js
│ ├── autoban/
│ │ └── index.js
│ ├── index.js
│ ├── livesync/
│ │ ├── get.py
│ │ └── index.js
│ └── weibo/
│ ├── index.js
│ └── login.html
├── interfaces/
│ ├── Base.js
│ ├── Config.js
│ ├── Danmu.js
│ ├── Http.js
│ └── Socket.js
├── libraries/
│ ├── cache/
│ │ └── index.js
│ ├── database/
│ │ ├── csv.js
│ │ ├── index.js
│ │ ├── mongo.js
│ │ ├── mysql.js
│ │ └── none.js
│ ├── http/
│ │ ├── index.js
│ │ ├── res/
│ │ │ ├── manage.css
│ │ │ ├── manage.js
│ │ │ └── realtime.js
│ │ ├── route/
│ │ │ ├── index.js
│ │ │ ├── manage.js
│ │ │ ├── manageBlock.js
│ │ │ ├── manageConfig.js
│ │ │ ├── manageDanmu.js
│ │ │ ├── manageRoom.js
│ │ │ ├── manageSearch.js
│ │ │ ├── post.js
│ │ │ └── realtime.js
│ │ └── view/
│ │ ├── index.html
│ │ ├── manage.html
│ │ └── realtime.html
│ ├── socket/
│ │ └── index.js
│ └── transfer/
│ └── index.js
└── utilities/
├── filter.js
├── index.js
└── log.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
/http/res/*
config.js
================================================
FILE: .eslintrc
================================================
{
"env": {
"es6": true,
"browser": false,
"node": true
},
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module"
},
"plugins": [
"babel"
],
"extends": [
"standard"
]
}
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
.vs
.idea
.vscode
*.csv
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# Visual Studio Code
typings
================================================
FILE: Dockerfile
================================================
FROM node:latest
MAINTAINER zsx <zsx@zsxsoft.com>
ENV APP /usr/src/app
## ----------------------------
## MariaDB Start
## ----------------------------
## Add MariaDB PPK
RUN apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db && \
echo 'deb http://mirrors.syringanetworks.net/mariadb/repo/10.1/ubuntu trusty main' >> /etc/apt/sources.list && \
echo 'deb-src http://mirrors.syringanetworks.net/mariadb/repo/10.1/ubuntu trusty main' >> /etc/apt/sources.list && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y mariadb-server pwgen && \
rm -rf /var/lib/mysql/* && \
sed -i -r 's/bind-address.*$/bind-address = 0.0.0.0/' /etc/mysql/my.cnf && \
apt-get install -y memcached && \
mkdir -p ${APP}
WORKDIR ${APP}/
ADD ./ ./
ADD ./docker/ /docker
RUN chmod +x /docker/*.sh && \
npm install && \
npm cache clean && apt-get clean && rm -rf /var/lib/apt/lists/*
VOLUME ["/etc/mysql", "/var/lib/mysql"]
EXPOSE 3306 11211 3000
CMD ["/docker/run.sh"]
================================================
FILE: README.md
================================================
danmu-server
================
[](https://david-dm.org/zsxsoft/danmu-server)
弹幕服务器,其客户端项目见[danmu-client](https://github.com/zsxsoft/danmu-client)。
**欲使用此项目,客户端需要使用对应的版本。[已发布的服务端](https://github.com/zsxsoft/danmu-server/releases)均已写明对应的客户端版本号,开发分支内的服务端版本仅对应开发分支的客户端。**
## 功能特色
- 跨平台;
- 房间功能;
- 后台管理;
- 弹幕记录与搜索(需要开启数据库);
- 黑名单功能;
- 关键词替换、拦截功能;
- 弹幕记录;
- 扩展;
- 新浪微博登录扩展(需要开启缓存);
- 自动封禁功能扩展(需要开启缓存);
- 审核扩展;
- 直播拉取扩展
- 删除单条弹幕功能;
- 易于部署,简单高效。
## 后台截图

## 一些警告
稳定版请于[Release](https://github.com/zsxsoft/danmu-server/releases)手动下载。
## 部署方式
### 检查环境
#### Nodejs
Nodejs >= 6
#### 数据库
如使用``csv``,可无视此节。
默认使用``MySQL``数据库。如需使用,需检查[MariaDB](https://mariadb.org/)或[MySQL](https://www.mysql.com/)的安装状态。支持``5.0+``。安装完成后,请创建相应的数据库。
如使用``MongoDB``数据库,请检查[MongoDB](https://www.mongodb.org/)的安装状态。然后需要在安装完成后执行:``npm install mongodb``。
#### 缓存
如不使用新浪微博与自动封禁功能,可无视此节。
默认使用``memcached``。如需使用,请检查[Memcached(Linux)](http://memcached.org/)的安装状态。``Windows``用户请自行查找适合的``Memcached``版本。
如果要用[阿里云开放缓存服务OCS](http://www.aliyun.com/product/ocs/),需要在安装完成后执行:``npm install aliyun-sdk``。
### 直接安装
1. 配置MariaDB,创建数据库等,不需要创建数据表。
2. 修改``config.js``,使其参数与环境相符。
3. 切换到命令行或终端,``cd``到程序所在目录执行``npm install``,安装程序依赖库。
4. 现在,你可以直接``npm start``启动。
### Docker安装
__Dockerfile可能年久失修,建议自己用``alpine``封装一个。__
直接用``Docker``安装的话,镜像内是含``MariaDB``的。
1. [安装Docker](http://yeasy.gitbooks.io/docker_practice/content/install/index.html)。
2. ``config.js``调整配置。
3. ``docker build -t="zsxsoft/danmu-server:" . && docker run -t -i -p 3000:3000 "zsxsoft/danmu-server"``
## 升级
### 1.0.6 -> 1.1.0
* 在每个房间内增加cdn: false配置
### 1.0.5 -> 1.0.6
* 在每个房间内增加hostname配置,类型为数组,用于将房间与域名绑定
## 网页接口
### GET /
可以直接发布最简单的弹幕。
### GET /advanced
可以发布高级弹幕(需要密码)
### GET /manage
可以进行后台管理
### GET /realtime
可以实时接收弹幕并直接删除或封禁(需要密码)
## 配置说明
以下标有``*``的配置项,运行时不可在后台修改。
```javascript
"rooms": {
"房间1": {
* "hostname": ["test.zsxsoft.com", "localhost", "127.0.0.1"],
* "cdn": 是否使用CDN或反向代理(用于获取正确的IP),
* "display": "房间显示名",
* "table": "对应MySQL的数据表、MongoDB的集合",
"connectpassword": "客户端连接密码",
"managepassword": "管理密码",
"advancedpassword": "高级弹幕密码",
"keyword": {
"block": /强制屏蔽关键词,正则格式。/
"replacement": /替换关键词,正则格式/,
"ignore": /忽略词,正则格式/
},
"blockusers": [
"默认封禁用户列表"
],
"maxlength": 弹幕堆积队列最大长度,
"textlength": 每条弹幕最大长度,
* "image": {
* "regex": /图片弹幕解析正则,正则格式,不要修改/ig,
* "lifetime": 每个图片给每条弹幕增加的存货时间
},
"permissions": { // 普通用户允许的弹幕权限
"send": 弹幕开关;关闭后无论普通用户还是高级权限都完全禁止弹幕。,
"style": 弹幕样式开关,
"color": 颜色开关,
"textStyle": CSS开关,
"height": 高度开关,
"lifeTime": 显示时间开关,
}
},
"房间ID2": {
// 同上
}
},
* "database": { // 数据库
* "type": "数据库类型(mysql / mongo / csv / none)",
* "server": " 数据库地址(mysql / mongo)",
* "username": "数据库用户名(mysql / mongo)",
* "password": "数据库密码(mysql / mongo)",
* "port": "数据库端口(mysql / mongo)",
* "db": "数据库(mysql / mongo)",
* "retry": 24小时允许断线重连最大次数,超过则自动退出程序。24小时以第一次断线时间计。(mysql),
* "timeout": 数据库重连延时及Ping(mysql),
* "savedir": "指定文件保存位置(csv)",
},
"websocket": {
"interval": 弹幕发送间隔
"singlesize": 每次弹幕发送数量
},
* "http": {
* "port": 服务器HTTP端口,
* "headers": {}, // HTTP头
* "sessionKey": "随便写点,防冲突的"
},
* "cache": {
* "type": "缓存类型(memcached / aliyun)",
* "host": "缓存服务器地址,可用socket",
* "auth": 打开身份验证,
* "authUser": 身份验证账号,
* "authPassword": 身份验证密码,
},
"ext": {
// 扩展
}
}
```
## 扩展
### 新浪微博登录
```javascript
"weibo": { // 新浪微博扩展
"clientID": '', // App ID
"clientSecret": '', // App Secret
"callbackURL": 'http://test.zsxsoft.com:3000/auth/sina/callback', // 这里填写的是 网站地址/auth/sina/callback
"requireState": true // 是否打开CSRF防御
}
```
### 自动封禁
```javascript
"autoban": { // 自动封号扩展
"block": 3, // 被拦截超过一定数字自动封号
}
```
### 全局审核
```javascript
"audit": { // 审核扩展
}
```
### 直播同步
此扩展基于[danmu](https://github.com/littlecodersh/danmu)项目开发,需要安装Python 2.7+ 或 Python 3.5+。在启用前,你首先需要
```bash
pip install danmu
```
才可打开。
```javascript
"livesync": { // 新浪微博扩展
"房间名": {
"liveUrl": '', // 直播网站地址
}
}
```
## 常见问题
### 数据库相关
``{ [Error: Connection lost: The server closed the connection.] fatal: true, code: 'PROTOCOL_CONNECTION_LOST' }``
请把MySQL的``wait_timeout``设置得大一些。
## 搭配项目
- [danmu-client](https://github.com/zsxsoft/danmu-client)
## 流程图

## 协议
The MIT License (MIT)
## 博文
[弹幕服务器及搭配之透明弹幕客户端研究结题报告](http://blog.zsxsoft.com/post/15)
[弹幕服务器及搭配之透明弹幕客户端研究中期报告](http://blog.zsxsoft.com/post/14)
[弹幕服务器及搭配之透明弹幕客户端研究开题报告](http://blog.zsxsoft.com/post/13)
## 开发者
zsx - https://www.zsxsoft.com / 博客 - https://blog.zsxsoft.com
================================================
FILE: app.js
================================================
const os = require('os')
const async = require('async')
const fs = require('fs')
const path = require('path')
const configEvent = require('./src/interfaces/Config')
const log = require('./src/utilities/log')
const packageJson = require('./package.json')
let config = require('./config')
global.version = packageJson.version
global.Promise = require('bluebird')
{
log.log(`弹幕服务器版本:${global.version}`)
log.log(`环境:${os.platform()}(${os.release()}) ${os.arch()} with ${parseInt(os.totalmem() / 1024 / 1024)}MB`)
let dbPos = config.database
if (process.env.MYSQL_PORT_3306_TCP_PORT) { // 检测DaoCloud的MySQL服务
dbPos.type = 'mysql'
dbPos.server = process.env.MYSQL_PORT_3306_TCP_ADDR
dbPos.username = process.env.MYSQL_USERNAME
dbPos.password = process.env.MYSQL_PASSWORD
dbPos.port = process.env.MYSQL_PORT_3306_TCP_PORT
dbPos.db = process.env.MYSQL_INSTANCE_NAME
console.log('检测到配置在环境变量内的MySQL,自动使用之。')
} else if (dbPos.type === 'mongo' && process.env['27017/tcp']) { // MongoDB服务
dbPos.type = 'mongo'
dbPos.server = process.env['27017/tcp'].split(':')[0].trim() // tcp://xx.xx.xx.xx:27017
dbPos.port = process.env['27017/tcp'].split(':')[1].trim() // tcp://xx.xx.xx.xx:27017
dbPos.username = process.env.USERNAME
dbPos.password = process.env.PASSWORD
dbPos.db = process.env.INSTANCE_NAME
console.log('检测到配置在环境变量内的MongoDB,自动使用之。')
}
// 加载模块
async.map(['extensions', 'libraries/cache', 'libraries/transfer', 'libraries/database', 'libraries/http', 'libraries/socket'], (mdl, callback) => {
require(`./src/${mdl}`).init(callback)
}, err => {
if (err) throw err
fs.readdir(path.join(__dirname, './src/controllers'), (err, files) => {
if (err) throw err
files.forEach((filename) => require(path.join(__dirname, './src/controllers', filename)))
})
configEvent.updated.emit()
log.log('服务器初始化完成')
})
}
================================================
FILE: config.js
================================================
module.exports = {
"rooms": {
"default": {
"hostname": ["test.zsxsoft.com", "danmu.zsxsoft.com"],
"cdn": false,
"display": "默认",
"table": "room_default", // 数据表
"connectpassword": "123456", // 客户端连接密码
"managepassword": "123456", // 管理密码
"advancedpassword": "123456", // 高级弹幕密码
"keyword": {
"block": /宣你|阳痿|臭脚|难听|难看|法克|木耳|纵欲|dick|爽|下流|非礼|煞笔|傻比|沙比|蠢|丁丁|抠脚|奸|粑|EXO|TFBoys|王源|王俊凯|易烊千玺|吴亦凡|鹿晗|Love|土狗|我爱|爱你|我喜欢|喜欢你|嫁|在一起|娶|啪啪啪|性交|(大|肉|小|贫|丰|巨|胸|乳)(胸|罩|乳|房|棒)|fuck|bitch|傻|残|垃圾|(大|小)便|屎|滚|逼|屄|叼|屌|草泥马|陪侍|女友|阴茎|睾丸|附睾|阴囊|前列腺|精液|尿道|精囊|阴蒂|阴道|阴唇|子宫|输卵|卵巢|(前|后)庭|(推广|群发|广告|解密|赌博|包青天|阿凡提|发贴|顶贴|(针孔|隐形|隐蔽)摄像|干扰|顶帖|发帖|消声|遥控|解码|窃听|身份证生成|拦截|复制|监听|定位|消声|作弊|扩散|侦探|追杀)(机|器|软件|设备|系统)|(求|换|有偿|买|卖|出售)(肾|器官|眼角膜|血)|肾源|(假|毕业)(证|文凭|发票|币)|(手榴|人|麻醉|霰)弹|治疗(肿瘤|乙肝|性病|红斑狼疮)|重亚硒酸钠|(粘氯|原砷)酸|麻醉乙醚|原藜芦碱A|永伏虫|蝇毒|罂粟|银氰化钾|氯胺酮|因毒(硫磷|磷)|异氰酸(甲酯|苯酯)|异硫氰酸烯丙酯|乙酰(亚砷酸铜|替硫脲)|乙烯甲醇|乙酸(亚铊|铊|三乙基锡|三甲基锡|甲氧基乙基汞|汞)|乙硼烷|乙醇腈|乙撑亚胺|乙撑氯醇|伊皮恩|海洛因|一氧(化汞|化二氟)|一氯(乙醛|丙酮)|氧氯化磷|氧化(亚铊|铊|汞|二丁基锡)|烟碱|亚硝酰乙氧|亚硝酸乙酯|亚硒酸氢钠|亚硒酸钠|亚硒酸镁|亚硒酸二钠|亚硒酸|亚砷酸(钠|钾|酐)|冰毒|摇头丸|预测答案|考前预测|押题|代写论文|(提供|司考|级|传送|考中|短信)答案|(待|代|带|替|助)考|(包|顺利|保)过|考后付款|作弊|考前密卷|漏题|中特|一肖|报码|(合|香港)彩|彩宝|3D轮盘|liuhecai|一码|(皇家|俄罗斯)轮盘|赌具|特码|盗取?(号|qq|密码)|嗑药|帮招人|社会混|拜大哥|电警棒|帮人怀孕|切腹|电鸡|手枪|炸弹|走私|陪聊|h(图|漫|网)|开苞|找(男|女)|(口|足|胸|乳)(淫|交|推)|后入式|卖身|一夜|(男|女)奴|双(筒|桶)|看JJ|(做|坐)台|厕奴|骚女|嫩逼|一夜激情|乱伦|泡友|富(姐|婆)|(足|群|茹)交|阴户|性(服务|伴侣|伙伴|交)|(有|无)码|包养|(犬|兽|幼)交|根浴|援交|性(虐|爱|息)|刻章|昏药|性奴|透视眼(睛|镜)|拍肩神|(失忆|催情|迷(幻|昏|奸)?|安定)(药|片|香)|香港生子|土炮|胎盘|手机魔卡|容弹量|枪模|铅弹|汽(枪|狗|走表器)|气枪|气狗|伟哥|纽扣摄像机|免电灯|麻醉药|康生丹|警徽|记号扑克|激光(汽|气)|红床|狗友|电子狗导航手机|弹(种|夹)|(追|讨)债|避孕|办理(证件|文凭)|斑蝥|暗访包|BB(枪|弹)|雷管|弓弩|(电|长)狗|导爆索|爆炸物|爆破|左棍|婊子|换妻|成人片|淫(靡|水|兽)|阴(毛|蒂|道|唇)|小穴|缩阴|少妇自拍|(三级|色情|激情|黄色|小)(片|电影|视频|交友|电话)|肉棒|(情|奸)杀|裸照|乱伦|口交|禁(网|片)|春宫图|SM用品|自动群发|私家侦探服务|生意宝|商务(快车|短信)|慧聪|供应发票|发票代开|短信群发|短信猫|点金商务|士的宁|士的年|六合(采|彩)|乐透码|彩票|百乐二呓|百家乐|黄页|出租|求购|留学咨询|外挂|网络(兼职|赚钱)|(证件|婚庆|翻译|搬家|追债|债务)公司|手机(游戏|窃听|监听|铃声|图片)|三唑仑|彩(信|铃|票)|显示屏|投影仪|虚拟主机|(域名|专业)注册|营销|性病|不孕不育|乳腺病|尖锐湿疣|皮肤病|减肥|瘦|3P|人兽|代孕|打炮|找小姐|刻章|乱伦|中出|楼凤|卖淫|荡妇|群交|幼女|18禁|伦理电影|(催情|蒙汗|蒙汉|春)药|情趣用品|成人.+?(电影|用品)|激情(视频|电影|影院)|爽片|美女|交友|怀孕|裸聊|制服诱惑|丝袜|长腿|寂寞女子|双色球|福彩|体彩|6合彩|时时彩|双色球|咨询热线|股票|荐股|开股|私服|枪|警棒|警服|麻醉|诚招加盟|诚信经营|杀手|(游戏|金)币|群发|加盟|名表|特卖|分销|残党|共惨党|共匪|赤匪|裆中央|北京当局|中宣|真理部|十八大|18大|太子|上海帮|团派|九常委|九长老|政治局常委内幕|锦涛|hujin|家宝|影帝|wenjiabao|wjb|近平|xijinping|xjp|假庆淋|jiaqinglin|李月月鳥|李鹏|回良玉|汪洋|岐山|王山支山|wangqishan张高丽|俞正声|徐才厚|郭伯雄|梁光烈|孟建柱|戴秉国|马凯|计划|韩正|章沁生|陈世炬|泽民|贼民|先皇|太上皇|蛤蟆|驾崩|jiangzemin|jzm|邓小平|庆红|罗干|likeqiang|zhouyongkang|lichangchun|wubangguo|heguoqiang|老人政治|老人干政|陈光诚事件|自由光诚|陈光诚.*使馆|使馆.*陈光诚|光诚.*沂南|沂南.*光诚|要有光.*要有诚|要有诚.*要有光|马驰.*新加坡|职称英语.*答案|答案.*职称英语|公务员.*答案|答案.*公务员|重庆|熙来|薄督|不厚|薄瓜瓜|谷开来|海伍德|尼尔伍德|heywood|neil.*wood|wood.*neil|天线宝宝.*康师傅|康师傅.*天线宝宝|天线宝宝.*方便面|方便面.*天线宝宝|政变|暴动|枪声|戒严|3\.19|北京事件|北京.*出事了|出事了.*北京|北京怎么了|叶城.*砍杀|砍杀.*叶城|弟弟.*睡|睡.*弟弟|山水文园|跑官|移动.*十年兴衰|十年兴衰.*移动|叶兵|张斌|陈瑞卿|高念书|华如秀|鲁向东|曲乃杰|孙静晔|涂志森|于剑鸣|张晓明|赵志强|郑建源|丘小雄|公诉|右派|增城|莫日根|内蒙古.*抗议|抗议.*内蒙古|西乌旗|天府|人民公园|埃及|突尼斯|茉莉|jasmine.*revolution|revolution.*jasmine|moli|集会|公开信|六四|六 四|六\.四|64|天安门|八九|平反64|六月四日|5月35日|5月35号|89动乱|64memo|tiananmen|8964|天安.*事件|事件.*天安|1989.*天安門|天安門.*1989|开枪|广场|1989年|198964|89.*学生动乱|学生动乱.*89|89.*学生运动|学生运动.*89|64.*学生运动|学生运动.*64|64.*镇压|镇压.*64|64.*真相|真相.*64|学潮|罢课|民运|学运|学联|学自联|高自联|工自联|坦克人|挡坦克|tankman|木犀地|维园晚会|blood is on the square|耀邦|紫阳|改革.*历程|历程.*改革|国家的囚徒|prisoner of the state|民联|民阵|中国民主党|中国民主正义党|中国民主运动|世纪中国基金会|姜维平|艾未未|艾末末|路青|发课|余杰|辛子陵|茅于轼|铁流|liu.*xiaobo|xiaobo.*liu|刘霞|我没有敌人|我的最后陈述|零八.*宪章|宪章.*零八|08.*宪章|宪章.*08|八宪章|8宪章|零八.*县长|县长.*零八|08县长|淋巴县长|谭作人|高智晟|冯正虎|丁子霖|唯色|焦国标|何清涟|方励之|严家其|柴玲|乌尔凯西|封从德|炳章|苏绍智|陈一谘|韩东方|辛灏年|曹长青|陈破空|盘古乐队|盛雪|伍凡|魏京生|司徒华|黎安友|张宏堡|地下教会|冤民大同盟|达赖|藏独|freetibet|雪山狮子|西藏流亡政府|青天白日旗|民进党|洪哲胜|独立台湾会|台湾政论区|台湾自由联盟|台湾建国运动组织|台湾.*独立联盟|独立联盟.*台湾|新疆.*独立|独立.*新疆|东土耳其斯坦|east.*turkistan|世维会|迪里夏提|明报|纽约时报|美国之音|自由亚洲电台|记者无疆界|维基解密.*中国|中国.*维基解密|世界经济导报|中国数字时代|蟹农场|中国.*禁闻|禁闻.*中国|阅后即焚|阿波罗网|阿波罗新闻|大参考|bignews|多维|看中国|博讯|boxun|peacehall|hrichina|独立中文笔会|华夏文摘|开放杂志|大家论坛|华夏论坛|中国|坛|木子论坛|争鸣论坛|大中华论坛|反腐败论坛|新观察论坛|新华通论坛|正义党论坛|热站政论网|华通时事论坛|华语世界论坛|华岳时事论坛|两岸三地论坛|南大自由论坛|人民之声论坛|万维读者论坛|你说我说论坛|东西南北论坛|东南西北论谈|知情者|红太阳的陨落|和谐拯救危机|血房|一个孤僻的人|河殇|天葬|黄祸|我的奋斗|历史的伤口|改革年代政治斗争|改革年代的政治斗争|关键时刻|超越红墙|梦萦未名湖|一寸山河一寸血|北国之春|北京之春|中国之春|东方红时空|婴儿汤|代开.*发票|发票.*代开|钓鱼岛|女保镖|chinese people eating babies|洗脑|网特|内斗|党魁|文字狱|一党专政|一党独裁|新闻封锁|^freechina|反社会|维权人士|维权律师|异见人士|异议人士|高瞻|地下刊物|tits|boobs|色情|花花公子|中功|法轮|falun|明慧|minghui|退党|三退|九评|nine commentaries|洪吟|神韵艺术|神韵晚会|人民报|renminbao|纪元|^dajiyuan|epochtimes|新唐人|ntdtv|ndtv|新生网|^xinsheng|正见网|zhengjian|追查国际|真善忍|法会|正念|经文|天灭|天怒|迫害|酷刑|邪恶|讲真相|马三家|善恶有报|活摘器官|群体灭绝|防火长城|great.*firewall|firewall.*great|gfw.*什么|什么.*gfw|国家防火墙|翻墙|代理|方滨兴|vpn.*免费|免费.*vpn|vpn.*下载|下载.*vpn|vpn.*世纪|世纪.*vpn|hotspot.*shield|shield.*hotspot|goagent|ultrasurf|动态网|花园网|纳米比亚|freegate|自由门|自由門|无界|無界|动网通|dongtaiwang/ig,
// 强制屏蔽关键词
"replacement": /父|母|夫|妻|女儿|儿子|孙子|孙女|女婿|娘|爹|爸|妈|爷|奶|哥|弟|兄|姐|妹|鸡|鸭|狗|猪|gay|mother|mom|father|dad|sister|brother|son|daughter|dog|pig/ig,
// 替换关键词
"ignore": /\~|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|_|\||\+|\-|\=|\{|\}|\[|\]|\;|\'|\:|\"|\<|\>|\?|\/|\.|\,|\!|\#|\¥|\…|\(|\)|\—|\、|\【|\】|\{|\}|\;|\:|\‘|\’|\“|\”|\《|\》|\\|\,|\。|\、|\?|\ |\ /ig
// 忽略词
},
"blockusers": [ // 封禁用户
"test"
],
"maxlength": 100, // 队列最大长度
"textlength": 1000, // 弹幕最大长度
"image": {
"regex": /\[IMG WIDTH=(\d+)\](.+?)\[\/IMG\]/ig, // 图片弹幕
"lifetime": 300 // 每个图片给每条弹幕增加的时间
},
"permissions": { // 普通用户允许的弹幕权限
"send": true, // 弹幕开关;关闭后无论普通用户还是高级权限都完全禁止弹幕。
"style": false, // 弹幕样式开关
"color": false, // 颜色开关
"textStyle": false, // CSS开关
"height": false, // 高度开关
"lifeTime": false, // 显示时间开关
"sourceCode": false, // 自定义高级JavaScript弹幕开关
}
},
"unlimited": {
"hostname": ["127.0.0.1", "localhost"],
"cdn": false,
"display": "无限房间",
"table": "room_unlimited", // 数据表
"connectpassword": "", // 客户端连接密码
"managepassword": "", // 管理密码
"advancedpassword": "", // 高级弹幕密码
"keyword": {
"block": /^$/,
// 强制屏蔽关键词
"replacement": /^$/,
// 替换关键词
"ignore": /^$/
// 忽略词
},
"blockusers": [ // 封禁用户
],
"maxlength": 1000, // 队列最大长度
"textlength": 10000, // 弹幕最大长度
"image": {
"regex": /\[IMG WIDTH=(\d+)\](.+?)\[\/IMG\]/ig, // 图片弹幕
"lifetime": 300 // 每个图片给每条弹幕增加的时间
},
"permissions": { // 普通用户允许的弹幕权限
"send": true, // 弹幕开关;关闭后无论普通用户还是高级权限都完全禁止弹幕。
"style": true, // 弹幕样式开关
"color": true, // 颜色开关
"textStyle": true, // CSS开关
"height": true, // 高度开关
"lifeTime": true, // 显示时间开关
"sourceCode": true, // 自定义高级JavaScript弹幕开关
}
}
},
"database": {
"type": "csv", // 数据库类型(mysql / mongo / csv / none)
"server": "127.0.0.1", // 数据库地址(mysql / mongo)
"username": "root", // 数据库用户名(mysql / mongo)
"password": "123456", // 数据库密码(mysql / mongo)
"port": "3306", // 数据库端口(mysql / mongo)
"db": "danmu", // 数据库(mysql / mongo)
"retry": 10, // 24小时允许断线重连最大次数,超过则自动退出程序。24小时以第一次断线时间计。(mysql)
"timeout": 1000, // 数据库重连延时及Ping(mysql)
"savedir": "./", // 指定文件保存位置(csv)
},
"websocket": {
"interval": 10, // 弹幕发送间隔
"singlesize": 5 // 每次弹幕发送数量
},
"http": {
"port": 3000, // 服务器端口
"headers": { // HTTP头
//"Access-Control-Allow-Origin": "*",
//"Access-Control-Allow-Methods": "POST"
},
"sessionKey": "hey"
},
"cache": {
"type": "none", // 缓存类型,支持memcached和aliyun。后者需要npm install aliyun-sdk
"host": "127.0.0.1:11211", // 缓存服务器地址,可用socket
"auth": false, // 是否打开身份验证
"authUser": "", // 身份验证账号
"authPassword": "" // 身份验证密码
},
"ext": {
/*
"weibo": { // 新浪微博扩展
"clientID": '', // App ID
"clientSecret": '', // App Secret
"callbackURL": 'http://test.zsxsoft.com:3000/auth/sina/callback', // 这里填写的是 网站地址/auth/sina/callback
"requireState": true // 是否打开CSRF防御
},*/
"autoban": { // 自动封号扩展
"block": 3, // 被拦截超过一定数字自动封号
},
/*"audit": { // 审核扩展
},*/
"livesync": {
"unlimited": { // 房间名
"liveUrl": "http://live.bilibili.com/3" // 直播地址
}
}
}
};
================================================
FILE: docker/create_db.sh
================================================
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <db_name>"
exit 1
fi
echo "=> Creating database $1"
RET=1
while [[ RET -ne 0 ]]; do
sleep 5
mysql -uroot -e "CREATE DATABASE $1"
RET=$?
done
echo "=> Done!"
================================================
FILE: docker/create_mysql_admin_user.sh
================================================
#!/bin/bash
echo "=> Creating database danmu in MySQL"
/docker/create_db.sh danmu
PASS=${MYSQL_PASS:-$(pwgen -s 12 1)}
_word=$( [ ${MYSQL_PASS} ] && echo "preset" || echo "random" )
echo "=> Creating MySQL admin user with ${_word} password"
mysql -uroot -e "CREATE USER 'admin'@'%' IDENTIFIED BY '$PASS'"
mysql -uroot -e "GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION"
echo "=> Done!"
echo "========================================================================"
echo "You can now connect to this MySQL Server using:"
echo ""
echo " mysql -uadmin -p$PASS -h<host> -P<port>"
echo ""
echo "Please remember to change the above password as soon as possible!"
echo "MySQL user 'root' has no password but only allows local connections"
echo "========================================================================"
================================================
FILE: docker/run.sh
================================================
#!/bin/bash
## Part MySQL
if [ ! -n "$MYSQL_PORT_3306_TCP_PORT" ]; then
VOLUME_HOME="/var/lib/mysql"
MYSQL_INSTALLED="no"
if [[ ! -d $VOLUME_HOME/mysql ]]; then
echo "=> An empty or uninitialized MySQL volume is detected in $VOLUME_HOME"
echo "=> Installing MySQL ..."
mysql_install_db > /dev/null 2>&1
echo "=> Done!"
else
MYSQL_INSTALLED="yes"
fi
echo "=> Starting MySQL ..."
/usr/bin/mysqld_safe > /dev/null 2>&1 &
MYSQL_STATE=1
while [[ RET -ne 0 ]]; do
echo "=> Waiting for confirmation of MySQL service startup"
sleep 5
mysql -uroot -e "status" > /dev/null 2>&1
MYSQL_STATE=$?
done
if [ "$MYSQL_INSTALLED" = no ]; then
/docker/create_mysql_admin_user.sh
fi
fi
## Part Memcached
USERNOTEXISTSRET=true
getent passwd memcached >/dev/null 2>&1 && USERNOTEXISTSRET=false
if $USERNOTEXISTSRET; then
useradd memcached -s /nologin
fi
echo "=> Starting memcached ..."
memcached -u memcached &
## Part Main
echo "=> Starting service ..."
npm start
================================================
FILE: jsconfig.json
================================================
{
// See http://go.microsoft.com/fwlink/?LinkId=759670
// for the documentation about the jsconfig.json format
"compilerOptions": {
"target": "es6"
},
"exclude": [
"node_modules",
"bower_components",
"jspm_packages",
"tmp",
"temp",
"lib/http/res"
]
}
================================================
FILE: package.json
================================================
{
"name": "danmu-server",
"version": "1.1.0",
"license": "MIT",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"angular": "^1.4.6",
"angular-ui-bootstrap": "^0.14.3",
"async": "^2.5.0",
"bluebird": "^3.5.0",
"body-parser": "^1.17.2",
"bootstrap": "^3.3.7",
"cookie-parser": "^1.4.3",
"ejs": "^2.5.7",
"errorhandler": "^1.5.0",
"express": "^4.15.4",
"express-session": "^1.15.5",
"jquery": "^3.2.1",
"memcached": "^2.2.2",
"morgan": "^1.8.2",
"mysql": "^2.14.1",
"passport": "^0.4.0",
"passport-sina": "git+https://github.com/zsxtoys/passport-sina-fork.git",
"ramda": "^0.24.1",
"socket.io": "^2.0.3"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
"eslint": "^4.5.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1"
},
"description": "danmu-server",
"main": "app.js",
"repository": {
"type": "git",
"url": "git+https://github.com/zsxsoft/danmu-server.git"
},
"keywords": [
"danmu",
"danmaku",
"弹幕"
],
"author": "zsx <zsx@zsxsoft.com>",
"bugs": {
"url": "https://github.com/zsxsoft/danmu-server/issues"
},
"homepage": "https://github.com/zsxsoft/danmu-server#readme",
"engines": {
"node": ">=5.5"
},
"babel": {
"presets": [
"es2015",
"stage-0"
]
}
}
================================================
FILE: src/controllers/DanmuController.js
================================================
const filter = require('../utilities/filter')
const log = require('../utilities/log')
const danmuEvent = require('../interfaces/Danmu')
const configEvent = require('../interfaces/Config')
const permissions = ['color', 'style', 'height', 'lifeTime', 'textStyle', 'sourceCode'] // 为了不foreach
let config = require('../../config')
danmuEvent.addSingle.listen((data, inputs = {}, extra = {
password: '',
isAdvanced: false
}) => {
return new Promise((resolve, reject) => {
const room = data.room
const roomConfig = config.rooms[room]
const realFilter = filter(room)
if (!roomConfig.permissions.send) {
return reject(new Error('弹幕暂时被关闭'))
}
if (extra.isAdvanced) {
if (extra.password !== roomConfig.advancedpassword) {
return reject(new Error('高级弹幕密码错误!'))
}
}
if (!extra.isAdvanced && data.text.length > roomConfig.textlength) {
return reject(new Error(`弹幕长度大于${roomConfig.textlength}个字,可能影响弹幕观感,请删减。`))
}
if (realFilter.checkUserIsBlocked(data.hash) || !realFilter.validateText(data.text)) {
log.log(`拦截 ${data.hash} - ${data.text}`)
danmuEvent.ban.emit(data)
return reject(new Error('发送失败!\n请检查你发送的弹幕有无关键词,或确认自己未被封禁。'))
}
permissions.forEach((val) => {
if (extra.isAdvanced || roomConfig.permissions[val]) {
data[val] = inputs[val] || ''
}
})
resolve(true)
danmuEvent.get.emit(data)
})
})
/**
* 删除一条弹幕
*/
danmuEvent.removeSingle.listen((data, blockUser = false) => {
let deleteObject = {}
deleteObject[data.room] = {
ids: [data.id],
hashs: [data.hash]
}
danmuEvent.removing.emit(deleteObject)
if (blockUser) configEvent.blockUser.emit(data.room, data.hash)
log.log(`删除弹幕 ${data.id} 成功`)
})
================================================
FILE: src/controllers/UserController.js
================================================
const configEvent = require('../interfaces/Config')
const log = require('../utilities/log')
let config = require('../../config')
configEvent.blockUser.listen((room, username) => {
config.rooms[room].blockusers.push(username)
configEvent.updated.emit()
log.log(`封禁用户${username}成功`)
})
configEvent.unblockUser.listen((room, username) => {
return new Promise((resolve, reject) => {
let indexOf = config.rooms[room].blockusers.indexOf(username)
if (indexOf >= 0) {
config.rooms[room].blockusers.splice(indexOf, 1)
configEvent.updated.emit()
log.log(`已从黑名单移除用户${username}`)
resolve()
} else {
log.log(`黑名单中未搜索到${username}`)
reject(new Error(username))
}
})
})
================================================
FILE: src/extensions/audit/Audit.js
================================================
const Base = require('../../interfaces/Base')
const className = 'audit'
const _ = require('ramda')
const register = _.curry(Base.register)(className)
const passed = register('passed')
class Audit extends Base {
static get className () {
return className
}
static get passed () {
return passed
}
}
module.exports = Audit
================================================
FILE: src/extensions/audit/audit.html
================================================
<!DOCTYPE html>
<html lang="zh-Hans-cn" ng-app="danmu.audit" ng-controller="MainCtrl">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="/manage.css" rel="stylesheet">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
<script src="http://cdn.staticfile.org/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
<title>弹幕审核</title>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header"><a class="navbar-brand" href="#">弹幕审核</a></div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<uib-alert type="danger" ng-show="haveError">错误{{err.code}}:{{err.desc}};建议刷新页面。</uib-alert>
<div class="col-sm-12 col-md-12 main">
<uib-accordion close-others="false">
<uib-accordion-group heading="管理信息" is-open="true">
<div class="container">
<uib-alert type="danger" ng-show="!isLogin">务必先填入房间信息再进行管理。</uib-alert>
<uib-alert type="success" ng-show="isLogin">你选择了{{room}}房间</uib-alert>
<div class="list-group" ng-show="!isLogin">
<a href="#" class="list-group-item" ng-class="{active: room == roomS.id}" ng-repeat="roomS in roomList" ng-click="initRoom(roomS.id)">{{roomS.display}} ({{roomS.id}})</a>
</div>
<form ng-submit="enterRoom(password)" ng-show="!isLogin">
<div class="input-group">
<input type="password" class="form-control" placeholder="房间密码" ng-model="password">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">确认</button>
</span>
</div>
</form>
</div>
</uib-accordion-group>
<uib-accordion-group heading="弹幕审核" is-open="true">
<div class="container">
<div ng-repeat="danmu in danmus track by danmu.socketId" style="margin-top: 5px; margin-right: 5px; display: inline-block">
<div class="btn-group" uib-dropdown>
<button id="split-button-{{$index}}" type="button" class="btn btn-success" ng-click="passDanmu($index, danmu.id, '')">{{danmu.text}}</button>
<button type="button" class="btn btn-danger" ng-click="deleteDanmu($index, danmu.id, '')">删</button>
<button type="button" class="btn btn-primary" uib-dropdown-toggle>
<span class="caret"></span>
<span class="sr-only">Split button!</span>
</button>
<ul uib-dropdown-menu role="menu" aria-labelledby="split-button-{{$index}}">
<li role="menuitem" class="disabled"><a href="#">{{danmu.hash}}</a></li>
<li class="divider"></li>
<li role="menuitem"><a href="#" ng-click="deleteDanmu($index, danmu.id, danmu.hash)">封</a></li>
</ul>
</div>
</div>
</div>
</uib-accordion-group>
</uib-accordion>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
var realtime = (function () {
var realtime = angular.module("danmu.audit", [
"ui.bootstrap",
"auditControllers"
]);
var auditControllers = angular.module('auditControllers', []);
var registerInit = []; // 用于初始化回调
var socket = null;
// alternatively, register the interceptor via an anonymous factory
realtime.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $rootScope) {
return {
'responseError': function (response) {
$rootScope.err.code = response.status;
$rootScope.err.desc = response.data.error;
$rootScope.haveError = true;
return $q.reject(response);
},
'response': function (response) {
$rootScope.err.code = response.status;
$rootScope.err.desc = "";
$rootScope.haveError = false;
return response;
}
};
});
}
]);
auditControllers.controller("MainCtrl",
function ($scope, $http, $rootScope) {
$scope.accordion = {
openInfo: true,
};
$scope.isLogin = false;
$scope.connectToServer = false;
$scope.room = "";
$scope.password = "";
$scope.danmus = [];
$scope.config = null;
$rootScope.haveError = false;
$rootScope.err = {
code: 200,
desc: ""
};
$scope.initRoom = function (room) {
$scope.room = room;
};
$scope.enterRoom = function (password) {
$scope.password = password;
for (var object in registerInit) registerInit[object].call();
$scope.isLogin = true;
$scope.accordion.openInfo = false;
};
$scope.buildParam = function (object) {
object.room = $scope.room;
object.password = $scope.password;
return object;
};
$scope.passDanmu = function ($index, id, blockHash) {
socket.emit("auditPass", {
room: $scope.room,
id: id
});
$scope.danmus.splice($index, 1);
};
$scope.deleteDanmu = function ($index, id, blockHash) {
socket.emit("auditFail", {
room: $scope.room,
id: id,
hash: blockHash,
});
$scope.danmus.splice($index, 1);
};
$http.post("/manage/room/get/", $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.roomList = data;
});
registerInit.push(function () {
$http.post("/manage/config/password/get/", $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.config = data;
socket = io(location.origin);
socket.on("connect", function() {
socket.emit("auditLogin", {
password: $scope.config.connectpassword,
room: $scope.room
});
});
socket.on("auditConnected", function () {
$scope.danmus = [];
$scope.$apply();
});
socket.on("auditDanmu", function (data) {
for (key in data) {
var value = data[key];
value.id = key;
value.socketId = value.id + "-" + socket.id;
$scope.danmus.push(value);
}
$scope.$apply();
});
});
});
}
);
return realtime;
})();
</script>
</body>
</html>
================================================
FILE: src/extensions/audit/index.js
================================================
// / <reference path="../../../typings/main.d.ts" />
'use strict'
const fs = require('fs')
const path = require('path')
const socketEvent = require('../../interfaces/Socket')
const httpEvent = require('../../interfaces/Http')
const configEvent = require('../../interfaces/Config')
const danmuEvent = require('../../interfaces/Danmu')
const auditEvent = require('./Audit')
const log = require('../../utilities/log')
const config = require('../../../config')
let danmuQueue = {}
let danmuId = 0
let io = null
function Audit () {
socketEvent.created.listen(socketObject => {
io = socketObject
io.on('connection', socket => {
socket.on('auditLogin', data => {
let room = data.room
if (!config.rooms[room]) return socket.emit('auditInit', 'Room Not Found')
let managePassword = config.rooms[room].managepassword
if (managePassword !== data.password) return socket.emit('auditInit', 'Password Error')
socket.join(`auditRoom${room}`)
socket.emit('auditConnected')
log.log(`审核页面${socket.id}已连接${room}`)
{
let danmuObject = {}
danmuQueue[room].forEach((value, key) => {
danmuObject[key] = value
})
socket.emit('auditDanmu', danmuObject)
}
})
socket.on('auditPass', data => {
log.log(`通过${data.room}(id = ${data.id})`)
auditEvent.passed.emit(danmuQueue[data.room].get(parseInt(data.id)))
danmuQueue[data.room].delete(data.id)
})
socket.on('auditFail', data => {
log.log(`否决${data.room}(id = ${data.id})`)
danmuQueue[data.room].delete(data.id)
if (data.hash !== '') {
config.rooms[data.room].blockusers.push(data.hash)
log.log(`封禁用户${data.hash}成功`)
configEvent.updated.emit()
}
})
})
})
httpEvent.beforeRoute.listen(app => {
let danmuKeys = Object.keys(config.rooms)
app.get('/audit', (req, res, next) => fs.readFile(path.join(__dirname, './audit.html'), (err, data) => {
if (err) throw err
res.end(data)
}))
// Remove all listeners to gotDanmu and bind to a new listener.
const danmuEvents = danmuEvent.get.listeners()
danmuEvent.get.removeAllListeners()
danmuEvents.forEach(event => auditEvent.passed.listen(event))
danmuKeys.forEach(room => {
danmuQueue[room] = new Map()
})
danmuEvent.get.listen(data => {
let room = data.room
danmuQueue[room].set(++danmuId, data)
io.to(`auditRoom${room}`).emit('auditDanmu', { [danmuId]: data }) // 懒得再去写队列
log.log(`${data.room}得到待审核弹幕(${data.hash}) - ${danmuId}:${data.text}`)
})
})
};
module.exports = Audit
================================================
FILE: src/extensions/autoban/index.js
================================================
// / <reference path="../../../typings/main.d.ts" />
'use strict'
const configEvent = require('../../interfaces/Config')
const danmuEvent = require('../../interfaces/Danmu')
const filter = require('../../utilities/filter')
const log = require('../../utilities/log')
const cache = require('../../libraries/cache')
let config = require('../../../config')
module.exports = function () {
// 弹幕被拦截达到一定次数后封号
danmuEvent.ban.listen(danmuData => {
process.nextTick(_ => {
if (filter(danmuData.room).checkUserIsBlocked(danmuData.hash)) return
cache.cache().get('block_' + danmuData.hash, (err, data) => {
if (err !== null && typeof err !== 'undefined') {
log.log('封禁用户查询失败')
console.log(err)
data = 0
} else if (typeof data === 'undefined') {
data = 0
} else {
data = parseInt(data)
data++
}
log.log('自动封号检测' + danmuData.hash + '次数为' + data)
if (data >= config.ext.autoban.block) { // 开始封号
config.rooms[danmuData.room].blockusers.push(danmuData.hash)
log.log('自动封号' + danmuData.hash)
configEvent.updated.emit()
} else {
cache.cache().set('block_' + danmuData.hash, data, 60 * 60 * 3, () => {})
}
})
})
})
}
================================================
FILE: src/extensions/index.js
================================================
// / <reference path="../../typings/main.d.ts" />
'use strict'
const path = require('path')
const log = require('../utilities/log')
let config = require('../../config')
module.exports.init = function (callback) {
Object.keys(config.ext).map(name => {
log.log('加载扩展组件:' + name)
require(path.join(__dirname, './', name))()
})
log.log('扩展组件加载完成')
callback(null)
}
================================================
FILE: src/extensions/livesync/get.py
================================================
import time, sys, json
from danmu import DanMuClient
def out(p):
sys.stdout.write(json.dumps(p) + "\n")
sys.stdout.flush()
dmc = DanMuClient(sys.argv[1])
if not dmc.isValid(): print('Url invalid')
@dmc.danmu
def danmu_fn(msg):
out({'type': 'danmu', 'data': msg})
@dmc.gift
def gift_fn(msg):
out({'type': 'gift', 'data': msg})
@dmc.other
def other_fn(msg):
out({'type': 'other', 'data': msg})
dmc.start(blockThread = True)
================================================
FILE: src/extensions/livesync/index.js
================================================
'use strict'
const cp = require('child_process')
const path = require('path')
const danmuEvent = require('../../interfaces/Danmu')
const log = require('../../utilities/log')
let config = require('../../../config')
const tryCatch = (fn, e) => {
try {
return fn()
} catch (err) {
return e(err)
}
}
module.exports = function () {
Object.keys(config.ext.livesync).forEach(room => {
const liveConfig = config.ext.livesync[room]
const ls = cp.spawn('python', [path.join(__dirname, '/get.py'), liveConfig.liveUrl], ['ignore', 'pipe', 'pipe'])
ls.stdout.on('data', stdout => {
const splitted = stdout.toString().split('\n')
splitted.forEach(data => {
if (data.trim() === '') return
tryCatch(() => {
const ret = JSON.parse(data)
let content = ''
switch (ret.type) {
case 'danmu':
content = `${ret.data.Content}`
break
case 'gift':
log.log(ret.data.NickName + ' 送了一份礼物')
return
case 'other':
log.log('弹幕直播信息:' + data)
return
}
danmuEvent.addSingle.wait({
hash: ret.data.NickName,
room,
text: content,
ip: '127.0.0.1',
ua: 'liveSync',
style: '',
textStyle: '',
lifeTime: '',
color: '',
height: '',
sourceCode: ''
})
}, (e) => {
log.log('解析弹幕失败:' + e.toString())
})
})
})
})
}
================================================
FILE: src/extensions/weibo/index.js
================================================
// / <reference path="../../../../typings/main.d.ts" />
'use strict'
const Passport = require('passport')
const PassportSina = require('passport-sina')
const session = require('express-session')
const cookieParser = require('cookie-parser')
const async = require('async')
const fs = require('fs')
const path = require('path')
const httpEvent = require('../../interfaces/Http')
const danmuEvent = require('../../interfaces/Danmu')
const log = require('../../utilities/log')
const utilities = require('../../utilities')
const cache = require('../../libraries/cache')
let config = require('../../../config')
/*
passport.serializeUser(function (user, callback) {
callback(null, user);
});
passport.deserializeUser(function (obj, callback) {
callback(null, obj);
});
*/
Passport.use(new PassportSina(config.ext.weibo,
function (accessToken, refreshToken, profile, callback) {
process.nextTick(function () {
return callback(null, {
accessToken: accessToken,
profile: profile
})
})
}))
module.exports = function () {
httpEvent.beforeRoute.listen(app => {
app.use(session({
secret: config.http.sessionKey,
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 24 * 60 * 60 * 1000 // 1 day
}
}))
app.use(Passport.initialize())
app.use(cookieParser())
app.get('/auth/sina', Passport.authenticate('sina', {
session: false
}))
app.get('/auth/sina/callback', (req, res, next) => {
Passport.authenticate('sina', {
session: false
}, function (err, data) {
if (err !== null) {
console.log(err)
res.redirect('/')
return
}
if (data === false) {
console.log(arguments)
res.type('html')
res.end("<meta charset='utf-8'><script>alert('系统错误,请重新登录,抱歉');location.href='/';</script>")
return
}
let hash = utilities.getHash(data.profile.id, data.profile.name, data.profile.created_at)
cache.cache().set('weibo_' + hash, JSON.stringify({
accessToken: data.accessToken,
name: data.profile.name,
id: data.profile.id,
nick: data.profile.screen_name
}), 24 * 60 * 60, (err, data) => {
err // eslint-disable-line no-unused-expressions
// Do nothing
// eat it
})
res.cookie('weibo', hash, {
maxAge: 24 * 60 * 60 * 1000
})
log.log('用户' + data.profile.id + '(' + data.profile.name + ')登录(' + hash + ')')
res.redirect('/')
})(req, res, next)
})
app.use(function (req, res, next) {
// 这里用来给req添加函数
req.getSina = function (callback) {
if (typeof req.cookies.weibo !== 'undefined') {
cache.cache().get('weibo_' + req.cookies.weibo, function (err, data) {
if (err) {
callback(err, false)
} else if (typeof data === 'undefined') {
callback(null, false)
} else {
callback(null, JSON.parse(data))
}
})
} else {
callback(null, false)
}
}
next()
})
// 未登录时直接跳转到新浪微博
app.get('/', (req, res, next) => {
async.waterfall([
function (callback) {
req.getSina(callback)
},
function (data, callback) {
if (data === false) {
fs.readFile(path.join(__dirname, './login.html'), function (err, data) {
err // eslint-disable-line no-unused-expressions
res.end(data)
})
} else {
next()
}
}
])
})
app.post('/post', (req, res, next) => {
req.getSina((err, data) => {
if (err || data === false) {
res.end('你还没有用微博登录!请刷新页面后重试!')
} else {
next()
}
})
})
danmuEvent.httpReceived.listen((req, res, danmuData) => {
return new Promise((resolve, reject) => {
req.getSina((err, data) => {
if (data && !err) {
danmuData.hash = data.nick
return resolve(true)
}
reject(err)
})
})
})
danmuEvent.transfer.listen(data => {
data.data.forEach(item => `${item.text}=@${item.hash}: ${item.text}`)
})
})
}
================================================
FILE: src/extensions/weibo/login.html
================================================
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>新浪微博登录</title>
<link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">弹幕</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"></ul>
</div>
</div>
</nav>
<div class="container">
<div class="starter-template">
<div class="row">
<p>
<a href="/auth/sina/"><img src="http://www.sinaimg.cn/blog/developer/wiki/240.png" alt="新浪微博登录" /></a>
</p>
</div>
<div class="row">
<p>你需要先使用新浪微博账号登录才可以发送弹幕。</p>
<p>程序<span style="color:red">无法</span>获知你的密码,也不会主动发送微博。</p>
<p>你可以随时在微博管理中心 -> 我的应用处取消授权。</p>
</div>
<div class="row">
<p>
<br/>
</p>
<p>Powered by zsx</p>
<p>若弹幕无法发送,则请刷新。</p>
<p>在弹幕上表白、使用不文明语言有被封号的可能。</p>
</div>
</div>
</div>
</body>
</html>
================================================
FILE: src/interfaces/Base.js
================================================
const events = require('events')
const eventEmitter = new events.EventEmitter()
const callbackObject = {}
class Base {
static get className () {
return 'base'
}
static register (className, fieldName) {
const eventName = `${className}/${fieldName}`
return {
eventName,
once: eventEmitter.once.bind(eventEmitter, eventName),
listen: eventEmitter.on.bind(eventEmitter, eventName),
emit: eventEmitter.emit.bind(eventEmitter, eventName),
listeners: () => eventEmitter.listeners(eventName),
removeAllListeners: () => eventEmitter.removeAllListeners(eventName)
}
}
static registerCallbackable (className, fieldName) {
const eventName = `${className}/${fieldName}`
if (callbackObject[eventName]) return callbackObject[eventName]
let callback = () => new Promise((resolve, reject) => resolve(true))
callbackObject[eventName] = {
listen: fun => {
callback = fun
},
wait: function (...args) {
return callback(...args) // eslint-disable-line standard/no-callback-literal
}
}
return callbackObject[eventName]
}
}
module.exports = Base
================================================
FILE: src/interfaces/Config.js
================================================
const Base = require('./Base')
const className = 'config'
const updated = Base.register(className, 'updated')
const blockUser = Base.register(className, 'blockUser')
const unblockUser = Base.register(className, 'unblockUser')
class Config extends Base {
static get className () {
return className
}
static get blockUser () {
return blockUser
}
static get updated () {
return updated
}
static get unblockUser () {
return unblockUser
}
}
module.exports = Config
================================================
FILE: src/interfaces/Danmu.js
================================================
const Base = require('./Base')
const className = 'danmu'
const addSingle = Base.registerCallbackable(className, 'addSingle')
const ban = Base.register(className, 'ban')
const transfer = Base.register(className, 'transfer')
const removeSingle = Base.register(className, 'removeSingle')
const removing = Base.register(className, 'removing')
const get = Base.register(className, 'get')
const httpReceived = Base.registerCallbackable(className, 'httpReceived')
const search = Base.registerCallbackable(className, 'search')
class Danmu extends Base {
static get className () {
return className
}
/**
* 新增一条未经处理的弹幕
*/
static get addSingle () {
return addSingle
}
/**
* 封禁弹幕
**/
static get ban () {
return ban
}
/**
* 收到用户刚刚发送的弹幕(通过HTTP)
**/
static get httpReceived () {
return httpReceived
}
/**
* 得到格式化后的弹幕
**/
static get get () {
return get
}
/**
* 删除单条弹幕事件
* @param {object} data
* @param {boolean?} blockUser false
**/
static get removeSingle () {
return removeSingle
}
/**
* 正在删除事件(即准备删除但还未动手)
**/
static get removing () {
return removing
}
/**
* 搜索弹幕
**/
static get search () {
return search
}
/**
* 传输弹幕到客户端
**/
static get transfer () {
return transfer
}
}
module.exports = Danmu
================================================
FILE: src/interfaces/Http.js
================================================
const Base = require('./Base')
const className = 'http'
const _ = require('ramda')
const register = _.curry(Base.register)(className)
const created = register('created')
const beforeRoute = register('beforeRoute')
class Http extends Base {
static get className () {
return className
}
static get created () {
return created
}
static get beforeRoute () {
return beforeRoute
}
}
module.exports = Http
================================================
FILE: src/interfaces/Socket.js
================================================
const Base = require('./Base')
const className = 'socket'
const _ = require('ramda')
const register = _.curry(Base.register)(className)
const created = register('created')
class Socket extends Base {
static get className () {
return className
}
static get created () {
return created
}
}
module.exports = Socket
================================================
FILE: src/libraries/cache/index.js
================================================
'use strict'
let cache = null
const config = require('../../../config')
const memoryCacheMap = new Map()
module.exports = {
init: function (callback) {
switch (config.cache.type) {
case 'memcached':
cache = new (require('memcached'))(config.cache.host)
break
case 'aliyun':
const ALY = require('aliyun-sdk')
const PORT = config.cache.host.split(':')[1]
const HOST = config.cache.host.split(':')[0]
cache = ALY.MEMCACHED.createClient(PORT, HOST, {
username: config.cache.authUser,
password: config.cache.authPassword
})
break
default:
cache = {
get: (name, callback) => {
callback(null, memoryCacheMap.get(name))
},
set: (name, value, date, callback) => {
memoryCacheMap.set(name, value)
callback(null)
}
}
}
callback(null)
}
}
module.exports.cache = () => {
return cache
}
================================================
FILE: src/libraries/database/csv.js
================================================
const fs = require('fs')
const path = require('path')
const danmuEvent = require('../../interfaces/Danmu')
const log = require('../../utilities/log')
const config = require('../../../config')
function formatContent (content) {
return '"' + content.toString().replace(/"/g, '""') + '"'
}
module.exports = {}
module.exports.init = function (callback) {
let savePath = path.resolve(config.database.savedir)
log.log('保存位置:' + savePath)
callback(null)
danmuEvent.get.listen(data => {
let joinArray = []
joinArray.push(formatContent(Math.round(new Date().getTime() / 1000)))
joinArray.push(formatContent(data.hash))
joinArray.push(formatContent(data.ip))
joinArray.push(formatContent(data.ua))
joinArray.push(formatContent(data.text))
joinArray.push('\r\n')
fs.appendFile(path.resolve(savePath, data.room + '.csv'), joinArray.join(','), err => {
if (err) log.log(err)
})
})
danmuEvent.search.listen((data) => new Promise((resolve, reject) => {
resolve('[{"user": "ERROR", "text": "Not yet supported", "publish": ""}]')
}))
}
================================================
FILE: src/libraries/database/index.js
================================================
const config = require('../../../config')
module.exports = {
init: function (callback) {
require('./' + config.database.type + '.js').init(function () {
callback.apply(this, arguments)
})
}
}
================================================
FILE: src/libraries/database/mongo.js
================================================
const mongodb = require('mongodb')
const danmuEvent = require('../../interfaces/Danmu')
const log = require('../../utilities/log')
const config = require('../../../config')
let db = null
const server = new mongodb.Server(config.database.server, config.database.port, {
auto_reconnect: true
})
const getConnection = function (callback) {
db = new mongodb.Db(config.database.db, server, {
w: 1
})
db.open(err => {
if (err !== null) {
log.log('数据库连接错误')
throw err
}
if (config.database.username !== '') {
db.authenticate(config.database.username, config.database.password, function (err, result) {
if (err !== null) {
log.log('数据库验证错误')
throw err
}
callback.apply(callback, arguments)
})
}
callback.apply(callback, arguments)
log.log('数据库连接成功')
})
// callback(null);
}
module.exports = {
init: function (callback) {
getConnection(callback)
danmuEvent.get.listen(data => {
let room = data.room
db.collection(config.rooms[room].table).insert({
user: data.hash,
text: data.text,
publish: Math.round(new Date().getTime() / 1000),
ip: data.ip,
ua: data.ua
}, (err, results) => {
if (err !== null) {
log.log('数据库写入出错')
console.log(err)
}
})
})
danmuEvent.search.listen((data) => new Promise((resolve, reject) => {
let room = data.room
db.collection(config.rooms[room].table).find({
text: {
$regex: '.*?' + pregQuote(data.key) + '.*?'
}
}, null, null).toArray(function (err, results) {
if (err === null) {
results.map(function (object) {
object.id = object._id
})
resolve(JSON.stringify(results))
} else {
log.log('数据库搜索出错')
console.error(err)
reject(err)
}
})
}))
}
}
function pregQuote (str, delimiter) {
// discuss at: http://phpjs.org/functions/preg_quote/
// original by: booeyOH
// improved by: Ates Goral (http://magnetiq.com)
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Brett Zamir (http://brett-zamir.me)
// bugfixed by: Onno Marsman
// example 1: preg_quote("$40");
// returns 1: '\\$40'
// example 2: preg_quote("*RRRING* Hello?");
// returns 2: '\\*RRRING\\* Hello\\?'
// example 3: preg_quote("\\.+*?[^]$(){}=!<>|:");
// returns 3: '\\\\\\.\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:'
return String(str)
.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&')
}
================================================
FILE: src/libraries/database/mysql.js
================================================
const SECONDS_IN_DAY = 24 * 60 * 60 * 1000
const mysql = require('mysql')
const async = require('async')
const danmuEvent = require('../../interfaces/Danmu')
const log = require('../../utilities/log')
const config = require('../../../config')
let pool = null
let connection = null
let errorCounter = 0
let firstErrorTime = new Date()
const createTableSql = [
'CREATE TABLE IF NOT EXISTS `%table%` (',
'danmu_id int(11) NOT NULL AUTO_INCREMENT,',
"danmu_user varchar(255) NOT NULL DEFAULT '',",
'danmu_text text NOT NULL,',
"danmu_publish int(11) NOT NULL DEFAULT '0',",
"danmu_ip varchar(255) NOT NULL DEFAULT '',",
'danmu_useragent text NOT NULL,',
'PRIMARY KEY (danmu_id),',
'KEY danmu_TPISC (danmu_publish)',
') ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;'
].join('\n')
const createDatabase = function (callbackOrig) {
const asyncList = Object.keys(config.rooms)
async.each(asyncList, (room, callback) => {
connection.query('SELECT MAX(danmu_id) FROM `' + config.rooms[room].table + '`', function (err, rows) {
if (err !== null) {
log.log('Creating Table...')
connection.query(createTableSql.replace(/%table%/g, config.rooms[room].table), function (err, rows) {
callback(err)
})
} else {
callback(null)
}
})
}, function (err) {
callbackOrig(err)
})
}
const dbErrorHandler = function (err) {
if (err !== null) {
if (err.errno !== 'ECONNRESET') { // 部分MySQL会自动超时,此时要重连但不计errorCounter
if (errorCounter === 0 || new Date() - firstErrorTime >= SECONDS_IN_DAY) {
firstErrorTime = new Date()
errorCounter = 0
}
errorCounter++
console.log(err)
log.log('数据库第' + errorCounter + '次连接出错。')
if (connection) {
connection.release()
}
getConnection()
}
if (errorCounter >= config.database.retry) {
log.log('数据库连接错误次数超过上限,程序退出。')
throw err
}
}
}
const getConnection = function (callback) {
let called = false
pool.getConnection((err, privateConnection) => {
connection = privateConnection
if (err) {
dbErrorHandler(err)
if (!called && callback) {
callback(err)
called = true
}
} else {
connection.on('error', dbErrorHandler)
log.log('数据库连接正常')
createDatabase(err => {
if (!called && callback) {
callback(err)
called = true
}
})
}
})
}
module.exports = {
init: function (callback) {
pool = mysql.createPool({
host: config.database.server,
user: config.database.username,
password: config.database.password,
port: config.database.port,
database: config.database.db,
acquireTimeout: config.database.timeout,
connectionLimit: 1
// debug: true
})
getConnection(callback)
let keepAlive = function () {
if (!connection) return
connection.ping()
}
danmuEvent.get.listen(data => {
let room = data.room
connection.query('INSERT INTO `%table%` (danmu_user, danmu_text, danmu_publish, danmu_ip, danmu_useragent) VALUES (?, ?, ?, ?, ?)'.replace('%table%', config.rooms[room].table), [
data.hash, data.text, Math.round(new Date().getTime() / 1000), data.ip, data.ua
], function (err, rows) {
if (err !== null) {
log.log('数据库写入出错')
console.log(err)
}
})
})
danmuEvent.search.listen((data) => new Promise((resolve, reject) => {
let room = data.room
connection.query('SELECT * from `%table%` where `danmu_text` LIKE ? LIMIT 20'.replace('%table%', config.rooms[room].table), [
'%' + data.key + '%'
], function (err, rows) {
if (err === null) {
let ret = []
ret = JSON.stringify(rows).replace(/"danmu_/g, '"')
resolve(ret)
} else {
log.log('数据库搜索出错')
console.log(err)
reject(err)
}
})
}))
getConnection()
setInterval(keepAlive, config.database.timeout)
// connection.on("error", dbErrorHandler);
// connectDataBase(callback);
}
}
================================================
FILE: src/libraries/database/none.js
================================================
const log = require('../../utilities/log')
module.exports = {
init: function (callback) {
log.log('无数据库')
callback(null)
}
}
================================================
FILE: src/libraries/http/index.js
================================================
const fs = require('fs')
const express = require('express')
const errorHandler = require('errorhandler')
const path = require('path')
const app = express()
const bodyParser = require('body-parser')
const httpEvent = require('../../interfaces/Http')
const log = require('../../utilities/log')
let config = require('../../../config')
module.exports = {
init: function (callback) {
app
.engine('.html', require('ejs').__express)
// .use(logger('dev'))
.use(bodyParser.json())
.use(bodyParser.urlencoded({
extended: true
}))
.use(errorHandler())
.set('view engine', 'html')
.set('views', path.join(__dirname, './view/'))
.use('/static/bootstrap', express.static(path.join(__dirname, '../../../node_modules/bootstrap/dist/')))
.use('/static/jquery', express.static(path.join(__dirname, '../../../node_modules/jquery/dist/')))
.use('/static/angular', express.static(path.join(__dirname, '../../../node_modules/angular/')))
.use('/static/angular-ui-bootstrap', express.static(path.join(__dirname, '../../../node_modules/angular-ui-bootstrap/')))
.use(express.static(path.join(__dirname, './res/')))
httpEvent.beforeRoute.emit(app)
// 处理路由
fs.readdir(path.join(__dirname, './route'), (err, files) => {
if (err) {
console.error(err)
return
}
files.forEach((filename) => {
require(path.join(__dirname, './route', filename))(app)
})
})
let server = app.listen(config.http.port, () => {
log.log(`服务器于http://127.0.0.1:${config.http.port}/成功创建`)
httpEvent.created.emit(server)
callback(null)
})
}
}
================================================
FILE: src/libraries/http/res/manage.css
================================================
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5;
border-right: 1px solid #eee;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
================================================
FILE: src/libraries/http/res/manage.js
================================================
var manage = (function () { // eslint-disable-line
var manage = angular.module('danmu.manage', [ // eslint-disable-line
'ui.bootstrap',
'manageControllers'
])
var manageControllers = angular.module('manageControllers', []) // eslint-disable-line
var registerInit = [] // 用于初始化回调
// alternatively, register the interceptor via an anonymous factory
manage.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $rootScope) {
return {
'responseError': function (response) {
$rootScope.err.code = response.status
$rootScope.err.desc = response.data.error
$rootScope.haveError = true
return $q.reject(response)
},
'response': function (response) {
$rootScope.err.code = response.status
$rootScope.err.desc = ''
$rootScope.haveError = false
return response
}
}
})
}
])
manageControllers.controller('MainCtrl',
function ($scope, $http, $rootScope) {
$scope.accordion = {
closeOther: false,
openInfo: true,
openDanmu: false,
openBlock: true,
openConfig: false,
openPermissions: true,
openPassword: false,
disableInfo: false
}
$scope.isLogin = false
$scope.room = ''
$scope.password = ''
$rootScope.haveError = false
$rootScope.err = {
code: 200,
desc: ''
}
$scope.initRoom = function (room) {
$scope.room = room
}
$scope.enterRoom = function (password) {
$scope.password = password
for (var object in registerInit) registerInit[object].call()
$scope.isLogin = true
$scope.accordion.openInfo = false
}
$scope.buildParam = function (object) {
object.room = $scope.room
object.password = $scope.password
return object
}
$http.post('/manage/room/get/', $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.roomList = data
})
}
)
manageControllers.controller('DanmuCtrl',
function ($scope, $http) {
$scope.danmu = {}
$scope.danmu.searchKey = ''
$scope.danmu.doSearch = function () {
$http.post('/manage/search', $scope.buildParam({
key: $scope.danmu.searchKey
})).success(function (data, status, headers, config) {
$scope.danmu.result = data
})
}
}
)
manageControllers.controller('BlockCtrl',
function ($scope, $http) {
$scope.block = {}
$scope.block.textUser = ''
$scope.block.doAdd = function () {
$http.post('/manage/block/add', $scope.buildParam({
user: $scope.block.textUser
})).success(function (data, status, headers, config) {
$scope.block.result.push($scope.block.textUser)
$scope.block.textUser = ''
})
}
$scope.block.checkKeyDown = function (e) {
if (e.keyCode === 13) this.doAdd()
}
$scope.block.doRemove = function (user) {
$http.post('/manage/block/remove', $scope.buildParam({
user: user
})).success(function (data, status, headers, config) {
$scope.block.result.splice($scope.block.result.indexOf(user), 1)
})
}
registerInit.push(function () {
$http.post('/manage/block/get/', $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.block.result = data
})
})
}
)
manageControllers.controller('ConfigCtrl',
function ($scope, $http) {
$scope.config = {}
$scope.config.realConfig = {}
$scope.config.realConfig.replaceKeyword = ''
$scope.config.realConfig.blockKeyword = ''
$scope.config.realConfig.ignoreKeyword = ''
$scope.config.realConfig.socketinterval = 0
$scope.config.realConfig.socketsingle = 0
$scope.config.realConfig.maxlength = 0
$scope.config.realConfig.textlength = 0
$scope.config.submitConfig = function () {
try {
$http.post('/manage/config/set/', $scope.buildParam($scope.config.realConfig)).success(function (data, status, headers, config) {
$scope.config.realConfig = data
})
} catch (e) {
window.alert('正则检测出错!\n\n' + e.toString())
}
}
registerInit.push(function () {
$http.post('/manage/config/get/', $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.config.realConfig = data
})
})
}
)
manageControllers.controller('PermissionCtrl',
function ($scope, $http) {
$scope.config = {}
$scope.$makeClass = function (configName) {
return {
'active': $scope.config[configName],
'btn-danger': !$scope.config[configName],
'btn-success': $scope.config[configName]
}
}
$scope.$getStateText = function (configName) {
return $scope.config[configName] ? '开' : '关'
}
$scope.$setState = function (configName) {
$scope.config[configName] = !$scope.config[configName]
}
$scope.$submitPermissions = function () {
$http.post('/manage/config/permissions/set/', $scope.buildParam($scope.config)).success(function (data, status, headers, config) {
$scope.config = data
})
}
registerInit.push(function () {
$http.post('/manage/config/permissions/get/', $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.config = data
})
})
}
)
manageControllers.controller('PasswordCtrl',
function ($scope, $http) {
$scope.config = {
advancedpassword: '',
managepassword: '',
connectpassword: ''
}
$scope.$submitPassword = function (passwordState) {
var modal = passwordState + 'password'
if (!$scope.config[modal] || $scope.config[modal] === '') return
if (window.confirm('确定要更新密码(类型:' + modal + ')?\n\n更新连接密码后,已经连接的客户端不受影响,新客户端将使用新密码;\n更新管理密码后,必须刷新页面才可以继续使用。')) {
$http.post('/manage/config/password/set/', $scope.buildParam({
type: modal,
newPassword: $scope.config[modal]
})).success(function (data, status, headers, config) {
if (modal === 'managepassword') {
window.location.reload()
}
$scope.config[modal] = ''
})
}
}
}
)
return manage
})()
================================================
FILE: src/libraries/http/res/realtime.js
================================================
// / <reference path="../../../typings/main.d.ts" />
var realtime = (function () { // eslint-disable-line
var realtime = angular.module('danmu.realtime', [ // eslint-disable-line
'ui.bootstrap',
'realtimeControllers'
])
var realtimeControllers = angular.module('realtimeControllers', []) // eslint-disable-line
var registerInit = [] // 用于初始化回调
var socket = null
// alternatively, register the interceptor via an anonymous factory
realtime.config(['$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $rootScope) {
return {
'responseError': function (response) {
$rootScope.err.code = response.status
$rootScope.err.desc = response.data.error
$rootScope.haveError = true
return $q.reject(response)
},
'response': function (response) {
$rootScope.err.code = response.status
$rootScope.err.desc = ''
$rootScope.haveError = false
return response
}
}
})
}
])
realtimeControllers.controller('MainCtrl',
function ($scope, $http, $rootScope) {
$scope.accordion = {
openInfo: true
}
$scope.isLogin = false
$scope.connectToServer = false
$scope.room = ''
$scope.password = ''
$scope.danmus = []
$scope.config = null
$rootScope.haveError = false
$rootScope.err = {
code: 200,
desc: ''
}
$scope.initRoom = function (room) {
$scope.room = room
}
$scope.enterRoom = function (password) {
$scope.password = password
for (var object in registerInit) registerInit[object].call()
$scope.isLogin = true
$scope.accordion.openInfo = false
}
$scope.buildParam = function (object) {
object.room = $scope.room
object.password = $scope.password
return object
}
$scope.deleteDanmu = function ($index, id, blockHash) {
$http.post('/manage/danmu/delete/', $scope.buildParam({
id: id,
hash: blockHash
})).success(function (data, status, headers, config) {
if ($scope.danmus[$index]) {
if ($scope.danmus[$index].id === id) {
$scope.danmus[$index].lifeTime = 0
}
}
})
}
$http.post('/manage/room/get/', $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.roomList = data
})
registerInit.push(function () {
$http.post('/manage/config/password/get/', $scope.buildParam({})).success(function (data, status, headers, config) {
$scope.config = data
socket = window.io(window.location.origin)
socket.emit('password', {
password: $scope.config.connectpassword,
room: $scope.room,
info: {
version: window.serverVersion
}
})
socket.on('connected', function () {
$scope.connectToServer = true
})
socket.on('danmu', function (data) {
data.data.forEach(function (value) {
value.socketId = value.id + '-' + socket.id
value.lifeTime = parseInt(value.lifeTime)
$scope.danmus.push(value)
})
$scope.$apply()
})
setInterval(function () {
var isRemoved = false
$scope.danmus.forEach(function (value, key) {
value.lifeTime -= 60
if (value.lifeTime <= 0) {
$scope.danmus.splice(key, 1)
isRemoved = true
}
})
if (isRemoved) {
$scope.$apply()
}
}, 1000) // auto remove 60fps
})
})
}
)
return realtime
})()
================================================
FILE: src/libraries/http/route/index.js
================================================
const config = require('../../../../config')
module.exports = function (app) {
// Initialize Hostname Map
const hostnameMap = new Map()
Object.keys(config.rooms).forEach(room => config.rooms[room].hostname.forEach(value => hostnameMap.set(value, room)))
function getRoom (hostname) {
return (hostnameMap.has(hostname)) ? hostnameMap.get(hostname) : null
}
function renderIndex (advanced, room) {
const permission = config.rooms[room].permissions
return {
config,
advanced,
room,
permission
}
}
app.route('/*').all((req, res, next) => {
res.append('Server', 'zsx\'s Danmu Server')
req.room = getRoom(req.hostname)
for (let item in config.http.headers) {
res.append(item, config.http.headers[item])
}
if (req.room === null) {
res.status(403)
res.end('403 Forbidden')
}
next()
})
app.get('/', (req, res) => {
res.render('index', renderIndex(false, req.room))
})
app.get('/advanced', (req, res) => {
res.render('index', renderIndex(true, req.room))
})
app.post((req, res, next) => {
res.header('Content-Type', 'text/html; charset=utf-8')
next()
})
}
================================================
FILE: src/libraries/http/route/manage.js
================================================
const config = require('../../../../config')
module.exports = function (app) {
app.get('/manage', function (req, res) {
res.render('manage', {
config
})
})
// 总身份验证
app.post('/manage/*', (req, res, next) => {
if (/room\/get/.test(req.url)) {
// 如果是房间下发则不验证身份
return next()
}
let room = req.body.room
if (!config.rooms[room]) {
res.status(404)
return res.end('{"error": "房间错误"}')
}
if (config.rooms[room].managepassword !== req.body.password) {
res.status(403)
return res.end('{"error": "密码错误"}')
}
return next()
})
}
================================================
FILE: src/libraries/http/route/manageBlock.js
================================================
const configEvent = require('../../../interfaces/Config')
const log = require('../../../utilities/log')
const config = require('../../../../config')
module.exports = function (app) {
app.post('/manage/block/add/', (req, res) => {
let room = req.body.room
configEvent.blockUser.emit(room, req.body.user)
res.end('{"error": "封禁用户成功"}')
})
app.post('/manage/block/get/', (req, res) => {
let room = req.body.room
log.log('请求被封禁用户成功')
res.end(JSON.stringify(config.rooms[room].blockusers))
})
app.post('/manage/block/remove/', (req, res) => {
let room = req.body.room
configEvent.unblockUser.emit(room, req.body.user)
res.end('{"error": "移除封禁成功"}')
})
}
================================================
FILE: src/libraries/http/route/manageConfig.js
================================================
const configEvent = require('../../../interfaces/Config')
const utilities = require('../../../utilities')
const log = require('../../../utilities/log')
const config = require('../../../../config')
module.exports = function (app) {
app.post('/manage/config/set/', (req, res) => {
const room = req.body.room || ''
config.rooms[room].keyword.replacement = new RegExp(req.body.replaceKeyword, 'ig')
config.rooms[room].keyword.block = new RegExp(req.body.blockKeyword, 'ig')
config.rooms[room].keyword.ignore = new RegExp(req.body.ignoreKeyword, 'ig')
config.rooms[room].maxlength = req.body.maxlength
config.rooms[room].textlength = req.body.textlength
config.rooms[room].openstate = req.body.openstate
config.websocket.interval = req.body.socketinterval
config.websocket.singlesize = req.body.socketsingle
const newConfig = JSON.stringify(utilities.buildConfigToArray(room))
log.log('收到配置信息:' + newConfig)
configEvent.updated.emit()
return res.end(newConfig)
})
app.post('/manage/config/permissions/set/', (req, res) => {
const room = req.body.room || ''
Object.keys(config.rooms[room].permissions).map(item => {
config.rooms[room].permissions[item] = !!req.body[item]
})
let newConfig = JSON.stringify(config.rooms[room].permissions)
log.log('收到权限配置信息:' + newConfig)
configEvent.updated.emit()
return res.end(newConfig)
})
app.post('/manage/config/password/set/', (req, res) => {
const type = req.body.type || ''
const password = req.body.newPassword || ''
const room = req.body.room || ''
if (config.rooms[room][type]) {
config.rooms[room][type] = password
log.log('房间(' + room + ')的' + type + '已更新为' + password)
}
configEvent.updated.emit()
return res.end()
})
app.post('/manage/config/permissions/get/', (req, res) => {
const room = req.body.room || ''
log.log('已将权限配置向管理页面下发')
return res.end(JSON.stringify(config.rooms[room].permissions))
})
app.post('/manage/config/get/', (req, res) => {
const room = req.body.room || ''
log.log('已将配置向管理页面下发')
return res.end(JSON.stringify(utilities.buildConfigToArray(room)))
})
app.post('/manage/config/password/get/', (req, res) => {
const room = req.body.room || ''
log.log('已将密码向管理页面下发')
return res.end(JSON.stringify({
connectpassword: config.rooms[room].connectpassword
}))
})
}
================================================
FILE: src/libraries/http/route/manageDanmu.js
================================================
const danmuEvent = require('../../../interfaces/Danmu')
module.exports = function (app) {
app.post('/manage/danmu/delete/', (req, res) => {
const data = {}
data.hash = req.body.hash || ''
data.id = req.body.id || 0
data.room = req.body.room || ''
if (data.id === 0) {
res.end({})
return
}
danmuEvent.removeSingle.emit(data, data.hash !== '')
return res.end('{"error": "删除弹幕成功"}')
})
}
================================================
FILE: src/libraries/http/route/manageRoom.js
================================================
const log = require('../../../utilities/log')
const config = require('../../../../config')
module.exports = function (app) {
app.post('/manage/room/get/', (req, res) => {
const ret = []
Object.keys(config.rooms).map(room => {
ret.push({
id: room,
display: config.rooms[room].display
})
})
log.log('已把房间信息向管理页面下发')
return res.end(JSON.stringify(ret))
})
}
================================================
FILE: src/libraries/http/route/manageSearch.js
================================================
const log = require('../../../utilities/log')
const danmuEvent = require('../../../interfaces/Danmu')
module.exports = function (app) {
app.post('/manage/search', function (req, res) {
const room = req.body.room
log.log('尝试搜索' + req.body.key)
danmuEvent.search.wait({
key: req.body.key,
room
}).then(data => {
log.log('搜索' + req.body.key + '成功')
res.end(data)
}).catch(data => {
res.end(`[{"user": "ERROR", "text": "${JSON.parse(data.toString())}", "publish": ""}]`)
})
})
}
================================================
FILE: src/libraries/http/route/post.js
================================================
const utilities = require('../../../utilities')
const danmuEvent = require('../../../interfaces/Danmu')
const config = require('../../../../config')
module.exports = function (app) {
app.post('/post', (req, res) => {
const room = req.room
const roomConfig = config.rooms[room]
const ip = roomConfig.cdn ? req.ip : (req.get('X-Real-IP') || req.get('X-Forwarded-For') || req.ip)
const hash = utilities.getHash(ip, req.headers['user-agent'], req.body.hash)
const danmuData = {
hash,
room,
text: req.body.text,
ip,
ua: req.headers['user-agent'],
style: '',
textStyle: '',
lifeTime: '',
color: '',
height: '',
sourceCode: ''
}
if (req.body.text === '') {
return res.end('弹幕不能为空')
}
danmuEvent.httpReceived.wait(req, res, danmuData)
.then(() => danmuEvent.addSingle.wait(danmuData, req.body, {
password: req.body.password,
isAdvanced: req.body.type === 'advanced'
}))
.then(() => res.end('发送成功!'))
.catch(e => res.end(e.toString()))
})
}
================================================
FILE: src/libraries/http/route/realtime.js
================================================
const config = require('../../../../config')
module.exports = function (app) {
app.get('/realtime', (req, res) => {
res.render('realtime', {
config,
version: global.version
})
})
}
================================================
FILE: src/libraries/http/view/index.html
================================================
<!DOCTYPE html>
<!--懒得写CSS和一堆JS了,麻烦~-->
<html lang="zh-cmn-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>发送弹幕</title>
<link href="/static/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="/static/jquery/jquery.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
.danmu-advanced {
display: <%=advanced ? "block": "none"%>;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">弹幕</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"></ul>
</div>
</div>
</nav>
<div class="container">
<div class="starter-template">
<form id="form-ret">
<div class="row">
<p class="text-success">发送你的弹幕吧!</p>
</div>
<div class="row">
<textarea class="form-control" name="text" row="4" id="txt-barrage"></textarea>
</div>
<p> </p>
<div class="row danmu-advanced danmu-style">
<div class="form-group">
<label class="col-sm-4 control-label">特效</label>
<div class="col-sm-8">
<label class="radio-inline">
<input type="radio" name="style" value="scroll" checked>滚动</label>
<label class="radio-inline">
<input type="radio" name="style" value="reversescroll">反向滚动</label>
<label class="radio-inline">
<input type="radio" name="style" value="staticup">固定最上</label>
<label class="radio-inline">
<input type="radio" name="style" value="staticdown">固定最下</label>
<label class="radio-inline danmu-sourceCode">
<input type="radio" name="style" value="custom">自定义</label>
</div>
</div>
<p> </p>
</div>
<div class="row danmu-advanced danmu-color">
<div class="form-group">
<label class="col-sm-4 control-label">颜色(16进制)</label>
<div class="col-sm-8">
<input type="color" name="color" class="form-control" name="txt-color" value="#ffffff" />
</div>
</div>
<p> </p>
</div>
<div class="row danmu-advanced danmu-textStyle">
<div class="form-group">
<label class="col-sm-4 control-label">文字CSS</label>
<div class="col-sm-8">
<input type="text" name="textStyle" class="form-control" name="txt-style" value="normal bold 3em 微软雅黑" />
</div>
</div>
<p> </p>
</div>
<div class="row danmu-advanced danmu-height">
<div class="form-group">
<label class="col-sm-4 control-label">弹幕高度</label>
<div class="col-sm-8">
<input type="text" name="height" class="form-control" name="txt-height" value="30" />
</div>
</div>
<p> </p>
</div>
<div class="row danmu-advanced danmu-lifeTime">
<div class="form-group">
<label class="col-sm-4 control-label">显示时间</label>
<div class="col-sm-8">
<input type="text" name="lifeTime" class="form-control" name="txt-time" value="240" />
</div>
</div>
<p> </p>
</div>
<div class="row danmu-advanced">
<div class="form-group">
<label class="col-sm-4 control-label">高级弹幕密码</label>
<div class="col-sm-8">
<input type="password" name="password" class="form-control" name="txt-password" value="" />
</div>
</div>
<p> </p>
</div>
<div class="row">
<button class="form-control btn-success" id="btn-send">喷射</button>
</div>
<div class="row">
<div class="checkbox">
<label>
<input type="checkbox" id="saveContent">保留发送内容</label>
</div>
</div>
<input type="hidden" name="type" value="<%=advanced?'advanced':''%>" />
<div class="danmu-custom danmu-sourceCode hide">
<div class="row">
<p><label>自定义弹幕</label></p>
</div>
<div class="row">
<textarea class="form-control" name="sourceCode" row="4" id="txt-custom" placeholder="Enter JavaScript Here.."></textarea>
</div>
</div>
<div class="row">
<p>
<br/>
</p>
<p>Powered by zsx</p>
<p>若弹幕无法发送,则请刷新。</p>
<p>在弹幕上表白、使用不文明语言有被封号的可能。</p>
</div>
</form>
</div>
</div>
<script>
$(function() {
var roomsConfig = <%-JSON.stringify(permission)%>;
var room = "<%=room%>";
var danmuHash = null;
calculateHash();
initializeRoomSelector();
bindSendButton();
function calculateHash() {
function b2h(s) {
var i, l, n, o = '';
s += '';
for (i = 0, l = s.length; i < l; i++) {
n = s.charCodeAt(i).toString(16);
o += n.length < 2 ? '0' + n : n;
}
return o;
}
// In some modes, localStorage cannot be used.
try {
danmuHash = localStorage.getItem("danmuHash");
} catch (e) {
danmuHash = "CANNOT_GET_HASH";
// eat it
}
if (danmuHash === null) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.textBaseline = "top";
ctx.font = "14px '微软雅黑'";
ctx.fillStyle = "#0281cd";
ctx.fillText("zsx's Danmu Server\n\n弹幕验证帆布指纹", 5, 5);
ctx.fillText("http://www.zsxsoft.com/", 5, 30);
var b64 = b2h(atob(canvas.toDataURL().replace("data:image/png;base64,", "")).slice(-32, -24));
try {
localStorage.danmuHash = b64;
} catch (e) {
// Still do nothing
}
}
}
function initializeRoomSelector() {
function initializePermissions(room) {
for (var item in roomsConfig) {
if (roomsConfig[item]) {
$(".danmu-" + item).show();
}
}
}
initializePermissions(room);
$("input[name='style']").change(function(e) {
if ($(this).val() == "custom") {
$(".danmu-custom").removeClass("hide");
} else {
$(".danmu-custom").addClass("hide");
}
});
}
function bindSendButton() {
$("#btn-send").click(function() {
var ret = {};
var object = $("#form-ret").serializeArray();
for (var i = 0; i < object.length; i++) {
ret[object[i].name] = object[i].value;
}
ret.hash = danmuHash;
$.post("/post", ret).done(function(data) {
alert(data);
if (!$("#saveContent")[0].checked) $("#txt-barrage").val("").focus();
$("#btn-send").removeAttr('disabled');
});
$("#btn-send").attr("disabled", "disabled");
});
}
});
</script>
</body>
</html>
================================================
FILE: src/libraries/http/view/manage.html
================================================
<!DOCTYPE html>
<html lang="zh-cmn-CN" ng-app="danmu.manage" ng-controller="MainCtrl">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="/static/angular/angular.min.js"></script>
<script src="/static/angular-ui-bootstrap/ui-bootstrap-tpls.min.js"></script>
<link href="/manage.css" rel="stylesheet">
<title>系统管理</title>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header"><a class="navbar-brand" href="#">系统管理</a></div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<uib-alert type="danger" ng-show="haveError">错误{{err.code}}:{{err.desc}};建议刷新页面。</uib-alert>
<div class="col-sm-12 col-md-12 main">
<uib-accordion close-others="accordion.closeOther">
<uib-accordion-group heading="管理信息" is-open="accordion.openInfo" is-disabled="accordion.disableInfo">
<div class="container">
<uib-alert type="danger" ng-show="!isLogin">务必先填入房间信息再进行管理。</uib-alert>
<uib-alert type="success" ng-show="isLogin">你选择了{{room}}房间</uib-alert>
<div class="list-group" ng-show="!isLogin">
<a href="#" class="list-group-item" ng-class="{active: room == roomS.id}" ng-repeat="roomS in roomList" ng-click="initRoom(roomS.id)">{{roomS.display}} ({{roomS.id}})</a>
</div>
<form ng-submit="enterRoom(password)" ng-show="!isLogin">
<div class="input-group">
<input type="password" class="form-control" placeholder="房间密码" ng-model="password">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">确认</button>
</span>
</div>
</form>
</div>
</uib-accordion-group>
<uib-accordion-group heading="弹幕搜索" is-open="accordion.openDanmu">
<div ng-controller="DanmuCtrl" class="container">
<form ng-submit="danmu.doSearch()">
<div class="input-group">
<input type="text" class="form-control" placeholder="搜索弹幕" ng-model="danmu.searchKey">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">搜索</button>
</span>
</div>
</form>
<div class="row">
<p>
<br/>
</p>
<table class="table table-hover">
<tr>
<th>ID</th>
<th>用户</th>
<th>弹幕</th>
</tr>
<tr ng-repeat="item in danmu.result">
<td>{{item.id}}</td>
<td>{{item.user}}</td>
<td>{{item.text}}</td>
</tr>
</table>
</div>
</div>
</uib-accordion-group>
<uib-accordion-group heading="黑名单管理" is-open="accordion.openBlock">
<div ng-controller="BlockCtrl" class="container">
<form ng-submit="block.doAdd()">
<div class="input-group">
<input type="text" class="form-control" placeholder="添加用户" ng-model="block.textUser">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">添加</button>
</span>
</div>
</form>
<div class="row">
<p>
<br/>
</p>
<button type="button" class="btn btn-primary btn-sm" ng-repeat="item in block.result track by $index" style="margin-right: 5px" ng-click="block.doRemove(item)" ng-value="item">{{item}}</button>
</div>
</div>
</uib-accordion-group>
<uib-accordion-group heading="权限设置" is-open="accordion.openPermissions">
<div ng-controller="PermissionCtrl" class="container">
<form class="form-horizontal" ng-submit="$submitPermissions()">
<div class="form-group">
<label for="permissionSend" class="col-sm-2 control-label">弹幕接收开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('send')" id="permissionSend" ng-click="$setState('send')">当前状态:{{$getStateText('send')}}</button> <small>关闭后不再接收任何新弹幕</small>
</div>
</div>
<div class="form-group">
<label for="permissionStyle" class="col-sm-2 control-label">弹幕样式开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('style')" id="permissionStyle" ng-click="$setState('style')">当前状态:{{$getStateText('style')}}</button>
</div>
</div>
<div class="form-group">
<label for="permissionColor" class="col-sm-2 control-label">颜色设置开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('color')" id="permissionColor" ng-click="$setState('color')">当前状态:{{$getStateText('color')}}</button>
</div>
</div>
<div class="form-group">
<label for="permissionTextStyle" class="col-sm-2 control-label">文字样式开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('textStyle')" id="permissionTextStyle" ng-click="$setState('textStyle')">当前状态:{{$getStateText('textStyle')}}</button>
</div>
</div>
<div class="form-group">
<label for="permissionHeight" class="col-sm-2 control-label">高度设置开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('height')" id="permissionHeight" ng-click="$setState('height')">当前状态:{{$getStateText('height')}}</button>
</div>
</div>
<div class="form-group">
<label for="permissionLifeTime" class="col-sm-2 control-label">时间设置开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('lifeTime')" id="permissionLifeTime" ng-click="$setState('lifeTime')">当前状态:{{$getStateText('lifeTime')}}</button>
</div>
</div>
<div class="form-group">
<label for="permissionSourceCode" class="col-sm-2 control-label">自定义弹幕开关</label>
<div class="col-sm-10">
<button type="button" class="btn" ng-class="$makeClass('sourceCode')" id="permissionSourceCode" ng-click="$setState('sourceCode')">当前状态:{{$getStateText('sourceCode')}}</button> <small>此权限要求弹幕样式开关一并打开才可使用。非常危险,可能导致客户端出现莫名错误,对非信任用户不要打开!</small>
</div>
</div>
<div class="row">
<button class="col-sm-offset-5 btn btn-success" type="submit">更新权限</button>
</div>
</form>
</div>
</uib-accordion-group>
<uib-accordion-group heading="参数设置" is-open="accordion.openConfig">
<div ng-controller="ConfigCtrl" class="container">
<form class="form-horizontal" ng-submit="config.submitConfig()">
<div class="form-group">
<label for="configReplaceKeyword" class="col-sm-2 control-label">替换关键词</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="configReplaceKeyword" placeholder="" ng-model="config.realConfig.replaceKeyword">
</div>
</div>
<div class="form-group">
<label for="configBlockKeyword" class="col-sm-2 control-label">拦截关键词</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="configBlockKeyword" placeholder="" ng-model="config.realConfig.blockKeyword">
</div>
</div>
<div class="form-group">
<label for="configIgnoreKeyword" class="col-sm-2 control-label">判断拦截时忽略关键词</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="configIgnoreKeyword" placeholder="" ng-model="config.realConfig.ignoreKeyword">
</div>
</div>
<div class="form-group">
<label for="configSocketInterval" class="col-sm-2 control-label">弹幕向客户端发送间隔</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="configSocketInterval" placeholder="" ng-model="config.realConfig.socketinterval">
</div>
</div>
<div class="form-group">
<label for="configSocketSingle" class="col-sm-2 control-label">每次弹幕发送数量</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="configSocketSingle" placeholder="" ng-model="config.realConfig.socketsingle">
</div>
</div>
<div class="form-group">
<label for="configRoomLength" class="col-sm-2 control-label">弹幕堆积队列最大长度</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="configRoomLength" placeholder="" ng-model="config.realConfig.maxlength">
</div>
</div>
<div class="form-group">
<label for="configTextLength" class="col-sm-2 control-label">单条弹幕最大长度</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="configTextLength" placeholder="" ng-model="config.realConfig.textlength">
</div>
</div>
<div class="row">
<button class="col-sm-offset-5 btn btn-success" type="submit">更新配置</button>
</div>
</form>
</div>
</uib-accordion-group>
<uib-accordion-group heading="密码设置" is-open="accordion.openPassword">
<div ng-controller="PasswordCtrl" class="container">
<form class="form-horizontal" ng-submit="$submitPassword('advanced')">
<div class="form-group">
<label for="passwordAdvanced" class="col-sm-2 control-label">高级弹幕发送密码</label>
<div class="col-sm-8"><input type="password" class="form-control" id="passwordAdvanced" placeholder="" ng-model="config.advancedpassword"></div>
<div class="col-sm-1"><button class="col-sm-offset-5 btn btn-success" type="submit">更新密码</button></div>
</div>
</form>
<form class="form-horizontal" ng-submit="$submitPassword('manage')">
<div class="form-group">
<label for="passwordManage" class="col-sm-2 control-label">后台管理密码</label>
<div class="col-sm-8"><input type="password" class="form-control" id="passwordManage" placeholder="" ng-model="config.managepassword"></div>
<div class="col-sm-1"><button class="col-sm-offset-5 btn btn-success" type="submit">更新密码</button></div>
</div>
</form>
<form class="form-horizontal" ng-submit="$submitPassword('connect')">
<div class="form-group">
<label for="passwordConnect" class="col-sm-2 control-label">客户端连接密码</label>
<div class="col-sm-8"><input type="password" class="form-control" id="passwordConnect" placeholder="" ng-model="config.connectpassword"></div>
<div class="col-sm-1"><button class="col-sm-offset-5 btn btn-success" type="submit">更新密码</button></div>
</div>
</form>
</div>
</uib-accordion-group>
</uib-accordion>
</div>
</div>
</div>
<script src="/manage.js"></script>
</body>
</html>
================================================
FILE: src/libraries/http/view/realtime.html
================================================
<!DOCTYPE html>
<html lang="zh-cmn-CN" ng-app="danmu.realtime" ng-controller="MainCtrl">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="/static/angular/angular.min.js"></script>
<script src="/static/angular-ui-bootstrap/ui-bootstrap-tpls.js"></script>
<link href="/manage.css" rel="stylesheet">
<title>实时弹幕</title>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header"><a class="navbar-brand" href="#">实时弹幕</a></div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<uib-alert type="danger" ng-show="haveError">错误{{err.code}}:{{err.desc}};建议刷新页面。</uib-alert>
<div class="col-sm-12 col-md-12 main">
<uib-accordion close-others="false">
<uib-accordion-group heading="管理信息" is-open="true">
<div class="container">
<uib-alert type="danger" ng-show="!isLogin">务必先填入房间信息再进行管理。</uib-alert>
<uib-alert type="success" ng-show="isLogin">你选择了{{room}}房间</uib-alert>
<div class="list-group" ng-show="!isLogin">
<a href="#" class="list-group-item" ng-class="{active: room == roomS.id}" ng-repeat="roomS in roomList" ng-click="initRoom(roomS.id)">{{roomS.display}} ({{roomS.id}})</a>
</div>
<form ng-submit="enterRoom(password)" ng-show="!isLogin">
<div class="input-group">
<input type="password" class="form-control" placeholder="房间密码" ng-model="password">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">确认</button>
</span>
</div>
</form>
</div>
</uib-accordion-group>
<uib-accordion-group heading="实时弹幕" is-open="true">
<div class="container">
<div ng-repeat="danmu in danmus track by danmu.socketId" style="margin-top: 5px; margin-right: 5px; display: inline-block">
<div class="btn-group" uib-dropdown>
<button id="split-button-{{$index}}" type="button" class="btn btn-primary" ng-click="deleteDanmu($index, danmu.id, '')">{{danmu.text}}</button>
<button type="button" class="btn btn-primary" uib-dropdown-toggle>
<span class="caret"></span>
<span class="sr-only">Split button!</span>
</button>
<ul uib-dropdown-menu role="menu" aria-labelledby="split-button-{{$index}}">
<li role="menuitem" class="disabled"><a href="#">{{danmu.hash}}</a></li>
<li class="divider"></li>
<li role="menuitem"><a href="#" ng-click="deleteDanmu($index, danmu.id, '')">删</a></li>
<li role="menuitem"><a href="#" ng-click="deleteDanmu($index, danmu.id, danmu.hash)">封</a></li>
</ul>
</div>
</div>
</div>
</uib-accordion-group>
</uib-accordion>
</div>
</div>
</div>
<script>
var serverVersion = "<%=version%>";
</script>
<script src="realtime.js"></script>
<script src="/socket.io/socket.io.js"></script>
</body>
</html>
================================================
FILE: src/libraries/socket/index.js
================================================
const httpEvent = require('../../interfaces/Http')
const socketEvent = require('../../interfaces/Socket')
const danmuEvent = require('../../interfaces/Danmu')
const log = require('../../utilities/log')
const config = require('../../../config')
let inProcessRandomNumber = Math.random()
let io = null
module.exports = {
init: function (callback) {
// 删除弹幕
danmuEvent.removing.listen(data => {
Object.keys(data).forEach(room => {
io.to(room).emit('delete', data[room])
})
})
// 推送弹幕
danmuEvent.transfer.listen(data => {
io.to(data.room).emit('danmu', data)
})
// 当服务器创建后,绑定WebSocket
httpEvent.created.listen(app => {
io = require('socket.io')(app)
io.on('connection', socket => {
// 向客户端推送密码请求
socket.emit('init', 'Require Password.')
socket.on('password', data => {
let room = data.room
if (!config.rooms[room]) {
socket.emit('init', 'Room Not Found')
log.log(`${socket.id}试图加入未定义房间`)
return false
}
if (data.password !== config.rooms[room].connectpassword) {
socket.emit('init', 'Password error')
return false
}
if (!data.info) {
log.log('该版本弹幕客户端过老,请更新弹幕客户端。')
return false
}
log.log(`客户端 ${socket.id}(${data.info.version}) in ${socket.conn.remoteAddress} 连接于 ${room}`)
socket.join(room)
socket.emit('connected', {
version, // eslint-disable-line
randomNumber: inProcessRandomNumber // 用于给客户端检测服务器是重启还是断线
})
})
})
socketEvent.created.emit(io)
})
callback(null)
}
}
================================================
FILE: src/libraries/transfer/index.js
================================================
const configEvent = require('../../interfaces/Config')
const danmuEvent = require('../../interfaces/Danmu')
const filter = require('../../utilities/filter')
const log = require('../../utilities/log')
const utilities = require('../../utilities')
let danmuQueue = {}
let danmuKeys = []
const config = require('../../../config')
let danmuId = 0
module.exports = {
init: function (callback) {
callback(null)
}
}
// 更新配置
configEvent.updated.listen(data => {
clearAllTimeval()
initDanmuQueue()
startAllTimeval()
})
// 待推送弹幕
danmuEvent.get.listen(data => {
// 过老弹幕没有意义,直接从队列头出队列
while (danmuQueue[data.room].queue.length > config.rooms[data.room].maxlength) {
danmuQueue[data.room].queue.shift()
}
if (data.lifeTime === '') {
data.lifeTime = utilities.parseLifeTime(data)
}
log.log(`房间${data.room}得到弹幕(${data.hash}):${data.text}`)
danmuQueue[data.room].queue.push(data)
})
let initDanmuQueue = function () {
danmuQueue = {}
danmuKeys = Object.keys(config.rooms)
danmuKeys.forEach(room => {
danmuQueue[room] = {
queue: [],
timeval: null
}
}
)
}
let startAllTimeval = function () {
danmuKeys.forEach(room => {
if (config.rooms[room].permissions.send) {
danmuQueue[room].timeval = initTimeval(room)
log.log(`创建(${room})定时器 - ${config.websocket.interval} ms.`)
} else {
log.log(`${room} 房间弹幕已关闭,不创建定时器。`)
}
})
}
let clearAllTimeval = function () {
danmuKeys.forEach(room => {
log.log(`清理(${room})定时器`)
clearInterval(danmuQueue[room].timeval)
})
}
let initTimeval = function (room) {
return setInterval(() => {
// 定时推送
let ret = []
if (danmuQueue[room].queue.length === 0) return
while (ret.length < config.websocket.singlesize && danmuQueue[room].queue.length > 0) {
let object = danmuQueue[room].queue.pop()
// 只在传输时才需要进行替换
object.text = filter(room).replaceKeyword(object.text)
object.id = ++danmuId
ret.push(object)
}
log.log(`推送${ret.length}条弹幕到${room},剩余${danmuQueue[room].queue.length}条。`)
danmuEvent.transfer.emit({
room: room,
data: ret
})
}, config.websocket.interval)
}
================================================
FILE: src/utilities/filter.js
================================================
const configEvent = require('../interfaces/Config')
const _ = require('ramda')
const config = require('../../config')
const cachedFilters = {}
/**
* 检测用户是否被封禁
*/
const checkUserIsBlocked = _.curry((blockUsers, hash) => {
return (blockUsers.indexOf(hash)) > -1
})
/**
* 检测文字是否和谐
*/
const validateText = _.curry((ignoreRegEx, checkRegEx, str) => {
checkRegEx.lastIndex = 0
const testStr = str.replace(ignoreRegEx, '')
return !checkRegEx.test(testStr)
})
/**
* 替换关键字
*/
const replaceKeyword = _.curry((regex, str) => str.replace(regex, '***'))
function initialize (roomName, forceUpdate) {
if (cachedFilters[roomName] && !forceUpdate) {
return cachedFilters[roomName]
}
const room = config.rooms[roomName]
if (typeof room.keyword.block === 'string') {
console.error('请升级你的配置,将所有字符串类型关键词转换为正则类型关键词。')
throw new Error('Init RegExp Error')
}
const ret = {
checkUserIsBlocked: checkUserIsBlocked(room.blockusers),
validateText: validateText(room.keyword.ignore)(room.keyword.block),
replaceKeyword: replaceKeyword(room.keyword.replacement)
}
cachedFilters[roomName] = null // Release Memory
cachedFilters[roomName] = ret
return ret
};
// 正则缓存更新
configEvent.updated.listen(() => {
Object.keys(config.rooms).forEach(room => initialize(room, true))
})
module.exports = initialize
================================================
FILE: src/utilities/index.js
================================================
'use strict'
const crypto = require('crypto')
const config = require('../../config')
/**
* 生成MD5
*/
const md5 = text => crypto.createHash('md5').update(text).digest('hex')
/**
* 计算Hash
*/
const getHash = (ip, userAgent, hashCode) => md5(`IP=${ip}\nUA=${userAgent}\nHC=${hashCode}`)
/**
* 获取一个可读的当前时间
*/
const getTime = () => {
const d = new Date()
return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + '.' + d.getMilliseconds()
}
/**
* 把配置格式化为数组
*/
const buildConfigToArray = room => {
return {
replaceKeyword: config.rooms[room].keyword.replacement.source,
blockKeyword: config.rooms[room].keyword.block.source,
ignoreKeyword: config.rooms[room].keyword.ignore.source,
maxlength: config.rooms[room].maxlength,
textlength: config.rooms[room].textlength,
socketinterval: config.websocket.interval,
socketsingle: config.websocket.singlesize
}
}
/**
* 计算弹幕生存时间
*/
const parseLifeTime = data => {
const imageMatches = data.text.match(config.rooms[data.room].image.regex)
const imageLength = imageMatches === null ? 0 : imageMatches.length
return (Math.trunc(data.text.length / 10)) * 240 + config.rooms[data.room].image.lifetime * imageLength
}
// 全局工具
module.exports = {
md5,
getHash,
getTime,
buildConfigToArray,
parseLifeTime
}
================================================
FILE: src/utilities/log.js
================================================
const utilities = require('./')
module.exports = {
log: function (text) {
console.log('[' + utilities.getTime() + '] ' + text)
}
}
gitextract_ll54n030/
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── Dockerfile
├── README.md
├── app.js
├── config.js
├── docker/
│ ├── create_db.sh
│ ├── create_mysql_admin_user.sh
│ └── run.sh
├── jsconfig.json
├── package.json
└── src/
├── controllers/
│ ├── DanmuController.js
│ └── UserController.js
├── extensions/
│ ├── audit/
│ │ ├── Audit.js
│ │ ├── audit.html
│ │ └── index.js
│ ├── autoban/
│ │ └── index.js
│ ├── index.js
│ ├── livesync/
│ │ ├── get.py
│ │ └── index.js
│ └── weibo/
│ ├── index.js
│ └── login.html
├── interfaces/
│ ├── Base.js
│ ├── Config.js
│ ├── Danmu.js
│ ├── Http.js
│ └── Socket.js
├── libraries/
│ ├── cache/
│ │ └── index.js
│ ├── database/
│ │ ├── csv.js
│ │ ├── index.js
│ │ ├── mongo.js
│ │ ├── mysql.js
│ │ └── none.js
│ ├── http/
│ │ ├── index.js
│ │ ├── res/
│ │ │ ├── manage.css
│ │ │ ├── manage.js
│ │ │ └── realtime.js
│ │ ├── route/
│ │ │ ├── index.js
│ │ │ ├── manage.js
│ │ │ ├── manageBlock.js
│ │ │ ├── manageConfig.js
│ │ │ ├── manageDanmu.js
│ │ │ ├── manageRoom.js
│ │ │ ├── manageSearch.js
│ │ │ ├── post.js
│ │ │ └── realtime.js
│ │ └── view/
│ │ ├── index.html
│ │ ├── manage.html
│ │ └── realtime.html
│ ├── socket/
│ │ └── index.js
│ └── transfer/
│ └── index.js
└── utilities/
├── filter.js
├── index.js
└── log.js
SYMBOL INDEX (40 symbols across 13 files)
FILE: src/extensions/audit/Audit.js
class Audit (line 6) | class Audit extends Base {
method className (line 7) | static get className () {
method passed (line 10) | static get passed () {
FILE: src/extensions/audit/index.js
function Audit (line 17) | function Audit () {
FILE: src/extensions/livesync/get.py
function out (line 4) | def out(p):
function danmu_fn (line 12) | def danmu_fn(msg):
function gift_fn (line 16) | def gift_fn(msg):
function other_fn (line 20) | def other_fn(msg):
FILE: src/interfaces/Base.js
class Base (line 4) | class Base {
method className (line 5) | static get className () {
method register (line 8) | static register (className, fieldName) {
method registerCallbackable (line 19) | static registerCallbackable (className, fieldName) {
FILE: src/interfaces/Config.js
class Config (line 6) | class Config extends Base {
method className (line 7) | static get className () {
method blockUser (line 10) | static get blockUser () {
method updated (line 13) | static get updated () {
method unblockUser (line 16) | static get unblockUser () {
FILE: src/interfaces/Danmu.js
class Danmu (line 11) | class Danmu extends Base {
method className (line 12) | static get className () {
method addSingle (line 18) | static get addSingle () {
method ban (line 24) | static get ban () {
method httpReceived (line 30) | static get httpReceived () {
method get (line 36) | static get get () {
method removeSingle (line 44) | static get removeSingle () {
method removing (line 50) | static get removing () {
method search (line 56) | static get search () {
method transfer (line 62) | static get transfer () {
FILE: src/interfaces/Http.js
class Http (line 7) | class Http extends Base {
method className (line 8) | static get className () {
method created (line 11) | static get created () {
method beforeRoute (line 14) | static get beforeRoute () {
FILE: src/interfaces/Socket.js
class Socket (line 6) | class Socket extends Base {
method className (line 7) | static get className () {
method created (line 10) | static get created () {
FILE: src/libraries/database/csv.js
function formatContent (line 7) | function formatContent (content) {
FILE: src/libraries/database/mongo.js
function pregQuote (line 78) | function pregQuote (str, delimiter) {
FILE: src/libraries/database/mysql.js
constant SECONDS_IN_DAY (line 1) | const SECONDS_IN_DAY = 24 * 60 * 60 * 1000
FILE: src/libraries/http/route/index.js
function getRoom (line 8) | function getRoom (hostname) {
function renderIndex (line 12) | function renderIndex (advanced, room) {
FILE: src/utilities/filter.js
function initialize (line 25) | function initialize (roomName, forceUpdate) {
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (135K chars).
[
{
"path": ".eslintignore",
"chars": 21,
"preview": "/http/res/*\nconfig.js"
},
{
"path": ".eslintrc",
"chars": 222,
"preview": "{\n \"env\": {\n \"es6\": true,\n \"browser\": false,\n \"node\": true\n },\n \"parser\": \"babel-eslint\",\n \"parserOptions\":"
},
{
"path": ".gitattributes",
"chars": 378,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs diff=csharp\n\n# St"
},
{
"path": ".gitignore",
"chars": 1256,
"preview": ".vs\n.idea\n.vscode\n*.csv\n\n# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generate"
},
{
"path": "Dockerfile",
"chars": 1045,
"preview": "FROM node:latest\nMAINTAINER zsx <zsx@zsxsoft.com>\nENV APP /usr/src/app\n\n## ----------------------------\n## MariaDB"
},
{
"path": "README.md",
"chars": 4721,
"preview": "danmu-server\n================\n[](https://david-dm.org/zsxsof"
},
{
"path": "app.js",
"chars": 1906,
"preview": "const os = require('os')\nconst async = require('async')\nconst fs = require('fs')\nconst path = require('path')\nconst conf"
},
{
"path": "config.js",
"chars": 7746,
"preview": "module.exports = {\n\t\"rooms\": {\n\t\t\"default\": {\n\t\t\t\"hostname\": [\"test.zsxsoft.com\", \"danmu.zsxsoft.com\"], \n\t\t\t\"cdn\": false"
},
{
"path": "docker/create_db.sh",
"chars": 217,
"preview": "#!/bin/bash\n\nif [[ $# -eq 0 ]]; then\n\techo \"Usage: $0 <db_name>\"\n\texit 1\nfi\n\necho \"=> Creating database $1\"\nRET=1\nwhile "
},
{
"path": "docker/create_mysql_admin_user.sh",
"chars": 840,
"preview": "#!/bin/bash\n\n\n\necho \"=> Creating database danmu in MySQL\"\n/docker/create_db.sh danmu\n\n\nPASS=${MYSQL_PASS:-$(pwgen -s 12 "
},
{
"path": "docker/run.sh",
"chars": 1081,
"preview": "#!/bin/bash\n\n## Part MySQL\nif [ ! -n \"$MYSQL_PORT_3306_TCP_PORT\" ]; then\n VOLUME_HOME=\"/var/lib/mysql\"\n MYSQL_INST"
},
{
"path": "jsconfig.json",
"chars": 278,
"preview": "{\n\t// See http://go.microsoft.com/fwlink/?LinkId=759670\n\t// for the documentation about the jsconfig.json format\n\t\"compi"
},
{
"path": "package.json",
"chars": 1525,
"preview": "{\n \"name\": \"danmu-server\",\n \"version\": \"1.1.0\",\n \"license\": \"MIT\",\n \"scripts\": {\n \"start\": \"node app.js\"\n },\n \""
},
{
"path": "src/controllers/DanmuController.js",
"chars": 1750,
"preview": "const filter = require('../utilities/filter')\nconst log = require('../utilities/log')\nconst danmuEvent = require('../int"
},
{
"path": "src/controllers/UserController.js",
"chars": 719,
"preview": "const configEvent = require('../interfaces/Config')\nconst log = require('../utilities/log')\nlet config = require('../../"
},
{
"path": "src/extensions/audit/Audit.js",
"chars": 336,
"preview": "const Base = require('../../interfaces/Base')\nconst className = 'audit'\nconst _ = require('ramda')\nconst register = _.cu"
},
{
"path": "src/extensions/audit/audit.html",
"chars": 7292,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-Hans-cn\" ng-app=\"danmu.audit\" ng-controller=\"MainCtrl\">\n\n<head>\n <meta charset=\"utf-8\""
},
{
"path": "src/extensions/audit/index.js",
"chars": 2696,
"preview": "// / <reference path=\"../../../typings/main.d.ts\" />\n\n'use strict'\nconst fs = require('fs')\nconst path = require('path')"
},
{
"path": "src/extensions/autoban/index.js",
"chars": 1295,
"preview": "// / <reference path=\"../../../typings/main.d.ts\" />\n\n'use strict'\nconst configEvent = require('../../interfaces/Config'"
},
{
"path": "src/extensions/index.js",
"chars": 378,
"preview": "// / <reference path=\"../../typings/main.d.ts\" />\n'use strict'\nconst path = require('path')\nconst log = require('../util"
},
{
"path": "src/extensions/livesync/get.py",
"chars": 447,
"preview": "import time, sys, json\nfrom danmu import DanMuClient\n\ndef out(p):\n sys.stdout.write(json.dumps(p) + \"\\n\")\n sys.std"
},
{
"path": "src/extensions/livesync/index.js",
"chars": 1573,
"preview": "'use strict'\nconst cp = require('child_process')\nconst path = require('path')\nconst danmuEvent = require('../../interfac"
},
{
"path": "src/extensions/weibo/index.js",
"chars": 4340,
"preview": "// / <reference path=\"../../../../typings/main.d.ts\" />\n\n'use strict'\nconst Passport = require('passport')\nconst Passpor"
},
{
"path": "src/extensions/weibo/login.html",
"chars": 1808,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-cn\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"I"
},
{
"path": "src/interfaces/Base.js",
"chars": 1150,
"preview": "const events = require('events')\nconst eventEmitter = new events.EventEmitter()\nconst callbackObject = {}\nclass Base {\n "
},
{
"path": "src/interfaces/Config.js",
"chars": 492,
"preview": "const Base = require('./Base')\nconst className = 'config'\nconst updated = Base.register(className, 'updated')\nconst bloc"
},
{
"path": "src/interfaces/Danmu.js",
"chars": 1323,
"preview": "const Base = require('./Base')\nconst className = 'danmu'\nconst addSingle = Base.registerCallbackable(className, 'addSing"
},
{
"path": "src/interfaces/Http.js",
"chars": 423,
"preview": "const Base = require('./Base')\nconst className = 'http'\nconst _ = require('ramda')\nconst register = _.curry(Base.registe"
},
{
"path": "src/interfaces/Socket.js",
"chars": 328,
"preview": "const Base = require('./Base')\nconst className = 'socket'\nconst _ = require('ramda')\nconst register = _.curry(Base.regis"
},
{
"path": "src/libraries/cache/index.js",
"chars": 986,
"preview": "'use strict'\nlet cache = null\nconst config = require('../../../config')\nconst memoryCacheMap = new Map()\n\nmodule.exports"
},
{
"path": "src/libraries/database/csv.js",
"chars": 1083,
"preview": "const fs = require('fs')\nconst path = require('path')\nconst danmuEvent = require('../../interfaces/Danmu')\nconst log = r"
},
{
"path": "src/libraries/database/index.js",
"chars": 211,
"preview": "const config = require('../../../config')\n\nmodule.exports = {\n init: function (callback) {\n require('./' + config.da"
},
{
"path": "src/libraries/database/mongo.js",
"chars": 2682,
"preview": "const mongodb = require('mongodb')\nconst danmuEvent = require('../../interfaces/Danmu')\nconst log = require('../../utili"
},
{
"path": "src/libraries/database/mysql.js",
"chars": 4149,
"preview": "const SECONDS_IN_DAY = 24 * 60 * 60 * 1000\nconst mysql = require('mysql')\nconst async = require('async')\nconst danmuEven"
},
{
"path": "src/libraries/database/none.js",
"chars": 138,
"preview": "const log = require('../../utilities/log')\n\nmodule.exports = {\n init: function (callback) {\n log.log('无数据库')\n cal"
},
{
"path": "src/libraries/http/index.js",
"chars": 1671,
"preview": "const fs = require('fs')\nconst express = require('express')\nconst errorHandler = require('errorhandler')\nconst path = re"
},
{
"path": "src/libraries/http/res/manage.css",
"chars": 1556,
"preview": "/*\n * Base structure\n */\n\n/* Move down content because we have a fixed navbar that is 50px tall */\nbody {\n padding-top:"
},
{
"path": "src/libraries/http/res/manage.js",
"chars": 6602,
"preview": "var manage = (function () { // eslint-disable-line\n var manage = angular.module('danmu.manage', [ // eslint-disable-lin"
},
{
"path": "src/libraries/http/res/realtime.js",
"chars": 3885,
"preview": "// / <reference path=\"../../../typings/main.d.ts\" />\nvar realtime = (function () { // eslint-disable-line\n var realtime"
},
{
"path": "src/libraries/http/route/index.js",
"chars": 1183,
"preview": "const config = require('../../../../config')\n\nmodule.exports = function (app) {\n// Initialize Hostname Map\n const hostn"
},
{
"path": "src/libraries/http/route/manage.js",
"chars": 611,
"preview": "const config = require('../../../../config')\n\nmodule.exports = function (app) {\n app.get('/manage', function (req, res)"
},
{
"path": "src/libraries/http/route/manageBlock.js",
"chars": 700,
"preview": "const configEvent = require('../../../interfaces/Config')\nconst log = require('../../../utilities/log')\nconst config = r"
},
{
"path": "src/libraries/http/route/manageConfig.js",
"chars": 2429,
"preview": "const configEvent = require('../../../interfaces/Config')\nconst utilities = require('../../../utilities')\nconst log = re"
},
{
"path": "src/libraries/http/route/manageDanmu.js",
"chars": 436,
"preview": "const danmuEvent = require('../../../interfaces/Danmu')\n\nmodule.exports = function (app) {\n app.post('/manage/danmu/del"
},
{
"path": "src/libraries/http/route/manageRoom.js",
"chars": 408,
"preview": "const log = require('../../../utilities/log')\nconst config = require('../../../../config')\n\nmodule.exports = function (a"
},
{
"path": "src/libraries/http/route/manageSearch.js",
"chars": 535,
"preview": "const log = require('../../../utilities/log')\nconst danmuEvent = require('../../../interfaces/Danmu')\n\nmodule.exports = "
},
{
"path": "src/libraries/http/route/post.js",
"chars": 1088,
"preview": "const utilities = require('../../../utilities')\nconst danmuEvent = require('../../../interfaces/Danmu')\nconst config = r"
},
{
"path": "src/libraries/http/route/realtime.js",
"chars": 206,
"preview": "const config = require('../../../../config')\n\nmodule.exports = function (app) {\n app.get('/realtime', (req, res) => {\n "
},
{
"path": "src/libraries/http/view/index.html",
"chars": 9146,
"preview": "<!DOCTYPE html>\n<!--懒得写CSS和一堆JS了,麻烦~-->\n<html lang=\"zh-cmn-CN\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv="
},
{
"path": "src/libraries/http/view/manage.html",
"chars": 15741,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-cmn-CN\" ng-app=\"danmu.manage\" ng-controller=\"MainCtrl\">\n\n<head>\n <meta charset=\"utf-8\""
},
{
"path": "src/libraries/http/view/realtime.html",
"chars": 4116,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-cmn-CN\" ng-app=\"danmu.realtime\" ng-controller=\"MainCtrl\">\n\n<head>\n <meta charset=\"utf-"
},
{
"path": "src/libraries/socket/index.js",
"chars": 1713,
"preview": "const httpEvent = require('../../interfaces/Http')\nconst socketEvent = require('../../interfaces/Socket')\nconst danmuEve"
},
{
"path": "src/libraries/transfer/index.js",
"chars": 2167,
"preview": "const configEvent = require('../../interfaces/Config')\nconst danmuEvent = require('../../interfaces/Danmu')\nconst filter"
},
{
"path": "src/utilities/filter.js",
"chars": 1333,
"preview": "const configEvent = require('../interfaces/Config')\nconst _ = require('ramda')\nconst config = require('../../config')\n\nc"
},
{
"path": "src/utilities/index.js",
"chars": 1374,
"preview": "'use strict'\nconst crypto = require('crypto')\nconst config = require('../../config')\n/**\n * 生成MD5\n */\nconst md5 = text ="
},
{
"path": "src/utilities/log.js",
"chars": 139,
"preview": "const utilities = require('./')\nmodule.exports = {\n log: function (text) {\n console.log('[' + utilities.getTime() + "
}
]
About this extraction
This page contains the full source code of the zsxsoft/danmu-server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (111.2 KB), approximately 33.4k tokens, and a symbol index with 40 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.