Giter Site home page Giter Site logo

3shain / comen Goto Github PK

View Code? Open in Web Editor NEW
912.0 13.0 83.0 4.99 MB

📺直播用弹幕栏【原bilichat】

Home Page: https://comen.app

License: MIT License

Dockerfile 0.05% JavaScript 0.64% TypeScript 52.56% HTML 7.73% CSS 38.88% SCSS 0.14%
bilibili youtube livechat acfun bilichat obs overlay

comen's Introduction

Comen

此项目已长期没有维护

MIT License PRs Welcome 原项目【bilichat】请查看bilichat分支

Comen是一个主要用于在网络直播中向观众展示当前直播间实时评论流的工具。

主要特性

  • 多平台支持:默认提供了Acfun,Bilibili的接入实现。合理的抽象低耦合设计使得新平台的接入也很容易。
  • 高可靠性:不只是一个爱好项目。原项目已运行两年的时间,积累了各种对用户体验和异常处理的优化。
  • 兼容Youtube评论栏样式,只需修改URL就能迁移。
  • 高度自定义 (计划中,TBD)
  • 作为原项目的延续,提供用户几乎无感知的兼容。

贡献代码

本项目主要技术栈为前端Angular+后端Nestjs,即完全使用TypeScript(JavaScript)实现。

本项目使用到了nx workspace作为管理代码的工具。本项目是一个monorepo的结构,所以任何本项目开源的部分都能在此仓库找到。

代码结构

- apps
    - core    --- 前端项目
    - node-backend    --- 后端的node(cli)包装
- libs
    - backend-core    --- 后端的可嵌入式实现
    - common          --- 常用与扩展代码
    - gamma           --- 兼容Youtube的评论栏渲染器(Angular组件库)
    - isomorphic-danmaku            --- 同构弹幕,实现了acfun、bilibili的ws连接
    - isomprphic-danmaku-server     --- 同构弹幕,服务端only部分

调试运行 拉取仓库后,首先安装项目依赖

npm install

然后分别打开前端与后端的dev-server

nx serve core
nx serve node-backend

现在可以对代码进行更改了。任何有关代码更改都会触发自动刷新。

我想贡献一些新功能/我想修复一些问题

本项目的主要目标是将一类实时/非实时评论流在任何兼容html5的平台上渲染出来。在抽象设计上,本项目可分成三大部分:评论源(source),过滤器(filter)和渲染器(renderer)

  • 我想添加一个新的平台实现

参考 apps/core/src/app/sources 下的文件。你需要实现MessageSource接口,其中connect方法就是返回一个生产Message的可观察对象。

  • 我想修改过滤相关的代码

参考 apps/core/src/app/common/filter.ts 。过滤器是通过rxjs的pipe实现的,强烈建议熟练掌握rxjs,或仅在现有的代码上修改。原理上有点类似于后端的middleware,但rxjs的可观察对象概念要复杂一些,涉及到错误处理,subscription和complete的处理。 另外你还可能需要参考各类Message对象的定义(common包的message.ts)。他们只是单纯的数据对象,不涉及到类也没有成员函数。这些类型定义可能并不稳定。Message由source生成,经过filter后最终流向renderer。但并不是所有类型(以type字段区分的类型)都会被renderer处理。filter也有可能在中间对类型对象数据进行转换/变更。

  • 我想修改渲染相关的代码

所有渲染相关代码都在libs/gamma包内。

comen's People

Contributors

3shain avatar dependabot[bot] avatar lonelyion avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

comen's Issues

换个图标

不再直接使用bilibili的icon,避免法律问题

comen在线版本链接字段缺失

comen在线版本,bilibili方面无法正常运作
对比老版本的自动转化链接发现缺少&bilichat=字段,补上后可正常运行
不知是特性还是尚未处理的bug
(老版本的自动转化链接如删去&bilichat=字段也会导致无法正常运作)

groupSimilar可能存在误伤

现在的groupSimilar用indexOf,只要包含之前发过的弹幕就算是Similar,这样似乎会误伤不少弹幕。
比如一个人发了
另一个人之后发了一大堆东西 草
那么后面那个弹幕就会算作前面那个弹幕的重复

如图:可以看到测试 233hit了7次
屏幕快照 2019-05-04 上午3 40 36
屏幕快照 2019-05-04 上午3 40 48

实际可能发生的情况:
屏幕快照 2019-05-04 上午3 26 56
屏幕快照 2019-05-04 上午3 27 05

能否增加本地comment转发

指主播本地输入转发,不再需要进直播间发送评论(不过vtb们好像不需要这个鸡肋功能(救救聋哑小主播们(((

timestamp样式缺失导致无法控制是否显示timestamp

在仍是bilichat的时候,bilichat的timestamp的display的样式通过 var(--yt-live-chat-item-timestamp-display,inline); 来控制是否显示timestamp,此时 yt-live-chat-item-timestamp-display 的默认值是 none ,如果不使用css覆盖的话将会默认不显示,而comen的timestamp样式同样通过 var(--yt-live-chat-item-timestamp-display,inline); 来控制是否显示timestamp,但yt-live-chat-item-timestamp-display样式缺失,导致display的默认样式变为 inline ,同时样式生成器 chat v2.0 style 改变是否显示时间戳的本质是增加和删除 display: inline !important; 样式来实现的,并非直接修改本身的样式,而是通过覆盖样式来实现的,并且在非勾选/反勾选状态仅仅是删除 display: inline !important; ,而不是修改为 display: none !important; ,这共同导致了这个错误

acfun直播支持

  • 协议逆向

  • 后端接口

  • 前端ws消息处理

  • 前端礼物信息处理

  • 新的图标

i18n多语言

随着工具的推广,多语言功能也显得必要了。

头像可在前端直接使用B站api获取

首先
GET https://api.bilibili.com/x/space/app/index?mid={USERID} 请求时注意去掉Referer
然后解析json在data->info->face就能拿到头像url
似乎没发现什么限制

[Feature/Bug]当指定wordFilter参数时无法解除默认的关键词屏蔽

请问"自行修改"指的是需要修改代码实现解除吗?
如果是,那能否加入当指定wordFilter参数为空时移除默认关键词屏蔽的功能呢?
因为有的时候关键词会误伤,比如如下弹幕:

这个梗是老生常谈了(触发关键词“梗”)
别刷新,阿B炸了(触发关键词“别刷”)
感谢主播送出的问号风暴(触发关键词“风暴”)

这样会造成反馈不佳的误解,能否解除或者采用更好的机制呢?

Hello-修改及功能升级意见

现在已有完善的弹幕及礼物信息接收功能,请问能否实现TwiTch中的礼物及订阅展示,开放两个端口,其中一个为弹幕展示,另一个为礼物及订阅特效展示,订阅特效及礼物可使用GIF或HTML5模板来进行更换或定义。详细可以邮件交流:[email protected]

授权协议缺失

不知道本项目是在哪个授权协议下开源出来的呢?

  • 补充 LICENSE 文件

检测脚本+封禁用户

前一个做不到,但后一个还是可以的(指屏蔽指定uid

@simon300000 你的弹幕记录有UID吗,昨天MIO的B限有很多脚本哥,我要去杀一波

api 服务器挂掉时不会显示弹幕

发现如果往 api 服务器的 http 请求失败了流就断了,弹幕就不显示了
尝试修复了一下,不大清楚RxJS用法,就不直接PR了→_→
https://github.com/simon300000/BiliChat/tree/api-catchError
https://github.com/3Shain/BiliChat/compare/master...simon300000:api-catchError?expand=1

读了Rxjs的Document,似乎这样是ok的(测试也是ok的
可是会有不知道哪来的48
image

原本的状态,弹幕不会显示:
image

不知道为什么api服务器断掉的瞬间会出现个48。。。
突然关掉api服务器效果图:
image

b站协议升级

前几天还能用,今天发现protover升级到3,原来的协议其实还能连接和heartbeat,但是出了一个新的ver 3的封包,用的是BrotliDecode。我本来想直接用Comen的包来做弹幕解析的,但是发现了这个问题,所以搬了一下b站的最新代码,并且使用brotliDecode算法可以正常解包。

附上我的部分代码:

const WebSocket = require("ws");
const _ = require("lodash");

const { BrotliDecode } = require("./brotli");
const { WS_CONSTANTS, WS_BINARY_HEADER_LIST } = require("./constants");
const { getRoomIdAndToken } = require("./server");

function toUint8Array(bufferObject) {
  var arrayBuffer = new ArrayBuffer(bufferObject.length);
  var typedArray = new Uint8Array(arrayBuffer);
  for (var i = 0; i < bufferObject.length; ++i) {
    typedArray[i] = bufferObject[i];
  }

  return typedArray;
}

function connectDanmu(roomId, token) {
  const ws = new WebSocket("wss://tx-gz-live-comet-02.chat.bilibili.com/sub", {
    origin: "https://live.bilibili.com",
  });

  ws.on("message", (payload) => {
    console.log(payload);
    const data = decodeData(payload);
    console.log(data);
    console.log(data.body);
  });

  ws.on("open", () => {
    ws.send(
      packageObject(7, {
        uid: 0,
        roomid: Number(roomId),
        protover: 3,
        platform: "web",
        type: 2,
        key: token,
      })
    );

    // heartbeat packets
    console.log("heartbeat");
    ws.send(packageHeartbeat());
    const heartbeat = setInterval(() => {
      console.log("heartbeat");
      ws.send(packageHeartbeat());
    }, 30 * 1000);
  });

  ws.on("close", (code, reason) => {
    console.log("close", code, reason);
  });

  ws.on("error", (err) => {
    console.error(err);
  });
}

function packageHeartbeat() {
  const body = new TextEncoder().encode({});
  return packageBinary(2, body);
}

function packageBinary(type, body) {
  // console.log("packageBinary", type, body);
  const tmp = new Uint8Array(16 + body.byteLength);
  const headDataView = new DataView(tmp.buffer);
  headDataView.setInt32(0, tmp.byteLength);
  headDataView.setInt16(4, 16);
  headDataView.setInt16(6, 1);
  headDataView.setInt32(8, type); // verify
  headDataView.setInt32(12, 1);
  tmp.set(body, 16);
  // console.log(tmp);
  return tmp;
}

function packageObject(type, bufferObj) {
  // console.log("packageObject", type, bufferObj);
  return packageBinary(
    type,
    new TextEncoder().encode(JSON.stringify(bufferObj))
  );
}

function decodeData(buffer) {
  const arr = toUint8Array(buffer);
  const dataView = new DataView(arr.buffer);

  const result = {
    body: [],
  };

  result.packetLen = dataView.getInt32(WS_CONSTANTS.WS_PACKAGE_OFFSET);
  WS_BINARY_HEADER_LIST.forEach((header) => {
    if (header.bytes === 4) {
      result[header.key] = dataView.getInt32(header.offset);
    }

    if (header.bytes === 2) {
      result[header.key] = dataView.getInt16(header.offset);
    }
  });

  console.log(result);

  if (
    !result.op ||
    (WS_CONSTANTS.WS_OP_MESSAGE !== result.op &&
      result.op !== WS_CONSTANTS.WS_OP_CONNECT_SUCCESS)
  ) {
    result.op &&
      WS_CONSTANTS.WS_OP_HEARTBEAT_REPLY === result.op &&
      (result.body = {
        count: dataView.getInt32(WS_CONSTANTS.WS_PACKAGE_HEADER_TOTAL_LENGTH),
      });
  } else {
    console.log("parsing non heartbeats");
    for (
      let cursor = WS_CONSTANTS.WS_PACKAGE_OFFSET,
        end = result.packetLen,
        start = "",
        payload = "";
      cursor < buffer.byteLength;
      cursor += end
    ) {
      (end = dataView.getInt32(cursor)),
        (start = dataView.getInt16(cursor + WS_CONSTANTS.WS_HEADER_OFFSET));
      try {
        if (result.ver === WS_CONSTANTS.WS_BODY_PROTOCOL_VERSION_NORMAL) {
          console.log(cursor, start, end);
          var normalDecoded = new TextDecoder().decode(
            buffer.slice(cursor + start, cursor + end)
          );
          payload =
            0 !== normalDecoded.length ? JSON.parse(normalDecoded) : null;
        } else if (
          result.ver === WS_CONSTANTS.WS_BODY_PROTOCOL_VERSION_BROTLI
        ) {
          var slice = buffer.slice(cursor + start, cursor + end),
            brotliDecoded = BrotliDecode(toUint8Array(slice));
          result.body = decodeData(Buffer.from(brotliDecoded)).body;
        }
        payload && result.body.push(payload);
      } catch (err) {
        console.error(
          "decode body error:",
          new Uint8Array(buffer),
          result,
          err
        );
      }
    }
  }
  return result;
}

getRoomIdAndToken(process.env.ROOM_ID || "1367262").then(
  ({ roomId, token }) => {
    console.log(roomId, token);
    connectDanmu(roomId, token);
  }
);

关于弹幕不自动置顶的问题

配置条目已经从100改成了10,服务已经重新启动,但是网页上面显示的条目还是多于10条

我这边出现的问题就是如果弹幕太多了的话,后面的弹幕不会出现在最前方,可能一直在下面不会被顶上来

image


我现在只有当弹幕满屏的时候再把滚动条的位置拉倒最下面,这样子我OBS展示的时候才能展示最新的弹幕,但是每次如果都这样子手动调整的话,有点麻烦

不知道有没有解释得清楚我现在的情况,就是是不是我哪里配置错误了没有恒定显示最新的10条,又或者项目是不是已经提供了弹幕倒序的功能配置,希望能得到你的解答,谢谢

前端显示头像

看到了#1
这里提出一个不一样的解决办法
后端通过 https://api.bilibili.com/x/space/acc/info?mid=,返回: data->face
前端head里面加 <meta name="referrer" content="no-referrer">

再给 https://api.bilibili.com/x/space/acc/info 套上一个lru-cache,应该能解决服务器的一部分负载问题

贴片广告

要不要考虑一下用我的 bili-api 233333

能否添加仅显示礼物信息的选项

因为自家VTuber做B限直播时想要感谢礼物,单因为弹幕姬流动太快无法回看,网页端的礼物界面太过复杂不好教学。所以想问问能不能添加一个仅显示礼物信息的功能,像油管的驻留上位chat,或者简单的文字信息也行。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.