├── src ├── index.ts ├── providers │ ├── index.ts │ ├── Reddit │ │ └── index.ts │ ├── Twitter │ │ └── index.ts │ ├── GameDiscounts │ │ └── index.ts │ ├── Instagram │ │ └── index.ts │ ├── YouTube │ │ ├── types.ts │ │ └── index.ts │ └── Twitch │ │ ├── types.ts │ │ └── index.ts └── utils │ └── rssParser.ts ├── dist ├── src │ ├── index.d.ts │ ├── providers │ │ ├── index.d.ts │ │ ├── Twitch │ │ │ ├── types.js │ │ │ ├── types.d.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── YouTube │ │ │ ├── types.js │ │ │ ├── types.d.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── Reddit │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── Twitter │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── GameDiscounts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── Instagram │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ └── index.js │ ├── utils │ │ ├── rssParser.d.ts │ │ └── rssParser.js │ └── index.js ├── package.json └── README.md ├── example ├── youtube.spec.ts └── twitch.spec.ts ├── tsconfig.json ├── .github └── workflows │ └── npm-publish-github-packages.yml └── README.md /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './providers'; -------------------------------------------------------------------------------- /dist/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './providers'; 2 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Twitch'; 2 | export * from './YouTube'; -------------------------------------------------------------------------------- /dist/src/providers/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './Twitch'; 2 | export * from './YouTube'; 3 | -------------------------------------------------------------------------------- /dist/src/utils/rssParser.d.ts: -------------------------------------------------------------------------------- 1 | export default function rssParser(url: string): Promise; 2 | -------------------------------------------------------------------------------- /dist/src/providers/Twitch/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/providers/YouTube/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/providers/Reddit/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'node:events'; 3 | export declare class Reddit extends EventEmitter { 4 | constructor(); 5 | } 6 | -------------------------------------------------------------------------------- /dist/src/providers/Twitter/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'node:events'; 3 | export declare class Twitter extends EventEmitter { 4 | constructor(); 5 | } 6 | -------------------------------------------------------------------------------- /dist/src/providers/GameDiscounts/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'node:events'; 3 | export declare class Instagram extends EventEmitter { 4 | constructor(); 5 | } 6 | -------------------------------------------------------------------------------- /dist/src/providers/Instagram/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'node:events'; 3 | export declare class GameDiscounts extends EventEmitter { 4 | constructor(); 5 | } 6 | -------------------------------------------------------------------------------- /src/providers/Reddit/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | export class Reddit extends EventEmitter { 4 | constructor() { 5 | super(); 6 | 7 | console.log('Reddit is not yet implemented.'); 8 | } 9 | } -------------------------------------------------------------------------------- /src/providers/Twitter/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | export class Twitter extends EventEmitter { 4 | constructor() { 5 | super(); 6 | 7 | console.log('Twitter is not yet implemented.'); 8 | } 9 | } -------------------------------------------------------------------------------- /src/providers/GameDiscounts/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | export class Instagram extends EventEmitter { 4 | constructor() { 5 | super(); 6 | 7 | console.log('Instagram is not yet implemented.'); 8 | } 9 | } -------------------------------------------------------------------------------- /dist/src/providers/YouTube/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Video { 2 | title: string; 3 | link: string; 4 | pubDate: string; 5 | author: string; 6 | id: string; 7 | isoDate: string; 8 | thumbnail: string; 9 | description: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/providers/Instagram/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | export class GameDiscounts extends EventEmitter { 4 | constructor() { 5 | super(); 6 | 7 | console.log('GameDiscounts is not yet implemented.'); 8 | } 9 | } -------------------------------------------------------------------------------- /src/providers/YouTube/types.ts: -------------------------------------------------------------------------------- 1 | export interface Video { 2 | title: string; 3 | link: string; 4 | pubDate: string; 5 | author: string; 6 | id: string; 7 | isoDate: string; 8 | thumbnail: string; 9 | description: string; 10 | } -------------------------------------------------------------------------------- /example/youtube.spec.ts: -------------------------------------------------------------------------------- 1 | import { YouTube } from '../src'; 2 | 3 | const channels = [ 4 | '' 5 | ]; 6 | 7 | const youtube = new YouTube({ 8 | channels, 9 | postedVideos: [], 10 | interval: 10000 11 | }); 12 | 13 | youtube.addChannel(''); 14 | 15 | youtube.on('upload', video => { 16 | console.log(video); 17 | }); -------------------------------------------------------------------------------- /dist/src/providers/Reddit/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Reddit = void 0; 4 | const node_events_1 = require("node:events"); 5 | class Reddit extends node_events_1.EventEmitter { 6 | constructor() { 7 | super(); 8 | console.log('Reddit is not yet implemented.'); 9 | } 10 | } 11 | exports.Reddit = Reddit; 12 | -------------------------------------------------------------------------------- /dist/src/providers/Twitter/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Twitter = void 0; 4 | const node_events_1 = require("node:events"); 5 | class Twitter extends node_events_1.EventEmitter { 6 | constructor() { 7 | super(); 8 | console.log('Twitter is not yet implemented.'); 9 | } 10 | } 11 | exports.Twitter = Twitter; 12 | -------------------------------------------------------------------------------- /dist/src/providers/GameDiscounts/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Instagram = void 0; 4 | const node_events_1 = require("node:events"); 5 | class Instagram extends node_events_1.EventEmitter { 6 | constructor() { 7 | super(); 8 | console.log('Instagram is not yet implemented.'); 9 | } 10 | } 11 | exports.Instagram = Instagram; 12 | -------------------------------------------------------------------------------- /dist/src/providers/Instagram/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.GameDiscounts = void 0; 4 | const node_events_1 = require("node:events"); 5 | class GameDiscounts extends node_events_1.EventEmitter { 6 | constructor() { 7 | super(); 8 | console.log('GameDiscounts is not yet implemented.'); 9 | } 10 | } 11 | exports.GameDiscounts = GameDiscounts; 12 | -------------------------------------------------------------------------------- /example/twitch.spec.ts: -------------------------------------------------------------------------------- 1 | import { Twitch } from '../src'; 2 | 3 | const twitch = new Twitch({ 4 | channels: [''], 5 | liveChannels: [], 6 | interval: 10000, 7 | client: { 8 | id: '', 9 | secret: '', 10 | token: '' 11 | } 12 | }); 13 | 14 | twitch.addChannel(''); 15 | 16 | twitch.on('live', channel => { 17 | console.log(channel); 18 | }); 19 | 20 | twitch.on('offline', channel => { 21 | console.log(channel); 22 | }); -------------------------------------------------------------------------------- /dist/src/providers/Twitch/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Client { 2 | id: string; 3 | token?: string; 4 | secret: string; 5 | } 6 | export interface Stream { 7 | id: string; 8 | user_id: string; 9 | user_login: string; 10 | user_name: string; 11 | game_id: string; 12 | game_name: string; 13 | type: string; 14 | title: string; 15 | viewer_count: number; 16 | started_at: string; 17 | language: string; 18 | thumbnail_url: string; 19 | tag_ids: string[]; 20 | tags: string[]; 21 | is_mature: boolean; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/rssParser.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { parseString } from 'xml2js'; 3 | 4 | export default async function rssParser(url: string): Promise { 5 | const data = await axios.get(url).then((res) => res.data); 6 | 7 | const parsedData = await new Promise((resolve, reject) => { 8 | parseString(data, (err, result) => { 9 | if (err) { 10 | reject(err); 11 | } else { 12 | resolve(result); 13 | } 14 | }); 15 | }); 16 | 17 | return parsedData; 18 | } -------------------------------------------------------------------------------- /src/providers/Twitch/types.ts: -------------------------------------------------------------------------------- 1 | export interface Client { 2 | id: string; 3 | token?: string; 4 | secret: string; 5 | } 6 | 7 | export interface Stream { 8 | id: string; 9 | user_id: string; 10 | user_login: string; 11 | user_name: string; 12 | game_id: string; 13 | game_name: string; 14 | type: string; 15 | title: string; 16 | viewer_count: number; 17 | started_at: string; 18 | language: string; 19 | thumbnail_url: string; 20 | tag_ids: string[]; 21 | tags: string[]; 22 | is_mature: boolean; 23 | } -------------------------------------------------------------------------------- /dist/src/providers/YouTube/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'node:events'; 3 | export declare class YouTube extends EventEmitter { 4 | postedVideos: Set; 5 | channels: Set; 6 | constructor({ channels, postedVideos, interval }: { 7 | channels: string[]; 8 | postedVideos: string[]; 9 | interval: number; 10 | }); 11 | addChannel(channel: string): void; 12 | removeChannel(channel: string): void; 13 | getChannels(): Set; 14 | getPostedVideos(): Set; 15 | private _newVideo; 16 | private _checkForNewVideos; 17 | } 18 | -------------------------------------------------------------------------------- /dist/src/providers/Twitch/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'node:events'; 3 | import { Client } from './types'; 4 | export declare class Twitch extends EventEmitter { 5 | liveChannels: Set; 6 | channels: Set; 7 | private client; 8 | constructor({ channels, liveChannels, interval, client }: { 9 | channels: string[]; 10 | liveChannels: string[]; 11 | interval: number; 12 | client: Client; 13 | }); 14 | addChannel(channel: string): void; 15 | removeChannel(channel: string): void; 16 | getChannels(): Set; 17 | getLiveChannels(): Set; 18 | private _newStream; 19 | private _streamEnded; 20 | private _checkForNewStreams; 21 | getToken(): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ "ESNext" ], 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "target": "ESNext", 8 | "noImplicitAny": true, 9 | "moduleResolution": "node", 10 | "sourceMap": false, 11 | "outDir": "dist", 12 | "baseUrl": ".", 13 | "resolveJsonModule": true, 14 | "declaration": true, 15 | "skipDefaultLibCheck": true, 16 | "skipLibCheck": true, 17 | "experimentalDecorators": true, 18 | "emitDecoratorMetadata": true 19 | }, 20 | "include": [ 21 | "src/**/*", 22 | "package.json", 23 | "EADME.md" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | ".gitignore" 28 | ] 29 | } -------------------------------------------------------------------------------- /dist/src/utils/rssParser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const axios_1 = __importDefault(require("axios")); 7 | const xml2js_1 = require("xml2js"); 8 | async function rssParser(url) { 9 | const data = await axios_1.default.get(url).then((res) => res.data); 10 | const parsedData = await new Promise((resolve, reject) => { 11 | (0, xml2js_1.parseString)(data, (err, result) => { 12 | if (err) { 13 | reject(err); 14 | } 15 | else { 16 | resolve(result); 17 | } 18 | }); 19 | }); 20 | return parsedData; 21 | } 22 | exports.default = rssParser; 23 | -------------------------------------------------------------------------------- /dist/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | __exportStar(require("./providers"), exports); 18 | -------------------------------------------------------------------------------- /dist/src/providers/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | __exportStar(require("./Twitch"), exports); 18 | __exportStar(require("./YouTube"), exports); 19 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish-github-packages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-gpr: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | packages: write 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 16 32 | registry-url: https://npm.pkg.github.com/ 33 | - run: npm ci 34 | - run: npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@voidpkg/social-alert", 3 | "decsription": "A simple package to get the latest video from a YouTube channel or the latest stream from a Twitch channel", 4 | "version": "1.0.2", 5 | "main": "./src/index.js", 6 | "keywords": [ 7 | "youtube", 8 | "twitch", 9 | "social", 10 | "alert", 11 | "instagram", 12 | "twitter", 13 | "reddit", 14 | "game-discounts", 15 | "steam", 16 | "epic-games" 17 | ], 18 | "scripts": { 19 | "build": "tsc && npm run copy:files", 20 | "copy:files": "cpx \"README.md\" \"dist\"", 21 | "test:yt": "ts-node-dev ./example/youtube.spec", 22 | "test:tw": "ts-node-dev ./example/twitch.spec" 23 | }, 24 | "author": "Void Development ", 25 | "license": "MIT", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/VoidDevsORG/social-alert.git" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20.2.5", 32 | "@types/xml2js": "^0.4.11" 33 | }, 34 | "dependencies": { 35 | "axios": "^1.4.0", 36 | "xml2js": "^0.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dist/src/providers/YouTube/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.YouTube = void 0; 7 | const node_events_1 = require("node:events"); 8 | const rssParser_1 = __importDefault(require("../../utils/rssParser")); 9 | class YouTube extends node_events_1.EventEmitter { 10 | postedVideos; 11 | channels; 12 | constructor({ channels, postedVideos, interval }) { 13 | super(); 14 | this.postedVideos = new Set(postedVideos); 15 | this.channels = new Set(channels); 16 | setInterval(() => { 17 | this._checkForNewVideos(); 18 | }, interval); 19 | this.on('upload', (video) => { 20 | this.postedVideos.add(video.id); 21 | }); 22 | return this; 23 | } 24 | addChannel(channel) { 25 | this.channels.add(channel); 26 | } 27 | removeChannel(channel) { 28 | this.channels = this.channels.delete(channel) ? this.channels : this.channels; 29 | } 30 | getChannels() { 31 | return this.channels; 32 | } 33 | getPostedVideos() { 34 | return this.postedVideos; 35 | } 36 | _newVideo(video) { 37 | console.log(Array.from(this.postedVideos)); 38 | this.emit('upload', video); 39 | } 40 | async _checkForNewVideos() { 41 | for (const channel of this.channels) { 42 | const data = await (0, rssParser_1.default)(`https://www.youtube.com/feeds/videos.xml?channel_id=${channel}`); 43 | const ch = data.feed.author[0].name[0]; 44 | const video = data.feed.entry?.[0] || null; 45 | if (!video) 46 | return; 47 | const title = video.title[0]; 48 | const link = video.link[0].$.href; 49 | const pubDate = video.published[0]; 50 | const author = video.author[0].name[0]; 51 | const id = video['yt:videoId'][0]; 52 | const isoDate = video['published'][0]; 53 | const thumbnail = video['media:group'][0]['media:thumbnail'][0].$.url; 54 | const description = video['media:group'][0]['media:description'][0]; 55 | const videoData = { 56 | title, 57 | link, 58 | pubDate, 59 | author, 60 | id, 61 | isoDate, 62 | thumbnail, 63 | description 64 | }; 65 | if (!this.postedVideos.has(id)) { 66 | this._newVideo(videoData); 67 | } 68 | } 69 | } 70 | } 71 | exports.YouTube = YouTube; 72 | -------------------------------------------------------------------------------- /src/providers/YouTube/index.ts: -------------------------------------------------------------------------------- 1 | import { Video } from './types'; 2 | import { EventEmitter } from 'node:events'; 3 | import rssParser from '../../utils/rssParser'; 4 | 5 | export class YouTube extends EventEmitter { 6 | public postedVideos: Set; 7 | public channels: Set; 8 | constructor({ channels, postedVideos, interval }: { channels: string[], postedVideos: string[], interval: number }) { 9 | super(); 10 | this.postedVideos = new Set(postedVideos); 11 | this.channels = new Set(channels); 12 | 13 | setInterval(() => { 14 | this._checkForNewVideos(); 15 | }, interval); 16 | 17 | this.on('upload', (video: Video) => { 18 | this.postedVideos.add(video.id); 19 | }); 20 | 21 | return this; 22 | } 23 | 24 | public addChannel(channel: string) { 25 | this.channels.add(channel); 26 | } 27 | 28 | public removeChannel(channel: string) { 29 | this.channels = this.channels.delete(channel) ? this.channels : this.channels; 30 | } 31 | 32 | public getChannels() { 33 | return this.channels; 34 | } 35 | 36 | public getPostedVideos() { 37 | return this.postedVideos; 38 | } 39 | 40 | private _newVideo(video: Video) { 41 | console.log(Array.from(this.postedVideos)); 42 | this.emit('upload', video); 43 | } 44 | 45 | private async _checkForNewVideos() { 46 | for (const channel of this.channels) { 47 | const data = await rssParser(`https://www.youtube.com/feeds/videos.xml?channel_id=${channel}`); 48 | 49 | const ch = data.feed.author[0].name[0]; 50 | const video = data.feed.entry?.[0] || null; 51 | 52 | if (!video) return; 53 | 54 | const title = video.title[0]; 55 | const link = video.link[0].$.href; 56 | const pubDate = video.published[0]; 57 | const author = video.author[0].name[0]; 58 | const id = video['yt:videoId'][0]; 59 | const isoDate = video['published'][0]; 60 | const thumbnail = video['media:group'][0]['media:thumbnail'][0].$.url; 61 | const description = video['media:group'][0]['media:description'][0]; 62 | 63 | const videoData: Video = { 64 | title, 65 | link, 66 | pubDate, 67 | author, 68 | id, 69 | isoDate, 70 | thumbnail, 71 | description 72 | }; 73 | 74 | if (!this.postedVideos.has(id)) { 75 | this._newVideo(videoData); 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | ![icon](https://voidapi.rest/images/bFQmP1j/social-alert.png) 2 | 3 | # 4 | 5 | ## Introduction 6 | 7 | **Social Alert** package is a social media alert package that provides instant broadcast notification in applications such as Instagram and twitch. 8 | 9 | With low latency, you can get information quickly when the broadcasts of the broadcasters start to broadcast, below is the user guide. 10 | 11 | ## Installation 12 | ```bash 13 | npm i @voidpkg/social-alert --save 14 | # or 15 | yarn add @voidpkg/social-alert 16 | ``` 17 | 18 |

19 | 20 | # Usage 21 | 22 | ### **YouTube** 23 | ```ts 24 | import { YouTube } from "@voidpkg/social-alert"; 25 | 26 | const youtube = new YouTube({ 27 | channels: [ "CHANNEL_ID" ], 28 | postedVideos: [], 29 | interval: 10000 30 | }); 31 | 32 | youtube.addChannel('CHANNEL_ID'); 33 | 34 | youtube.on('upload', (video: Video) => { 35 | console.log(video); 36 | }); 37 | ``` 38 | 39 | ### **Twitch** 40 | ```ts 41 | import { Twitch } from "@voidpkg/social-alert"; 42 | 43 | const twitch = new Twitch({ 44 | channels: ['elraenn'], 45 | liveChannels: [], 46 | interval: 10000, 47 | client: { 48 | id: '', // Get from: https://dev.twitch.tv 49 | secret: '', // Get from: https://dev.twitch.tv 50 | token: '' // After entering the ID and SECRET, run it and check your console, a token will be automatically generated for you. So you can leave this blank. 51 | } 52 | }); 53 | 54 | twitch.addChannel('wtcn'); 55 | 56 | twitch.on('live', (stream: Stream) => { 57 | console.log(channel); 58 | }); 59 | 60 | twitch.on('offline', (stream: Stream) => { 61 | console.log(channel); 62 | }); 63 | ``` 64 | 65 |

