Skip to content

Commit

Permalink
Don't generate image on Heroku for the sake of performance
Browse files Browse the repository at this point in the history
  • Loading branch information
rojvv committed Jun 16, 2021
0 parents commit 10f1537
Show file tree
Hide file tree
Showing 27 changed files with 1,312 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.idea/
.vscode/


node_modules/
package-lock.json


.gitignore


downloads/
.env
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @rojserbest
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#
.idea/
.vscode/

#
node_modules/
package-lock.json

#
downloads/
.env
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": false
}
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM node:16-buster-slim
RUN apt update && apt upgrade -y && apt install ffmpeg git -y
COPY . /app
WORKDIR /app
RUN npm install
CMD npm start
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Calls Music 2 - A Telegram bot to stream audio in calls using GramJS and tgcallsjs
Copyright (C) 2021 Roj Serbest

Calls Music 2 is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

Calls Music 2 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Calls Music 2. If not, see <https://www.gnu.org/licenses/>.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
worker: npm start
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Calls Music 2 — Stream audio in Telegram calls using GramJS and tgcallsjs

## Requirements

This bot needs a Linux system, Node JS version 15 or newer with the packages specified in the `package.json`, ffmpeg, Google Chrome or Chromium to work.

## Deployment

### Config

1. Copy `example.env` to `.env` and fill it with your credentials.
2. Install the required Node JS packages:
```bash
npm install
```
3. Run:
```bash
npm start
```

### Docker

1. Build:
```bash
docker build -t musicplayer .
```
2. Run:
```bash
docker run --env-file .env musicplayer
```

### Heroku

[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/callsmusic/callsmusic2)

- You can generate a string session [here](https://rojserbest.github.io/bssg).

## Commands

| Command | Usage |
| ------- | ---------------------------------------------------- |
| /play | play the replied audio file |
| /pause | pause the audio stream |
| /resume | resume the audio stream |
| /skip | skip the current audio stream |
| /stop | clear the queue and remove the userbot from the call |

## License

### GNU Affero General Public License v3.0

[Read more](http://www.gnu.org/licenses/#AGPL)

## Credits

- Andrew Lane ([TGCalls](https://github.com/tgcallsjs/tgcalls))
- Painor ([GramJS](https://github.com/gram-js/gramjs))
- Vitaly Domnikov ([Telegraf](https://github.com/telegraf/telegraf))
- Lonami ([Telethon](https://github.com/lonami/telethon))
32 changes: 32 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "Calls Music 2",
"description": "Stream audio in Telegram group calls using GramJS and tgcallsjs",
"keywords": ["telegram", "music"],
"repository": "https://github.com/callsmusic/callsmusic2",
"env": {
"STRING_SESSION": {
"description": "A GramJS/Telethon string session. You can generate one at https://rojserbest.github.io/bssg.",
"required": true
},
"BOT_TOKEN": {
"description": "A bot token from @BotFather.",
"required": true
},
"API_ID": {
"description": "An app ID from https://my.telegram.org/apps.",
"required": true
},
"API_HASH": {
"description": "An app hash from https://my.telegram.org/apps.",
"required": true
}
},
"buildpacks": [
{
"url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest"
},
{
"url": "heroku/nodejs"
}
]
}
39 changes: 39 additions & 0 deletions bot/handlers/controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { Composer } = require("grammy");
const connections = require("../../connections");
const queues = require("../../queues");

const composer = new Composer();

composer.command("resume", async (ctx) => {
const result = connections.resume(ctx.chat.id);
if (result === 0) await ctx.reply("<b>▶️ Resumed</b>");
else if (result === 1) await ctx.reply("<b>❌ Not playing</b>");
else if (result === 2) await ctx.reply("<b>❌ Not in call</b>");
});

composer.command("pause", async (ctx) => {
const result = connections.pause(ctx.chat.id);
if (result === 0) await ctx.reply("<b>⏸ Paused</b>");
else if (result === 1) await ctx.reply("<b>❌ Not playing</b>");
else if (result === 2) await ctx.reply("<b>❌ Not in call</b>");
});

composer.command("skip", async (ctx) => {
const result = await connections.stop(ctx.chat.id);
if (result === 0) await ctx.reply("<b>⏩ Skipped</b>");
else if (result === 1) await ctx.reply("<b>❌ Not playing</b>");
else if (result === 2) await ctx.reply("<b>❌ Not in call</b>");
});

composer.command("stop", async (ctx) => {
if (connections.inCall(ctx.chat.id)) {
await connections.stop(ctx.chat.id);
connections.remove(ctx.chat.id);
queues.clear(ctx.chat.id);
await ctx.reply("<b>⏹ Stopped</b>");
} else {
await ctx.reply("<b>❌ Not in call</b>");
}
});

module.exports = composer;
22 changes: 22 additions & 0 deletions bot/handlers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const play = require("./play");
const controls = require("./controls");
const voiceChatEnded = require("./voice_chat_ended");
const privateChat = require("./private");

module.exports = (bot) => {
bot.filter((ctx) => {
if (ctx.chat?.type === "supergroup") {
if (
ctx.message?.reply_to_message &&
(ctx.message?.reply_to_message?.audio ||
ctx.message?.reply_to_message?.voice)
) {
return true;
}
}
return false;
}).use(play);
bot.filter((ctx) => ctx.chat?.type === "supergroup", controls);
bot.filter((ctx) => ctx.chat?.type === "private", privateChat);
bot.use(voiceChatEnded);
};
43 changes: 43 additions & 0 deletions bot/handlers/play.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { Composer } = require("grammy");
const { createMessageLink, createUserLink, getFile } = require("../utils");
const connections = require("../../connections");
const ffmpeg = require("../../ffmpeg");
const queues = require("../../queues");

const composer = new Composer();

async function playOrQueue(ctx) {
const media =
ctx.message.reply_to_message.audio ||
ctx.message.reply_to_message.voice,
isVoice = !!ctx.message.reply_to_message.voice,
title = isVoice ? "Voice" : media.title,
artist = isVoice ? ctx.from.first_name : media.performer,
readable = ffmpeg(await getFile(ctx, media.file_id)),
link = createMessageLink(
ctx.message,
isVoice ? "a voice message" : artist + " - " + title
);
let text;

if (connections.playing(ctx.chat.id)) {
const position = queues.push(ctx.chat.id, {
title,
artist,
readable,
});
text =
`<b>#\ufe0f\u20e3 ${createUserLink(ctx.from)} ` +
`queued ${link} at position ${position}</b>`;
} else {
text =
`<b>\u25b6\ufe0f ${createUserLink(ctx.from)} ` +
`is now playing ${link}</b>`;
await connections.setReadable(ctx.chat.id, readable);
}

await ctx.reply(text);
}

composer.command("play", playOrQueue);
module.exports = composer;
28 changes: 28 additions & 0 deletions bot/handlers/private.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { Composer, InlineKeyboard } = require("grammy");
const { createUserLink } = require("../utils");

const composer = new Composer();

composer.command("start", async (ctx) => {
await ctx.reply(
`<b>👋🏻 Hi ${createUserLink(ctx.from)}!</b>
I am Calls Music bot, I let you play music in group calls.
The commands I currently support are:
/play - play the replied audio file or YouTube video
/pause - pause the audio stream
/resume - resume the audio stream
/skip - skip the current audio stream
/stop - clear the queue and remove the userbot from the call`,
{
reply_markup: new InlineKeyboard()
.url("🔈 News Channel", "https://t.me/callsmusic")
.row()
.url("💬 Support Group", "https://t.me/callsmusicchat"),
}
);
});

module.exports = composer;
12 changes: 12 additions & 0 deletions bot/handlers/voice_chat_ended.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { Composer } = require("grammy");
const connections = require("../../connections");
const queues = require("../../queues");

const composer = new Composer();

composer.on("message:voice_chat_ended", async (ctx) => {
connections.remove(ctx.chat.id);
queues.clear(ctx.chat.id);
});

module.exports = composer;
23 changes: 23 additions & 0 deletions bot/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { Bot } = require("grammy");
const config = require("../config");

const bot = new Bot(config.botToken);
require("./handlers")(bot);

bot.api.config.use((prev, method, payload) => {
return prev(method, {
...payload,
parse_mode: "HTML",
});
});

bot.catch(async (error) => {
if (error.error instanceof Error) {
await error.ctx.reply(error.error.toString());
}
});

module.exports.bot = bot;
module.exports.start = async () => {
await bot.start({ dropPendingUpdates: true });
};
39 changes: 39 additions & 0 deletions bot/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const config = require("../config");

const createLink = (url, text) => `<a href="${url}">${escape(text)}</a>`;

function escape(s, quote = true) {
s = s.replace(/&/g, "&amp;");
s = s.replace(/</g, "&lt;");
s = s.replace(/>/g, "&gt;");

if (quote) {
s = s.replace(/"/g, "&quot;");
s = s.replace(/'/g, "&#x27;");
}

return s;
}

function createUserLink(user) {
return createLink(`tg://user?id=${user.id}`, user.first_name);
}

function createMessageLink(message, text) {
return createLink(
"https://t.me/c/" +
(message.chat.id.toString().startsWith("-100")
? message.chat.id
.toString()
.slice(3, message.chat.id.toString().length)
: message.chat.id) +
`/${message.message_id}`,
text
);
}

const getFile = async (ctx, fileId) =>
"https://api.telegram.org/file/bot" +
`${config.botToken}/${(await ctx.api.getFile(fileId)).file_path}`;

module.exports = { createLink, createUserLink, createMessageLink, getFile };
9 changes: 9 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require("dotenv").config();

module.exports = {
apiId: parseInt(process.env.API_ID),
apiHash: process.env.API_HASH,
stringSession: process.env.STRING_SESSION,
botToken: process.env.BOT_TOKEN,
logLevel: process.env.LOG_LEVEL || "none",
};
Loading

0 comments on commit 10f1537

Please sign in to comment.