Skip to content

Commit 0613882

Browse files
committed
Init project
0 parents  commit 0613882

32 files changed

+18843
-0
lines changed

.dockerignore

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Nuxt dev/build outputs
2+
app/.output
3+
app/.data
4+
app/.nuxt
5+
app/.nitro
6+
app/.cache
7+
app/dist
8+
9+
# Node dependencies
10+
app/node_modules
11+
12+
# Logs
13+
app/logs
14+
app/*.log
15+
16+
# Misc
17+
app/.DS_Store
18+
app/.fleet
19+
app/.idea
20+
21+
# Local env files
22+
app/.env
23+
app/.env.*
24+
!app/.env.example
25+
26+
.vscode
27+
.devcontainer.json
28+
docker-compose.*.yml

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.vscode
2+
.devcontainer.json
3+
docker-compose.*.yml

Dockerfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM node:18-alpine as builder
2+
3+
ARG NODE_ENV=production
4+
ENV NODE_ENV=${NODE_ENV}
5+
6+
WORKDIR /home/node/app
7+
COPY app /home/node/app
8+
RUN npm ci --include=dev && npm run build
9+
10+
FROM node:18-alpine
11+
12+
COPY --from=builder /home/node/app/.output /home/node/app
13+
WORKDIR /home/node/app
14+
15+
ENV NITRO_PORT 80
16+
17+
CMD ["node", "server/index.mjs"]
18+
19+
EXPOSE 80

Dockerfile.dev

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM node:18-alpine
2+
3+
RUN apk add --no-cache git openssh

app/.gitignore

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Nuxt dev/build outputs
2+
.output
3+
.data
4+
.nuxt
5+
.nitro
6+
.cache
7+
dist
8+
9+
# Node dependencies
10+
node_modules
11+
12+
# Logs
13+
logs
14+
*.log
15+
16+
# Misc
17+
.DS_Store
18+
.fleet
19+
.idea
20+
21+
# Local env files
22+
.env
23+
.env.*
24+
!.env.example

app/README.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# [ChaosRat - 방송 채팅 오버레이](https://chaosrat.update.sh/)
2+
3+
ChaosRat은 [OBS Studio](https://obsproject.com/)와 같은 방송 소프트웨어에 사용할 수 있는 채팅 오버레이입니다.
4+
5+
## 주요 기능
6+
7+
- 다중 플랫폼 지원
8+
- 현재 [치지직(Chzzk)](https://chzzk.naver.com/), [트위치(Twitch)](https://www.twitch.tv/), [유튜브 라이브(Youtube Live)](https://www.youtube.com/live) 채팅을 지원합니다.
9+
- 이모티콘 지원
10+
- 각 플랫폼별 채팅에 포함된 이모티콘을 표시합니다.
11+
- 스티커 지원
12+
- 플랫폼과 별개로 채팅에 포함된 스티커(디시콘)를 표시합니다.
13+
- 테마 지원
14+
- 사용자가 원하는 테마를 선택할 수 있습니다.
15+
- 개발자 친화적
16+
- Nuxt3를 사용하여 프론트엔드와 백엔드를 동시에 개발할 수 있습니다.
17+
- 플랫폼 연결, 스티커, 테마 등을 모듈화하여 쉽게 추가할 수 있습니다.
18+
- [도커(Docker)](https://www.docker.com/)를 사용해 빠르게 개발 환경을 구축하고 배포할 수 있습니다.
19+
20+
아래 기능은 개발 예정이며, 현재는 지원하지 않습니다.
21+
22+
- 특정 키워드를 포함한 메시지 필터링
23+
- 특정 유저의 메시지 필터링
24+
25+
아래 기능은 지원 예정이 없습니다. 추후 요청이 있을 경우 추가될 수 있습니다.
26+
27+
- 후원 메시지 표시
28+
- 유저 뱃지 표시
29+
30+
## 사용 방법
31+
32+
[ChaosRat](https://chaosrat.update.sh/)에 접속하여 간단한 설정을 통해 채팅 오버레이 URL을 생성할 수 있습니다.
33+
34+
생성한 URL을 OBS Studio의 브라우저 소스에 입력하여 사용하시면 됩니다.
35+
36+
## 개발
37+
38+
직접 서버를 호스팅하거나 개발하고 싶다면 다음과 같이 실행할 수 있습니다.
39+
40+
이 저장소를 클론한 후 다음 명령어를 실행하여 서버를 시작할 수 있습니다.
41+
기본 주소는 `http://localhost:3000`입니다.
42+
43+
```bash
44+
docker-compose up
45+
```
46+
47+
## License
48+
49+
MIT License

app/assets/css/main.css

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Load css framework */
2+
@import "chota";
3+
4+
/* Load fonts */
5+
@import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css";
6+
@font-face {
7+
font-family: "ONE-Mobile-POP";
8+
src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/[email protected]/ONE-Mobile-POP.woff")
9+
format("woff");
10+
font-weight: normal;
11+
font-style: normal;
12+
}
13+
14+
html {
15+
-webkit-font-smoothing: antialiased;
16+
}

