parent
9f1cf5d4c3
commit
7ccb1fc72b
150
README.md
150
README.md
|
@ -1,120 +1,114 @@
|
|||
# stapxs-qq-lite
|
||||
<p align="center">
|
||||
<a href="https://blog.stapxs.cn" target="blank">
|
||||
<img src="public/img/icons/icon.svg" alt="Logo" width="156" height="156">
|
||||
</a>
|
||||
<h2 align="center" style="font-weight: 600">Stapxs QQ Lite 2.0</h2>
|
||||
|
||||
简体中文 | [English (US)](README/en_US.md)
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[![old](README/to-old-ver.png)](https://github.com/Stapxs/Stapxs-QQ-Lite)
|
||||
<p align="center">
|
||||
一个兼容 oicq-http2 的非官方网页 QQ 客户端
|
||||
<br />
|
||||
<a href="https://stapxs.github.io/Stapxs-QQ-Lite-2.0/" target="blank"><strong>🌎 访问 DEMO</strong></a> |
|
||||
<a href="https://github.com/Stapxs/Stapxs-QQ-Lite-2.0/releases" target="blank"><strong>📦️ 下载程序</strong></a> |
|
||||
<a href="https://github.com/Stapxs/Stapxs-QQ-Lite-2.0/issues/new?assignees=Stapxs&labels=%3Abug%3A+%E9%94%99%E8%AF%AF&template=----.md&title=%5B%E9%94%99%E8%AF%AF%5D" target="blank"><strong>💬 反馈问题</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<strong>本网页应用仅供学习交流使用,请勿用于其他用途</strong><br>
|
||||
<strong>版权争议请提出 issue 协商</strong>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
![card](README/card.png)
|
||||
|
||||
<div align="center">
|
||||
<h1>Stapxs QQ Lite 2.0</h1>
|
||||
<h3>这是一个兼容 oicq-http2 的非官方网页版 QQ 客户端</h3>
|
||||
<h3>使用 Vue 重新编写的全新版本</h3><br>
|
||||
<strong>本网页应用仅供学习交流使用,请勿用于其他用途</strong><br>
|
||||
<strong>版权争议请提出 issue 协商</strong>
|
||||
</div>
|
||||
## ✨ 特性支持
|
||||
- ✅ 使用 Vue.js 全家桶开发,快乐前后端分离
|
||||
- 🎨 自适应布局,竖版也能使用
|
||||
- 🖥️ 支持 PWA(都有 Electron 了(小声))
|
||||
- 🌚 Light/Dark Mode 自动切换
|
||||
- 🍱 该有的都有(虽然比不过官方端)
|
||||
- 复杂消息显示、转发、回复、撤回
|
||||
- 群文件、群公告、群设置(一小部分)、精华消息
|
||||
- 图片、收藏表情、文件发送
|
||||
- 📦️ 支持多种 bot,我就是要用!
|
||||
- 🔥 水生火热但是更好看的 Electron 客户端
|
||||
- 🥚 彩蛋!来更多的彩蛋!
|
||||
- 🛠 更多特性开发中
|
||||
|
||||
## 快速使用
|
||||
### 访问应用 - GitHub Pages
|
||||
本仓库开启了 GitHub Pages, 所有向主分支提交的代码将会自动构建并发布。你可以直接访问 [这儿](https://stapxs.github.io/Stapxs-QQ-Lite-2.0) 来使用已经构建并部署的页面。
|
||||
## ♿️ 快速使用
|
||||
### > 运行服务
|
||||
Stapxs QQ Lite 需要一个 QQ Bot 后端提供服务,你可以参考 [📖 这个文档](https://github.com/Stapxs/Stapxs-QQ-Lite-2.0/wiki/%E8%BF%9E%E6%8E%A5-oicq2-http) 布置它。
|
||||
|
||||
### 运行服务 - oicq2-http
|
||||
Stapxs QQ Lite 需要一个 QQ Bot 来支持功能,目前 2.0 版本只支持定制的 oicq-http2 克隆版本,你可以在 [这个仓库](https://github.com/Tim-Paik/oicq-http2) 下载到它。
|
||||
### > 访问应用
|
||||
本仓库开启了 GitHub Pages, 所有向主分支提交的代码将会自动构建并发布。你可以直接访问 [🌎 这个页面](https://stapxs.github.io/Stapxs-QQ-Lite-2.0) 来使用已经构建并部署的页面。
|
||||
|
||||
在开始运行服务前,请确认你的设备已经安装了 `yarn` 以用于运行,在终端(cmd、powershell、zsh 等)运行 `yarn -v` 来确认安装。
|
||||
### > 安装应用
|
||||
除了直接使用本仓库的构建页面,你也可以下载使用 electron 打包的功能**稍稍**更丰富的客户端版本,访问 [📦️ 这儿](https://github.com/Stapxs/Stapxs-QQ-Lite-2.0/releases) 查看构建列表。
|
||||
|
||||
如果一切没问题,那么将终端转到 oicq 的目录下,开始运行 Bot 服务。
|
||||
## 💬 提醒和问题
|
||||
|
||||
- 更新依赖
|
||||
|
||||
运行 `yarn` 指令, yarn 将会为仓库进行初始化安装运行所需要的依赖:
|
||||
### > 关于不安全连接
|
||||
- 当使用 https 页面连接 ws 服务(反之相同)的情况下,连接将会失败;这是由于其中某一者是不安全的。在这种情况下,你可以选择将 ws 提升为 wss 或者将 https 降级为 http(不安全)来解决问题,此处不提供解决方案。>> [Stapxs-QQ-Lite#32](https://github.com/Stapxs/Stapxs-QQ-Lite/issues/32)
|
||||
|
||||
![yarn](README/yarn.png)
|
||||
|
||||
- 配置设置文件
|
||||
|
||||
在 oicq 的目录下有一个示例的设置文件 `config.json`,你可以修改它来配置相关设置,你需要保证使用 Websocket 的选项是打开的,并且为了安全性需要设置连接密钥:
|
||||
|
||||
![config](README/config.png)
|
||||
|
||||
完成修改后把它放置到用户目录下的 `.oicq` 文件夹里,Windows 用户应该为:`C://User/[name]/.oicq`,Linux 用户则是:`/home/[name]/.oicq`,如果文件夹不存在请自行创建。
|
||||
|
||||
- 启动服务
|
||||
|
||||
准备工作都完成了。下面回到终端,运行指令 `yarn start [QQ]` 启动服务:
|
||||
|
||||
![run](README/run.png)
|
||||
|
||||
如果没什么问题的话, oicq-http2 应该会像上面这样完成启动,就快完成了!
|
||||
|
||||
- 连接服务
|
||||
|
||||
回到应用页面,输入地址和密钥连接服务,大功告成!
|
||||
|
||||
![connect](README/connect.png)
|
||||
|
||||
|
||||
![connect](README/home.png)
|
||||
|
||||
## 其他提醒
|
||||
|
||||
### 关于不安全连接
|
||||
- 当使用 https 页面连接 ws 服务(反之相同)的情况下,连接将会失败;这是由于其中某一者是不安全的。在这种情况下,你可以选择将 ws 提升为 wss 或者将 https 降级为 http(不安全)来解决问题,此处不提供解决方案。[Stapxs-QQ-Lite#32](https://github.com/Stapxs/Stapxs-QQ-Lite/issues/32)
|
||||
|
||||
## 更多问题
|
||||
|
||||
### 我能使用其他 QQ Http Bot 吗
|
||||
### > 我能使用其他 QQ Http Bot 吗
|
||||
|
||||
- 如果它兼容 [OneBot 11 协议](<https://github.com/botuniverse/onebot-11>), 你可以尝试连接它, 但是由于消息体格式和接口扩展的差异,大部分情况下都不能完全正常使用。
|
||||
- 以下是已兼容的 Bot:
|
||||
已经兼容的 Bot 都写在了文档里,可以去 [这里](https://github.com/Stapxs/Stapxs-QQ-Lite-2.0/wiki) 查看。
|
||||
|
||||
- [oicq http v2](https://github.com/Tim-Paik/oicq-http2):完全兼容
|
||||
|
||||
- 以下是计划兼容的 Bot:
|
||||
|
||||
- [oicq http v1](https://github.com/takayama-lily/oicq/tree/master/http-api):完全兼容计划
|
||||
- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp):完全兼容计划
|
||||
|
||||
### 使用 Bot 是否有风险
|
||||
### > 使用 Bot 是否有风险
|
||||
|
||||
- 如果你使用的是 oicq-http, 可以查看此处了解 [使用风险](<https://github.com/takayama-lily/oicq/wiki/98.%E5%85%B3%E4%BA%8E%E8%B4%A6%E5%8F%B7%E5%86%BB%E7%BB%93%E5%92%8C%E9%A3%8E%E6%8E%A7>), 如果你尝试使用其他 QQ Bot (参见上一条问题), 请自行参考它的文档。
|
||||
|
||||
### 我遇到了问题
|
||||
### > 我遇到了问题
|
||||
|
||||
- 如果有什么奇奇怪怪的问题, 欢迎发起 [issue](<https://github.com/Stapxs/Stapxs-QQ-Lite/issues>) 询问! 如果有什么 BUG 和优化建议也可以哦!
|
||||
|
||||
## 构建应用
|
||||
Stapxs QQ Lite 2.0 是一个基于 Vue 的单页应用,这意味着如果你想自行部署服务需要进行构建,你可以参考下面的 bash 进行构建:
|
||||
## 📦️ 构建应用
|
||||
### > 构建 Web 页面
|
||||
Stapxs QQ Lite 2.0 是一个基于 Vue 的单页应用,这意味着如果你想自行部署到网页服务需要进行构建。
|
||||
|
||||
注意。在正式构建前,如果你的网站运作目录并不在根域名下,你需要修改(或增加)项目根目录下 `vue.config.js` 内导出的 `publicPath` 字段的值,它代表着最终你会运行在的目录,比如它在现在是 `/Stapxs-QQ-Lite-2.0/`;如果你本来就运行在根目录下,可以直接删去它。
|
||||
|
||||
如果你想使用 GitHub Action 自动构建本项目,可以直接参考本项目的自动构建配置,它在 [这儿](.github/workflows//vue-build.yml)。
|
||||
下面是构建 Vue 应用的命令,构建结果将最终输出在 `dist` 下:
|
||||
|
||||
``` bash
|
||||
# 安装依赖
|
||||
yarn install
|
||||
# 运行本地调试
|
||||
yarn serve
|
||||
# 代码检查和自动格式化
|
||||
yarn lint
|
||||
# 构建应用
|
||||
yarn build
|
||||
# 代码检查
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### > 构建 Electron 客户端
|
||||
在 `2.3.0` 版本后,Stapxs QQ Lite 2.0 支持构建为 Electron 应用并补充部分平台特性的功能,你也可以自行构建。
|
||||
|
||||
下面是构建 Electron 应用的命令,构建结果将最终输出在 `dist_electron/out` 下:
|
||||
|
||||
``` bash
|
||||
# electron 运行本地调试
|
||||
yarn electron:serve
|
||||
# electron 构建应用
|
||||
yarn electron:build
|
||||
```
|
||||
你可以补充平台选项来指定构建某个平台:
|
||||
|
||||
\* 你可以查看文档了解构建流程详情 [vue-cli 文档](https://cli.vuejs.org/config/)。
|
||||
```bash
|
||||
yarn electron:build --linux
|
||||
```
|
||||
### > Github Actions
|
||||
*为什么会有人对自动构建感兴趣,总之自动构建脚本我写了好久。感兴趣就自己去看好了(无端)。*
|
||||
|
||||
这是构建的一个例子:
|
||||
![auto-build](README/auto-build.png)
|
||||
|
||||
![build](README/build.png)
|
||||
## 🖼️ 截图
|
||||
![1](README/pics/1.png)
|
||||
![2](README/pics/2.png)
|
||||
![3](README/pics/4.png)
|
||||
![3](README/pics/3.png)
|
||||
|
||||
## 🎉 鸣谢
|
||||
感谢这些小伙伴们在开发和文本中提供的支持 ——
|
||||
|
||||
## 鸣谢
|
||||
<a href="https://github.com/Logic-Accepted"><img src="https://avatars.githubusercontent.com/u/36406453?s=48&v=4"></a>
|
||||
<a href="https://github.com/doodlehuang"><img src="https://avatars.githubusercontent.com/u/25525621?s=48&v=4"></a>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
After Width: | Height: | Size: 251 KiB |
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
|
@ -147,7 +147,7 @@
|
|||
</TransitionGroup>
|
||||
<Transition>
|
||||
<div class="pop-box" v-if="runtimeData.popBoxList.length > 0">
|
||||
<div class="pop-box-body ss-card"
|
||||
<div :class="'pop-box-body ss-card' + (runtimeData.popBoxList[0].full ? ' full' : '')"
|
||||
:style="'transform: translate(-50%, calc(-50% - ' + ((runtimeData.popBoxList.length > 3 ? 3 : runtimeData.popBoxList.length) * 10) + 'px))'">
|
||||
<header v-show="runtimeData.popBoxList[0].title != undefined">
|
||||
<div
|
||||
|
|
|
@ -345,7 +345,7 @@ textarea {
|
|||
.merge-pan.show > div:last-child {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
width: 50%;
|
||||
width: 450px;
|
||||
}
|
||||
.merge-pan > div:last-child > div:first-child {
|
||||
align-items: center;
|
||||
|
|
|
@ -596,10 +596,17 @@ html, body {
|
|||
margin-top: calc(50vh - 35px);
|
||||
transition: all .3s;
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
margin-left: 50%;
|
||||
width: 360px;
|
||||
min-width: 360px;
|
||||
z-index: 20;
|
||||
}
|
||||
.pop-box-body.full {
|
||||
height: calc(100vh - 120px);
|
||||
transform: unset !important;
|
||||
margin: 60px 20px 0 20px;
|
||||
width: calc(100% - 80px);
|
||||
}
|
||||
.pop-box-body > header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
|
|
@ -278,5 +278,7 @@
|
|||
"option_view_no_window": "去除窗口框架",
|
||||
"option_view_no_window_tip": "沉浸式没圆角体验(无端",
|
||||
"option_dev_restart": "重启应用",
|
||||
"option_dev_restart_tip": "99% 的特性都能通过重启解决!"
|
||||
"option_dev_restart_tip": "99% 的特性都能通过重启解决!",
|
||||
"btn_close": "关闭",
|
||||
"btn_open": "打开"
|
||||
}
|
|
@ -7,7 +7,7 @@ import path from 'path'
|
|||
|
||||
import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer'
|
||||
|
||||
import { Menu } from 'electron'
|
||||
import { Menu, session } from 'electron'
|
||||
import { app, protocol, BrowserWindow } from 'electron'
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
|
||||
|
@ -25,7 +25,7 @@ async function createWindow() {
|
|||
Menu.setApplicationMenu(null)
|
||||
regIpcListener()
|
||||
// 创建窗口
|
||||
let mainWindowState = windowStateKeeper({
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1200,
|
||||
defaultHeight: 800
|
||||
})
|
||||
|
@ -56,6 +56,15 @@ async function createWindow() {
|
|||
win.loadURL('app://./index.html')
|
||||
}
|
||||
console.log('应用加载完成')
|
||||
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
if(details.responseHeaders) {
|
||||
// 绕过 CSP 限制,X-Frame-Options 限制
|
||||
details.responseHeaders['content-security-policy'] = ['*']
|
||||
delete details.responseHeaders['x-frame-options']
|
||||
}
|
||||
callback({ cancel: false, responseHeaders: details.responseHeaders })
|
||||
})
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<div v-for="(item, index) in data.message" :class="View.isMsgInline(item.type) ? 'msg-inline' : ''" :key="data.message_id + '-m-' + index">
|
||||
<div v-if="item.type === undefined" ></div>
|
||||
<span v-else-if="isDebugMsg" class="msg-text">{{ item }}</span>
|
||||
<span v-else-if="item.type == 'text'" v-show="item.text !== ''" class="msg-text" v-html="parseText(item.text)"></span>
|
||||
<span v-else-if="item.type == 'text'" @click="textClick" v-show="item.text !== ''" class="msg-text" v-html="parseText(item.text)"></span>
|
||||
<img v-else-if="item.type == 'image'" :title="$t('chat_view_pic')" :alt="$t('chat_group_pic')" @load="scrollButtom" @error="imgLoadFail" @click="imgClick(data.message_id)" :class="imgStyle(data.message.length, index, item.asface)" :src="item.url">
|
||||
<img v-else-if="item.type == 'face'" :alt="item.text" class="msg-face" :src="require('./../assets/img/qq-face/' + item.id + '.gif')" :title="item.text">
|
||||
<span v-else-if="item.type == 'bface'" style="font-style: italic;opacity: 0.7;">[ {{ $t('chat_fun_menu_pic') }}:{{ item.text }} ]</span>
|
||||
|
@ -306,7 +306,7 @@ export default defineComponent({
|
|||
text = ViewFuns.parseText(text)
|
||||
// 链接判定
|
||||
const reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/gi //eslint-disable-line
|
||||
text = text.replaceAll(reg, '<a href="$&" target="_blank">$&</a>')
|
||||
text = text.replaceAll(reg, '<a href="" data-link="$&" onclick="return false">$&</a>')
|
||||
let linkList = text.match(reg)
|
||||
if (linkList !== null && !this.gotLink) {
|
||||
this.gotLink = true
|
||||
|
@ -444,6 +444,19 @@ export default defineComponent({
|
|||
fid: data.fid,
|
||||
md5: data.md5
|
||||
}, 'getVideoUrl_' + message_id)
|
||||
},
|
||||
|
||||
/**
|
||||
* 文本消息被点击
|
||||
* @param event 事件
|
||||
*/
|
||||
textClick(event: Event) {
|
||||
const target = event.target as HTMLElement
|
||||
if(target.dataset.link) {
|
||||
// 点击了链接
|
||||
const link = target.dataset.link
|
||||
Util.openLink(link)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -86,8 +86,8 @@ export default function regIpcListener() {
|
|||
ipcMain.on('sys:download', (evt, args) => {
|
||||
const downloadPath = args.downloadPath
|
||||
const fileName = args.fileName
|
||||
let ext = path.extname(fileName)
|
||||
let filters = [{ name: '全部文件', extensions: ['*'] }]
|
||||
const ext = path.extname(fileName)
|
||||
const filters = [{ name: '全部文件', extensions: ['*'] }]
|
||||
if (ext && ext !== '.' && ext != null) {
|
||||
const array = ext.match(/[a-zA-Z]+$/)
|
||||
if (array) {
|
||||
|
|
|
@ -37,6 +37,7 @@ export interface RunTimeDataElem {
|
|||
template?: any, // 填充模板(如果都有,优先填充 html)
|
||||
templateValue?: any, // 模板 props
|
||||
data?: any, // 模板的附加传参,只有这一个
|
||||
full?: boolean, // 是否填充整个页面
|
||||
button?: { // 按钮
|
||||
master?: boolean, // 是否高亮(主按钮)
|
||||
fun?: (value: any) => void, // 按钮回调
|
||||
|
|
|
@ -79,7 +79,30 @@ export function isExternal(path: string): boolean {
|
|||
* @param url 链接
|
||||
*/
|
||||
export function openLink(url: string) {
|
||||
window.open(url)
|
||||
// 判断是不是 Electron,是的话打开内嵌 iframe
|
||||
if(runtimeData.tags.isElectron) {
|
||||
const popInfo = {
|
||||
html: `<iframe src="${url}" style="width: calc(100% + 80px);border: none;margin: -40px -40px -20px -40px;height: calc(100vh - 145px);border-radius: 7px;"></iframe>`,
|
||||
full: true,
|
||||
button: [
|
||||
{
|
||||
text: app.config.globalProperties.$t('btn_open'),
|
||||
fun: () => {
|
||||
window.open(url)
|
||||
runtimeData.popBoxList.shift()
|
||||
}
|
||||
},
|
||||
{
|
||||
text: app.config.globalProperties.$t('btn_close'),
|
||||
master: true,
|
||||
fun: () => { runtimeData.popBoxList.shift() }
|
||||
}
|
||||
]
|
||||
}
|
||||
runtimeData.popBoxList.push(popInfo)
|
||||
} else {
|
||||
window.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue