├── .gitignore ├── README.md ├── config.yaml ├── config.ts ├── .github └── workflows │ └── yuu.yml ├── LICENSE ├── discord_webhook.ts └── main.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yuu 今天直播了嗎? 2 | 3 | ![Discord Bot](https://user-images.githubusercontent.com/20385106/122675337-82734e00-d20b-11eb-95c8-9fbd3730fbd7.png) 4 | 5 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | discord: 2 | enable: true 3 | content: 4 | description: Yuu正在直播 5 | color: 16744192 6 | button: 7 | watch: 8 | emoji: 9 | id: null 10 | name: ❤ 11 | -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'https://deno.land/std@0.98.0/encoding/_yaml/parse.ts' 2 | 3 | export async function ReadConfigFile(filename: string) { 4 | try { 5 | const yamlString = await Deno.readTextFile(filename) 6 | return parse(yamlString) 7 | } catch (err) { 8 | err.message = `${err.message}` 9 | throw err 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/yuu.yml: -------------------------------------------------------------------------------- 1 | name: yuu 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | schedule: 7 | - cron: '0 */1 * * *' 8 | watch: 9 | types: started 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - uses: denoland/setup-deno@v1 16 | with: 17 | deno-version: v1.x 18 | - env: 19 | DISCORD_WEBHHOK_URL: ${{ secrets.DISCORD_WEBHHOK_URL }} 20 | BILIBILI_MID: ${{ secrets.BILIBILI_MID }} 21 | run: deno run -A main.ts 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gizmo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /discord_webhook.ts: -------------------------------------------------------------------------------- 1 | const webhookUrl = Deno.env.get('DISCORD_WEBHHOK_URL') 2 | 3 | export const ComponentTypeActionRow = 1 4 | export const ComponentTypeButton = 2 5 | export const ButtonStylePrimary = 1 6 | export const ButtonStyleSecondary = 2 7 | export const ButtonStyleSuccess = 3 8 | export const ButtonStyleDanger = 4 9 | export const ButtonStyleLink = 5 10 | 11 | interface WebhookRequest { 12 | content?: string 13 | username?: string 14 | avatar_url?: string 15 | tts?: boolean 16 | embeds?: Embed[] 17 | components?: Component[] 18 | } 19 | 20 | interface Embed { 21 | title?: string 22 | description?: string 23 | url?: string 24 | timestamp?: number 25 | color?: number 26 | footer?: EmbedFooter 27 | image?: EmbedImage 28 | thumbnail?: EmbedThumbnail 29 | video?: EmbedVideo 30 | provider?: EmbedProvider 31 | author?: EmbedAuthor 32 | fields?: EmbedField[] 33 | } 34 | interface EmbedImage { 35 | url?: string 36 | proxy_url?: string 37 | height?: string 38 | width?: string 39 | } 40 | interface EmbedThumbnail extends EmbedImage {} 41 | interface EmbedFooter { 42 | text: string 43 | icon_url?: string 44 | proxy_icon_url?: string 45 | } 46 | interface EmbedVideo extends EmbedImage {} 47 | interface EmbedProvider { 48 | name?: string 49 | url?: string 50 | } 51 | interface EmbedAuthor { 52 | name?: string 53 | url?: string 54 | icon_url?: string 55 | proxy_icon_url?: string 56 | } 57 | interface EmbedField { 58 | name: string 59 | value: string 60 | inline?: boolean 61 | } 62 | 63 | interface Component { 64 | type: number 65 | style?: number 66 | label?: string 67 | emoji?: Emoji 68 | custom_id?: string 69 | url?: string 70 | disabled?: boolean 71 | components?: Component[] 72 | } 73 | interface Emoji { 74 | id: string | null 75 | name?: string 76 | animated?: boolean 77 | } 78 | 79 | export function Webhook(info: WebhookRequest) { 80 | if (!webhookUrl) return console.log("Webhook URL未找到, 停止執行") 81 | fetch(webhookUrl, { 82 | method: 'POST', 83 | body: JSON.stringify(info), 84 | headers: { 85 | 'content-type': 'application/json', 86 | }, 87 | }) 88 | .then((resp) => { 89 | if (resp.ok) return 90 | console.log(resp.statusText) 91 | resp.json().then((body) => { 92 | console.error(body) 93 | }) 94 | }) 95 | .catch((err) => { 96 | console.error(err) 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { ReadConfigFile } from './config.ts' 2 | import * as discord from './discord_webhook.ts' 3 | import Random from 'https://deno.land/x/random@v1.1.2/Random.js' 4 | 5 | let mid = Deno.env.get('BILIBILI_MID') 6 | mid = mid ? mid : '539700' // 👍 7 | const bilibiliApiUrl = 'https://api.bilibili.com/x/space/acc/info?mid=' + mid 8 | const config: any = await ReadConfigFile('./config.yaml') 9 | const random = new Random() 10 | 11 | fetch(bilibiliApiUrl) 12 | .then((resp) => resp.json()) 13 | .then((body) => { 14 | if (body.code != 0) { 15 | console.error(body.message) 16 | return 17 | } 18 | checkStreaming(body.data) 19 | }) 20 | .catch((err) => { 21 | console.error(err) 22 | }) 23 | 24 | interface LiveRoom { 25 | liveStatus: number 26 | url: string 27 | title: string 28 | cover: string 29 | } 30 | 31 | async function checkStreaming(data: any) { 32 | const info = data.live_room as LiveRoom 33 | if (!info.liveStatus) return console.log('未直播') 34 | 35 | const thumbnail = 36 | 'https://i0.hdslb.com/bfs/feed-admin/d11f0d19337292fc64c1f985131bae759219ddfc.png' 37 | const footer = config.discord.hitokoto 38 | ? await hitokoto() 39 | : footerList[random.int(0, 10)] 40 | if (config.discord.enable) { 41 | discord.Webhook({ 42 | content: config.discord.content, 43 | avatar_url: data.face ? data.face : undefined, 44 | embeds: [ 45 | { 46 | title: info.title, 47 | description: config.discord.description, 48 | color: config.discord.color ? config.discord.color : 16744192, 49 | url: info.url, 50 | image: { 51 | url: info.cover, 52 | }, 53 | thumbnail: { 54 | url: thumbnail ? thumbnail : undefined, 55 | }, 56 | footer: { 57 | text: footer, 58 | }, 59 | }, 60 | ], 61 | components: [ 62 | { 63 | type: discord.ComponentTypeActionRow, 64 | components: [ 65 | { 66 | type: discord.ComponentTypeButton, 67 | label: 'WATCH NOW', 68 | style: discord.ButtonStyleLink, 69 | url: info.url, 70 | emoji: { 71 | id: config.discord.button.watch.emoji.id, 72 | name: config.discord.button.watch.emoji.name, 73 | }, 74 | }, 75 | { 76 | type: discord.ComponentTypeButton, 77 | label: 'Source', 78 | style: discord.ButtonStyleLink, 79 | url: 'https://github.com/GizmoOAO/yuu-stream-notice-bot', 80 | emoji: { 81 | id: '803143081016819713', 82 | }, 83 | }, 84 | ], 85 | }, 86 | ], 87 | }) 88 | } 89 | } 90 | 91 | function hitokoto(): Promise { 92 | return new Promise((resolve, reject) => { 93 | fetch('https://international.v1.hitokoto.cn/?c=a&charset=utf8') 94 | .then((resp) => resp.json()) 95 | .then((body) => { 96 | let text = body.hitokoto 97 | text += ' ──' + body.from 98 | resolve(text) 99 | }) 100 | .catch((err) => { 101 | reject(err) 102 | }) 103 | }) 104 | } 105 | 106 | const footerList: string[] = [ 107 | '「人类的悲欢并不相通,我只是觉得他们吵闹。」——鲁迅', 108 | '我的吼聲之中必定存在意義。', 109 | '「慚愧、罪惡感、害怕。我們都感受得到。將你的悔悟集結起來,並盡力地消滅它們吧。讓你的敵人感受到你身上的重擔。」——愛達-1', 110 | '「比自己,比梦想更重要的东西永远都存在着...」——钢之炼金术师', 111 | '「愿你和重要的人,在来日重逢。」——艾拉', 112 | '「以盐水作配菜,糖水做主食,就有种奢侈的感觉呢。」——笨蛋测验召唤兽', 113 | '「猫是可爱的,狼是很帅的。就是说,孤独又可爱又帅。」——我的青春恋爱物语果然有问题', 114 | '「所谓这个世界的真理,就是由爱所记录下的一切。」——夏彦深见', 115 | '「献给徐徐多多的祭日。」——村上春树', 116 | '「宁教我负天下人,休教天下人负我。」——曹操', 117 | ] 118 | --------------------------------------------------------------------------------