快捷弹幕

B站直播间发送快捷弹幕

  1. // ==UserScript==
  2. // @name 快捷弹幕
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.5.5
  5. // @description B站直播间发送快捷弹幕
  6. // @author mianju
  7. // @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
  8. // @require https://cdn.staticfile.org/axios/0.27.2/axios.min.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. function main() {
  14. initLib()
  15. initCss()
  16. waitForLoaded(() => {
  17. initUi()
  18. })
  19. }
  20.  
  21. function initLib() {
  22. let scriptElement = document.createElement('script')
  23. scriptElement.src = 'https://cdn.staticfile.org/vue/2.6.9/vue.min.js'
  24. document.head.appendChild(scriptElement)
  25.  
  26. let linkElement = document.createElement('link')
  27. linkElement.rel = 'stylesheet'
  28. linkElement.href = 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/theme-chalk/index.css'
  29. document.head.appendChild(linkElement)
  30.  
  31. scriptElement = document.createElement('script')
  32. scriptElement.src = 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/index.js'
  33. document.head.appendChild(scriptElement)
  34. }
  35.  
  36. function initCss() {
  37. let css = `
  38. .el-select .el-input {
  39. width: 80px;
  40. }
  41. .input-with-select .el-input-group__prepend {
  42. background-color: #fff;
  43. }
  44. `
  45. let styleElement = document.createElement('style')
  46. styleElement.innerText = css
  47. document.head.appendChild(styleElement)
  48. }
  49.  
  50. function waitForLoaded(callback, timeout = 10 * 1000) {
  51. let startTime = new Date()
  52. function poll() {
  53. if (isLoaded()) {
  54. callback()
  55. return
  56. }
  57.  
  58. if (new Date() - startTime > timeout) {
  59. return
  60. }
  61. setTimeout(poll, 1000)
  62. }
  63. poll()
  64. }
  65.  
  66. function isLoaded() {
  67. if (window.ELEMENT === undefined) {
  68. return false
  69. }
  70. if (document.querySelector('#control-panel-ctnr-box') === null) {
  71. return false
  72. }
  73. return true
  74. }
  75. function initUi() {
  76. var father = document.getElementsByClassName('icon-left-part')[0];
  77. let quickDanmakuElement = document.createElement('div');
  78. father.appendChild(quickDanmakuElement);
  79.  
  80. new Vue({
  81. el: quickDanmakuElement,
  82. template: `
  83. <span>
  84. <template style="width: 200px" >
  85. <el-select v-model="value" placeholder="选择" @change="sendDanmaku" style="width: 80px;height: 24px" size="mini">
  86. <el-option-group
  87. v-for="group in options"
  88. :key="group.label"
  89. :label="group.label">
  90. <el-option
  91. v-for="danmaku in group.danmakus"
  92. :key="danmaku.label"
  93. :label="danmaku.label"
  94. :value="danmaku.label">
  95. </el-option>
  96. </el-option-group>
  97. </el-select>
  98. <el-button icon="el-icon-message" size="mini" circle @click="sendDanmaku(value)"></el-button>
  99. <el-popover
  100. placement="bottom"
  101. title="快捷弹幕设置"
  102. width="300px"
  103. trigger="click"
  104. >
  105. <span class="demonstration">添加: </span>
  106. <el-tooltip class="item" effect="dark" content="添加表情包教程:https://bbs.nga.cn/read.php?pid=594434351&opt=128" placement="top">
  107. <el-input placeholder="请输入内容" v-model="danmaku4add" class="input-with-select" maxlength="20" size="mini" style="width: 300px">
  108. <el-select v-model="select" slot="prepend" placeholder="请选择">
  109. <el-option label="当前" value="cur"></el-option>
  110. <el-option label="所有" value="all"></el-option>
  111. </el-select>
  112. <el-button slot="append" icon="el-icon-check" @click="addDanmaku" size="mini"></el-button>
  113. </el-input>
  114. </el-tooltip>
  115. <div class="block" style="margin-top: 10px">
  116. <span class="demonstration">删除: </span>
  117. <el-cascader
  118. size="mini"
  119. v-model="danmakus4del"
  120. :options="options"
  121. :props="{ expandTrigger: 'hover','children': 'danmakus','multiple': 'true','emitPath':'false' }"
  122. ></el-cascader>
  123. <el-button type="danger" icon="el-icon-delete" size="mini" @click="delDanmakus"></el-button>
  124. </div>
  125. <el-button slot="reference" icon="el-icon-setting" size="mini" circle></el-button>
  126. <div style="margin-top: 10px">
  127. 自动发送:
  128. <el-radio-group v-model="freq" size="mini">
  129. <el-radio-button label="高频"></el-radio-button>
  130. <el-radio-button label="正常"></el-radio-button>
  131. <el-radio-button label="低频"></el-radio-button>
  132. </el-radio-group>
  133. <el-switch
  134. size="mini"
  135. v-model="value4switch"
  136. :disabled="flag4switch"
  137. @change="autoSend">
  138. </el-switch>
  139. </div>
  140. </el-popover>
  141. </template>
  142. </span>
  143. `,
  144. data: {
  145. options: [
  146. {
  147. label: '所有直播间',
  148. value: 'all',
  149. danmakus: []
  150. },
  151. {
  152. label: '当前直播间',
  153. value: 'cur',
  154. danmakus: []
  155. }
  156. ],
  157. value: '',
  158. danmaku4add: '',
  159. select: 'cur',
  160. danmakus4del: [],
  161. roomId: 0,
  162. allRoomDanmakus: [],
  163. curRoomDanmakus: [],
  164. key4all: 'allRoomDanmukus',
  165. key4cur: '',
  166. value4switch: false,
  167. interval: null,
  168. freq: '正常',
  169. freqopts: {'高频':[30,6000],'正常':[15,12000],'低频':[10,18000]},
  170. chatInput: null
  171. },
  172. methods: {
  173. "delDanmakus": function () {
  174. function del(val,danmakus) {
  175. let index = danmakus.indexOf(val);
  176. danmakus.splice(index,1);
  177. };
  178. for (let i=0;i<this.danmakus4del.length;i++){
  179. let danmaku = this.danmakus4del[i];
  180. if (danmaku[0] == "cur") {
  181. del(danmaku[1],this.curRoomDanmakus);
  182. } else {
  183. del(danmaku[1],this.allRoomDanmakus);
  184. }
  185. }
  186. localStorage.setItem(this.key4cur,JSON.stringify(this.curRoomDanmakus));
  187. localStorage.setItem(this.key4all,JSON.stringify(this.allRoomDanmakus));
  188. this.danmakus4del = [];
  189. this.$message.success("删除成功");
  190. this.handleDanmakus();
  191. },
  192. "getRoomId": function () {
  193. if (window.__NEPTUNE_IS_MY_WAIFU__) {
  194. this.roomId = window.__NEPTUNE_IS_MY_WAIFU__.roomInfoRes.data.room_info.room_id;
  195. } else {
  196. let url = document.URL;
  197. var re = /\/\d+/.exec(url);
  198. this.roomId = re[0].substr(1);
  199. }
  200. },
  201. "getDanmakus": function (key) {
  202. if (localStorage.getItem(key)){
  203. return JSON.parse(localStorage.getItem(key));
  204. }
  205. return [];
  206. },
  207. "handleDanmakus": function () {
  208. function handle(danmakus4opt,danmakus4room){
  209. danmakus4opt.splice(0);
  210. for (let i=0;i<danmakus4room.length;i++) {
  211. danmakus4opt.push({label:danmakus4room[i],value:danmakus4room[i]});
  212. }
  213. }
  214. handle(this.options[0].danmakus,this.allRoomDanmakus);
  215. handle(this.options[1].danmakus,this.curRoomDanmakus);
  216. },
  217. "addDanmaku": function () {
  218. function add(key,danmakus,danmaku){
  219. danmakus.push(danmaku);
  220. localStorage.setItem(key,JSON.stringify(danmakus));
  221. };
  222. if (this.select == "cur") {
  223. add(this.key4cur,this.curRoomDanmakus,this.danmaku4add);
  224. } else {
  225. add(this.key4all,this.allRoomDanmakus,this.danmaku4add);
  226. }
  227. this.danmaku4add = '';
  228. this.$message.success("添加成功");
  229. this.handleDanmakus();
  230. },
  231. "sendDanmaku": function (danmaku4send) {
  232. var patt = /(room|official)(_\d+){1,2}/
  233. var jct = getCookie('bili_jct');
  234. var date = new Date();
  235. var data = new FormData();
  236. data.append('bubble',0);
  237. data.append('color',16777215);
  238. data.append('fontsize',25);
  239. data.append('mode',1);
  240. data.append('msg',danmaku4send);
  241. data.append('rnd',parseInt(date.getTime()/1000));
  242. data.append('roomid',this.roomId);
  243. data.append('csrf',jct);
  244. data.append('csrf_token',jct);
  245. if (patt.test(danmaku4send)) {
  246. data.set('msg',patt.exec(danmaku4send)[0])
  247. data.append('dm_type',1);
  248. }
  249. let apiClient = axios.create({
  250. baseURL: 'https://api.live.bilibili.com',
  251. withCredentials: true
  252. });
  253. apiClient.post('/msg/send',data).then((res)=>{
  254. if (res.data.code == 0) {
  255. switch (res.data.msg) {
  256. case '':
  257. this.$message.success('发送成功 - ' + danmaku4send);
  258. break;
  259. /*case 'f':
  260. this.$message.error('发送失败 - 包含B站屏蔽词: ' + danmaku4send);
  261. break;
  262. case 'k':
  263. this.$message.error('发送失败 - 包含直播间屏蔽词: ' + danmaku4send);
  264. break;*/
  265. case 'same restriction':
  266. this.$message.error('发送失败,该弹幕已被限制,请选择其它弹幕!');
  267. break;
  268. case 'max limit exceeded':
  269. this.$message.error('发送失败,弹幕池达到上限!');
  270. break;
  271. default:
  272. this.$message.error('发送失败 - ' + res.data.message);
  273. }
  274. } else {
  275. this.$message.error('发送失败 - ' + res.data.message);
  276. }
  277. }).catch(()=>{
  278. this.$message.error('发送失败 - ' + danmaku4send);
  279. });
  280. },
  281. "autoSend": function(flag) {
  282. if (flag) {
  283. this.$message.info('自动发送已开启');
  284. let count = 0;
  285. let _this = this
  286. let opt = this.freqopts[this.freq]
  287. this.interval = setInterval(
  288. ()=>{
  289. count++;
  290. if (count == opt[0]) {
  291. _this.value4switch = false;
  292. clearInterval(_this.interval)
  293. _this.$message.info('自动发送已关闭');
  294. }
  295. _this.sendDanmaku(_this.value);
  296. },opt[1]
  297. );
  298. } else {
  299. this.$message.info('自动发送已关闭');
  300. clearInterval(this.interval);
  301. }
  302. },
  303. "sendListener": function() {
  304. var _this = this;
  305. function keydown(event) {
  306. if (event.keyCode == 13) {
  307. if (_this.chatInput.chatInput != '') {
  308. _this.sendDanmaku(_this.chatInput.chatInput);
  309. }
  310. setTimeout(_this.chatInput.clearInput, 10);
  311. }
  312. };
  313. this.chatInput.onKeyDown = keydown;
  314. }
  315. } ,
  316. created: function () {
  317. this.getRoomId();
  318. this.key4cur = this.roomId + '-danmukus';
  319. this.curRoomDanmakus = this.getDanmakus(this.key4cur);
  320. this.allRoomDanmakus = this.getDanmakus(this.key4all);
  321. this.handleDanmakus();
  322. this.chatInput = document.querySelector('#control-panel-ctnr-box').__vue__;
  323. this.sendListener();
  324. },
  325. computed: {
  326. flag4switch: function() {
  327. return (this.value == '')
  328. }
  329. }
  330. });
  331. }
  332. function getCookie(cname){
  333. var name = cname + "=";
  334. var ca = document.cookie.split(';');
  335. for(var i=0; i<ca.length; i++) {
  336. var c = ca[i].trim();
  337. if (c.indexOf(name)==0) { return c.substring(name.length,c.length); }
  338. }
  339. return "";
  340. }
  341. main()
  342. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址