66 | 67 | # API 68 | 69 | ## Providers 70 | |Name|Events|Implemented|Return Interface|Import Name| 71 | |---|---|---|---|---| 72 | |Twitch|`live`, `offline`|✅|`Stream`|`Twitch`| 73 | |YouTube|`upload`|✅|`Video`|`YouTube`| 74 | |Instagram|`-`|❌|`-`|`Instagram`| 75 | |Reddit|`-`|❌|`-`|`Reddit`| 76 | |Twitter|`-`|❌|`-`|`Twitter`| 77 | |Game Discounts|`-`|❌|`-`|`GameDiscounts`| 78 | 79 |
80 | 81 | ## Interfaces 82 | 83 | ```ts 84 | Stream { 85 | id: string; 86 | user_id: string; 87 | user_login: string; 88 | user_name: string; 89 | game_id: string; 90 | game_name: string; 91 | type: string; 92 | title: string; 93 | viewer_count: number; 94 | started_at: string; 95 | language: string; 96 | thumbnail_url: string; 97 | tag_ids: string[]; 98 | tags: string[]; 99 | is_mature: boolean; 100 | } 101 | 102 | Video { 103 | title: string; 104 | link: string; 105 | pubDate: string; 106 | author: string; 107 | id: string; 108 | isoDate: string; 109 | thumbnail: string; 110 | description: string; 111 | } 112 | ``` 113 | 114 |