app/components/ChatOverlay.vue

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<template>
2+
<component :is="chatListComponent" :chatItems="sortedChatItems"></component>
3+
</template>
4+
5+
<script setup lang="ts">
6+
import { DefaultChatList, ColorfulChatList } from "#components";
7+
import { encodeFormatString } from "~/lib/utils";
8+
import type { ChatItem } from "~/lib/interfaces";
9+
10+
const props = defineProps<{
11+
theme: string | null;
12+
maxChatSize: number | null;
13+
chzzkChannelId: string | null;
14+
twitchChannel: string | null;
15+
youtubeHandle: string | null;
16+
isUseOpenDcconSelector: boolean | null;
17+
}>();
18+
const chatListComponent = computed(() => {
19+
switch (props.theme) {
20+
case "colorful":
21+
return ColorfulChatList;
22+
case "default":
23+
default:
24+
return DefaultChatList;
25+
}
26+
});
27+
28+
const maxChatSize = computed(() => {
29+
if (props.maxChatSize === null) {
30+
return 100;
31+
}
32+
return props.maxChatSize;
33+
});
34+
35+
const { chatItems: chzzkChatItems } = useChzzk(
36+
props.chzzkChannelId,
37+
maxChatSize
38+
);
39+
40+
const { chatItems: twitchChatItems } = useTwitch(
41+
props.twitchChannel,
42+
maxChatSize
43+
);
44+
45+
const { chatItems: youtubeLiveChatItems } = useYoutubeLive(
46+
props.youtubeHandle,
47+
maxChatSize
48+
);
49+
50+
const { stickerItems } = useOpenDcconSelector(
51+
props.isUseOpenDcconSelector ? props.twitchChannel : null
52+
);
53+
54+
function handleStickers(chat: ChatItem) {
55+
const stickers: { [key: string]: string } = {};
56+
for (const stickerItem of stickerItems.value) {
57+
if (chat.message.includes(`~${stickerItem.id}`)) {
58+
stickers[`~${stickerItem.id}`] = stickerItem.url;
59+
}
60+
}
61+
return stickers;
62+
}
63+
64+
const sortedChatItems = computed(() => {
65+
return [
66+
...chzzkChatItems.value,
67+
...twitchChatItems.value,
68+
...youtubeLiveChatItems.value,
69+
]
70+
.sort((a, b) => a.timestamp - b.timestamp)
71+
.slice(-maxChatSize.value)
72+
.map((chat) => {
73+
const stickers = handleStickers(chat);
74+
75+
const newChat = { ...chat, extra: { ...chat.extra, stickers } };
76+
77+
const encodeTargets = {
78+
...newChat.extra.emojis,
79+
...newChat.extra.stickers,
80+
};
81+
const { message: newMessage, targets: newTargets } = encodeFormatString(
82+
newChat.message,
83+
Object.keys(encodeTargets)
84+
);
85+
86+
const newEmojis: { [key: string]: string } = {};
87+
if (newChat.extra.emojis) {
88+
const oldEmojis = newChat.extra.emojis;
89+
Object.keys(oldEmojis).forEach((key) => {
90+
if (key in newTargets) {
91+
newEmojis[newTargets[key]] = oldEmojis[key];
92+
}
93+
});
94+
}
95+
96+
const newStickers: { [key: string]: string } = {};
97+
if (newChat.extra.stickers) {
98+
const oldStickers = newChat.extra.stickers;
99+
Object.keys(oldStickers).forEach((key) => {
100+
if (key in newTargets) {
101+
newStickers[newTargets[key]] = oldStickers[key];
102+
}
103+
});
104+
}
105+
106+
return {
107+
...newChat,
108+
message: newMessage,
109+
extra: {
110+
...newChat.extra,
111+
emojis: newEmojis,
112+
stickers: newStickers,
113+
},
114+
};
115+
});
116+
});
117+
</script>

app/components/TextWithShadow.vue

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<template>
2+
<div class="text-with-shadow">
3+
<slot></slot>
4+
</div>
5+
</template>
6+
7+
<script setup lang="ts">
8+
const props = withDefaults(
9+
defineProps<{
10+
shadowColor?: string;
11+
shadowSize?: number;
12+
unit?: string;
13+
}>(),
14+
{
15+
shadowColor: "black",
16+
shadowSize: 0.1,
17+
unit: "rem",
18+
}
19+
);
20+
21+
const straghtShadow = computed(() => `${props.shadowSize}${props.unit}`);
22+
const diagonalShadow = computed(
23+
() => `${Math.sqrt((props.shadowSize * props.shadowSize) / 2)}${props.unit}`
24+
);
25+
const negativeStraghtShadow = computed(() => `-${straghtShadow.value}`);
26+
const negativeDiagonalShadow = computed(() => `-${diagonalShadow.value}`);
27+
</script>
28+
29+
<style scoped>
30+
.text-with-shadow {
31+
text-shadow: v-bind(diagonalShadow) v-bind(diagonalShadow) v-bind(shadowColor),
32+
v-bind(straghtShadow) 0 v-bind(shadowColor),
33+
v-bind(diagonalShadow) v-bind(negativeDiagonalShadow) v-bind(shadowColor),
34+
0 v-bind(negativeStraghtShadow) v-bind(shadowColor),
35+
v-bind(negativeDiagonalShadow) v-bind(negativeDiagonalShadow)
36+
v-bind(shadowColor),
37+
v-bind(negativeStraghtShadow) 0 v-bind(shadowColor),
38+
v-bind(negativeDiagonalShadow) v-bind(diagonalShadow) v-bind(shadowColor),
39+
0 v-bind(straghtShadow) v-bind(shadowColor);
40+
}
41+
</style>

0 commit comments

Comments
 (0)