115 | 116 | --- 117 |
118 |
119 |

© 2019 — 2023 Void Development, Ltd. All rights reserved.

120 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![icon](https://voidapi.rest/images/bFQmP1j/social-alert.png) 2 | 3 | # 4 | 5 | ## Introduction 6 | 7 | **Social Alert** package is a social media alert package that provides instant broadcast notification in applications such as Instagram and twitch. 8 | 9 | With low latency, you can get information quickly when the broadcasts of the broadcasters start to broadcast, below is the user guide. 10 | 11 | ## Installation 12 | ```bash 13 | npm i @voidpkg/social-alert --save 14 | # or 15 | yarn add @voidpkg/social-alert 16 | ``` 17 | 18 |

19 | 20 | # Usage 21 | 22 | ### **YouTube** 23 | ```ts 24 | import { YouTube } from "@voidpkg/social-alert"; 25 | 26 | const youtube = new YouTube({ 27 | channels: [ "CHANNEL_ID" ], 28 | postedVideos: [], 29 | interval: 10000 30 | }); 31 | 32 | youtube.addChannel('CHANNEL_ID'); 33 | 34 | youtube.on('upload', (video: Video) => { 35 | console.log(video); 36 | }); 37 | ``` 38 | 39 | ### **Twitch** 40 | ```ts 41 | import { Twitch } from "@voidpkg/social-alert"; 42 | 43 | const twitch = new Twitch({ 44 | channels: ['elraenn'], 45 | liveChannels: [], 46 | interval: 10000, 47 | client: { 48 | id: '', // Get from: https://dev.twitch.tv 49 | secret: '', // Get from: https://dev.twitch.tv 50 | token: '' // After entering the ID and SECRET, run it and check your console, a token will be automatically generated for you. So you can leave this blank. 51 | } 52 | }); 53 | 54 | twitch.addChannel('wtcn'); 55 | 56 | twitch.on('live', (stream: Stream) => { 57 | console.log(channel); 58 | }); 59 | 60 | twitch.on('offline', (stream: Stream) => { 61 | console.log(channel); 62 | }); 63 | ``` 64 | 65 |

66 | 67 | # API 68 | 69 | ## Providers 70 | |Name|Events|Implemented|Return Interface|Import Name| 71 | |---|---|---|---|---| 72 | |Twitch|`live`, `offline`|✅|`Stream`|`Twitch`| 73 | |YouTube|`upload`|✅|`Video`|`YouTube`| 74 | |Instagram|`-`|❌|`-`|`Instagram`| 75 | |Reddit|`-`|❌|`-`|`Reddit`| 76 | |Twitter|`-`|❌|`-`|`Twitter`| 77 | |Game Discounts|`-`|❌|`-`|`GameDiscounts`| 78 | 79 |
80 | 81 | ## Interfaces 82 | 83 | ```ts 84 | Stream { 85 | id: string; 86 | user_id: string; 87 | user_login: string; 88 | user_name: string; 89 | game_id: string; 90 | game_name: string; 91 | type: string; 92 | title: string; 93 | viewer_count: number; 94 | started_at: string; 95 | language: string; 96 | thumbnail_url: string; 97 | tag_ids: string[]; 98 | tags: string[]; 99 | is_mature: boolean; 100 | } 101 | 102 | Video { 103 | title: string; 104 | link: string; 105 | pubDate: string; 106 | author: string; 107 | id: string; 108 | isoDate: string; 109 | thumbnail: string; 110 | description: string; 111 | } 112 | ``` 113 | 114 |

115 | 116 | --- 117 |
118 |
119 |

© 2019 — 2023 Void Development, Ltd. All rights reserved.

120 |
121 | -------------------------------------------------------------------------------- /src/providers/Twitch/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { EventEmitter } from 'node:events'; 3 | import { Client, Stream } from './types'; 4 | 5 | export class Twitch extends EventEmitter { 6 | public liveChannels: Set; 7 | public channels: Set; 8 | private client: Client; 9 | constructor({ channels, liveChannels, interval, client }: { channels: string[], liveChannels: string[], interval: number, client: Client }) { 10 | super(); 11 | this.liveChannels = new Set(liveChannels); 12 | this.channels = new Set(channels); 13 | if (!client) throw new Error('No client provided'); 14 | if (!client.id) throw new Error('No client id provided'); 15 | if (!client.secret) throw new Error('No client secret provided'); 16 | this.client = client; 17 | 18 | if (!client.token) { 19 | console.warn('No client token provided, fetching one now...'); 20 | this.getToken().then((token) => token ? this.client.token = token : console.warn('Failed to fetch token.')); 21 | } 22 | 23 | setInterval(() => { 24 | this._checkForNewStreams(); 25 | }, interval); 26 | 27 | return this; 28 | } 29 | 30 | public addChannel(channel: string) { 31 | console.log('Adding channel ' + channel); 32 | this.channels.add(channel); 33 | } 34 | 35 | public removeChannel(channel: string) { 36 | this.channels = this.channels.delete(channel) ? this.channels : this.channels; 37 | } 38 | 39 | public getChannels() { 40 | return this.channels; 41 | } 42 | 43 | public getLiveChannels() { 44 | return this.liveChannels; 45 | } 46 | 47 | private _newStream(stream: Stream) { 48 | if (!this.liveChannels.has(stream.user_login)) { 49 | this.liveChannels.add(stream.user_login); 50 | this.emit('live', stream); 51 | } 52 | } 53 | 54 | private _streamEnded(stream: Stream) { 55 | if (this.liveChannels.has(stream.user_login)) { 56 | this.liveChannels.delete(stream.user_login); 57 | this.emit('offline', stream); 58 | } 59 | } 60 | 61 | private async _checkForNewStreams() { 62 | for (const channel of this.channels) { 63 | const data = await axios.get(`https://api.twitch.tv/helix/streams?user_login=${channel}`, { 64 | headers: { 65 | "client-id": this.client.id, 66 | "Authorization": `Bearer ${this.client.token}` 67 | } 68 | }).then((res) => res.data).catch((err) => console.log(err)); 69 | 70 | const stream = data.data?.[0] || null; 71 | if (!stream) return; 72 | 73 | if (stream.type === 'live') this._newStream(stream); 74 | else this._streamEnded(stream); 75 | } 76 | } 77 | 78 | public async getToken() { 79 | const request = await axios.post('https://id.twitch.tv/oauth2/token', { 80 | client_id: this.client.id, 81 | client_secret: this.client.secret, 82 | grant_type: 'client_credentials', 83 | }).catch(err => { 84 | return err.response; 85 | }); 86 | 87 | const token = request?.data?.access_token ? request.data.access_token : null; 88 | console.log('(Twitch->getToken) New token created at ' + new Date().toLocaleString() + ' (' + token + ')'); 89 | return token; 90 | } 91 | } -------------------------------------------------------------------------------- /dist/src/providers/Twitch/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.Twitch = void 0; 7 | const axios_1 = __importDefault(require("axios")); 8 | const node_events_1 = require("node:events"); 9 | class Twitch extends node_events_1.EventEmitter { 10 | liveChannels; 11 | channels; 12 | client; 13 | constructor({ channels, liveChannels, interval, client }) { 14 | super(); 15 | this.liveChannels = new Set(liveChannels); 16 | this.channels = new Set(channels); 17 | if (!client) 18 | throw new Error('No client provided'); 19 | if (!client.id) 20 | throw new Error('No client id provided'); 21 | if (!client.secret) 22 | throw new Error('No client secret provided'); 23 | this.client = client; 24 | if (!client.token) { 25 | console.warn('No client token provided, fetching one now...'); 26 | this.getToken().then((token) => token ? this.client.token = token : console.warn('Failed to fetch token.')); 27 | } 28 | setInterval(() => { 29 | this._checkForNewStreams(); 30 | }, interval); 31 | return this; 32 | } 33 | addChannel(channel) { 34 | console.log('Adding channel ' + channel); 35 | this.channels.add(channel); 36 | } 37 | removeChannel(channel) { 38 | this.channels = this.channels.delete(channel) ? this.channels : this.channels; 39 | } 40 | getChannels() { 41 | return this.channels; 42 | } 43 | getLiveChannels() { 44 | return this.liveChannels; 45 | } 46 | _newStream(stream) { 47 | if (!this.liveChannels.has(stream.user_login)) { 48 | this.liveChannels.add(stream.user_login); 49 | this.emit('live', stream); 50 | } 51 | } 52 | _streamEnded(stream) { 53 | if (this.liveChannels.has(stream.user_login)) { 54 | this.liveChannels.delete(stream.user_login); 55 | this.emit('offline', stream); 56 | } 57 | } 58 | async _checkForNewStreams() { 59 | for (const channel of this.channels) { 60 | const data = await axios_1.default.get(`https://api.twitch.tv/helix/streams?user_login=${channel}`, { 61 | headers: { 62 | "client-id": this.client.id, 63 | "Authorization": `Bearer ${this.client.token}` 64 | } 65 | }).then((res) => res.data).catch((err) => console.log(err)); 66 | const stream = data.data?.[0] || null; 67 | if (!stream) 68 | return; 69 | if (stream.type === 'live') 70 | this._newStream(stream); 71 | else 72 | this._streamEnded(stream); 73 | } 74 | } 75 | async getToken() { 76 | const request = await axios_1.default.post('https://id.twitch.tv/oauth2/token', { 77 | client_id: this.client.id, 78 | client_secret: this.client.secret, 79 | grant_type: 'client_credentials', 80 | }).catch(err => { 81 | return err.response; 82 | }); 83 | const token = request?.data?.access_token ? request.data.access_token : null; 84 | console.log('(Twitch->getToken) New token created at ' + new Date().toLocaleString() + ' (' + token + ')'); 85 | return token; 86 | } 87 | } 88 | exports.Twitch = Twitch; 89 | --------------------------------------------------------------------------------