1
0
forked from notnull/apradiobot

Compare commits

..

13 Commits

Author SHA1 Message Date
d67835be30 add hacky fix for radio stopping 2020-02-23 19:36:38 -05:00
8048822859 added helpers 2020-01-18 00:16:57 -05:00
03fc3e81c3 add message for reset playlists 2020-01-07 06:17:19 -05:00
8362ef0659 add ability to delete tracks 2019-12-25 06:35:18 -05:00
aec3083b42 change wording for incorrect syntax 2019-12-25 02:23:29 -05:00
4718d523d9 add newline to addToPlaylist 2019-12-23 12:44:48 -05:00
7462dc79bb account for weird quote characters 2019-12-23 12:13:28 -05:00
307f3d575f update handleSpam 2019-12-23 09:08:30 -05:00
f55f0aa4e4 add handleSpam 2019-12-23 07:32:31 -05:00
17b3cc0c42 add error handling for reset, fix .flat() not supported 2019-12-23 05:54:26 -05:00
8f5510993c added ability to reset playlists
- only owners can run it 
- iterates over all playlists in foler /playlists (gitignored) 
- shuffles mpc playlist and then plays
2019-12-22 02:04:03 -05:00
1418299ff0 add requests to user playlists 2019-12-21 21:59:52 -05:00
1736ed2443 add help 2019-12-21 21:43:36 -05:00
9 changed files with 232 additions and 43 deletions

View File

@ -9,6 +9,7 @@
}
},
"env": {
"node": true
"node": true,
"es6": true
}
}

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
node_modules
old
.env
playlists/*
test

22
package-lock.json generated
View File

@ -689,16 +689,6 @@
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -1209,6 +1199,18 @@
"tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"dependencies": {
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
}
}
},
"request-promise": {

View File

@ -4,7 +4,8 @@
"description": "a bot",
"main": "index.js",
"scripts": {
"start": "node ./src"
"start": "node ./src",
"dev": "nodemon ./src"
},
"author": "notnull",
"license": "ISC",

23
src/handleSpam.js Normal file
View File

@ -0,0 +1,23 @@
const phrases = [
'Did you know? Zhachev is a twat.',
'Zhachev is a poor lonely baby who has been stalking this IRC for over a year. anti-civ 4 lyfe!',
"Zhachev could be blowing up transformers, but instead he's wasting time stalking us on IRC.",
]
let spam = false
const resetSpam = () => {
spam = false
console.log('reset spam counter.')
}
const handleSpam = event => {
if (spam === true) return
console.log('spam detected. setting spam counter.')
spam = true
setTimeout(resetSpam, 30000)
const phrase = phrases[Math.floor(Math.random() * phrases.length)]
return event.reply(phrase)
}
module.exports = handleSpam

View File

@ -1,13 +1,17 @@
const IRC = require('irc-framework')
const bot = new IRC.Client()
const { getPlaylist, getCurrentTrack, skipTrack } = require('./mpc-commands')
const { searchTrack, requestTrack } = require('./spotify')
const { searchTrack, requestTrack, deleteTrack } = require('./spotify')
const { getCurrentTrack, skipTrack, reset, poll } = require('./mpc-commands')
const { getMyPlaylist } = require('./linx-commands')
const chalk = require('chalk')
const owners = ['notnull']
const helpers = ['notnull', 'rfa', 'substack']
const autojoin = ['#anarchybots']
setInterval(poll, 60000)
const host = process.env.HOST || 'localhost'
const port = process.env.PORT || 6667
const nick = process.env.NICK || 'radiobot'
@ -26,15 +30,18 @@ bot.on('socket close', () => console.log('bot has disconnected.'))
bot.matchMessage(/^!hello/, event => event.reply('Hi!'))
/** ADMIN COMMANDS **/
bot.matchMessage(/^!reset/, event => handleReset(event))
bot.matchMessage(/^!join/, event => handleJoin(event))
bot.matchMessage(/^!part/, event => handlePart(event))
bot.matchMessage(/^!quit/, event => handleQuit(event))
/** RADIO COMMANDS**/
bot.matchMessage(/^!playlist/, event => sendPlaylist(event))
bot.matchMessage(/^!playlist/, event => getMyPlaylist(event, bot))
bot.matchMessage(/^!request/, event => requestTrack(event))
bot.matchMessage(/^!delete/, event => deleteTrack(event))
bot.matchMessage(/^!search/, event => searchTrack(event))
bot.matchMessage(/^!skip/, event => sendSkipTrack(event))
bot.matchMessage(/^!help/, event => sendHelp(event))
bot.matchMessage(/^!np/, event => sendCurrentTrack(event))
const handleJoin = event => {
@ -52,11 +59,20 @@ const handleQuit = event => {
bot.quit(["You'll cowards, don't even smoke crack."])
}
const sendPlaylist = event => {
const { error, playlist } = getPlaylist()
if (error) return event.reply('Something went wrong.')
const splitPlaylist = playlist.split('\n').slice(0, 5)
splitPlaylist.map(s => bot.say(event.nick, s))
const handleReset = event => {
if (!helpers.includes(event.nick)) return
reset(event)
}
const sendHelp = event => {
const help = [
'to search: !search "track name" artist "artist name" (artist and artist name optional)',
'to request: !request "track name" artist "artist name" (artist and artist name optional)',
'to skip current track: !skip',
"to view what's currently playing: !np",
'to view five upcoming tracks: !playlist',
]
help.map(h => bot.say(event.nick, h))
}
const sendCurrentTrack = event => {

16
src/linx-commands.js Normal file
View File

@ -0,0 +1,16 @@
const axios = require('axios')
const fs = require('fs')
const getMyPlaylist = (event, bot) => {
fs.readFile(`./playlists/${event.nick}`, 'utf-8', (err, playlist) => {
if (err) return event.reply("You don't have a playlist.")
const config = { headers: { 'Linx-Randomize': 'yes' } }
axios
.put('https://irc.anarchyplanet.org/img/upload', playlist, config)
.then(res => {
bot.say(event.nick, res.data)
})
})
}
module.exports = { getMyPlaylist }

View File

@ -1,10 +1,22 @@
const { spawnSync } = require('child_process')
const fs = require('fs')
let playing = null
const getPlaylist = () => {
const { stderr, stdout } = spawnSync('mpc', ['playlist'])
return { error: stderr.toString(), playlist: stdout.toString() }
}
const poll = () => {
const { stdout } = spawnSync('mpc', ['status'])
const nowPlaying = stdout.toString()
if (playing === nowPlaying) {
console.log('Song stopped playing. Skipping\n', nowPlaying)
spawnSync('mpc', ['next'])
} else playing = nowPlaying
}
const getCurrentTrack = () => {
const { stderr, stdout } = spawnSync('mpc', ['current'])
return { error: stderr.toString(), track: stdout.toString() }
@ -17,8 +29,95 @@ const skipTrack = () => {
}
const insertTrack = spotifyURI => {
console.log('inserting track:', JSON.stringify(spotifyURI))
const { stderr, stdout } = spawnSync('mpc', ['insert', spotifyURI])
console.log(stderr.toString(), stdout.toString())
return { error: stderr.toString(), insert: stdout.toString() }
}
module.exports = { getPlaylist, getCurrentTrack, skipTrack, insertTrack }
const insertTrackAsync = spotifyURI => {
console.log('inserting track:', spotifyURI)
const promise = new Promise((resolve, reject) => {
const { stdout, error } = spawnSync('mpc', ['insert', spotifyURI])
if (error) reject(error.message)
resolve({ track: stdout.toString() })
})
return promise
}
const reset = event => {
clearAllPlaylists(event)
addAllPlaylists(event)
}
const clearAllPlaylists = event => {
const { error } = spawnSync('mpc', ['clear'])
if (error) return handleError(error, event)
}
const addAllPlaylists = async event => {
try {
const files = fs.readdirSync('./playlists')
const tracks = readPlaylists(files)
await Promise.all(
tracks.map(t => insertTrackAsync(t.split(' # ')[0]))
).then(() => {
shuffleAllPlaylists()
play()
return event.reply('Successfully reset playlists.')
})
} catch (e) {
return handleError(e, event)
}
}
const readPlaylists = files =>
files
.map(f => fs.readFileSync('./playlists/' + f, 'utf-8').split('\n'))
.reduce((a, b) => a.concat(b))
const handleError = (err, event) => {
console.log(err)
return event.reply('Something went wrong.')
}
const shuffleAllPlaylists = event => {
const { error } = spawnSync('mpc', ['shuffle'])
if (error) return handleError(error, event)
}
const play = event => {
const { error } = spawnSync('mpc', ['play'])
if (error) return handleError(error, event)
}
const removeTrack = (trackName, artistName, uri, event) => {
console.log('removing track', uri, event.nick)
fs.readFile('./playlists/' + event.nick, 'utf-8', (err, playlist) => {
console.log('err', err, 'playlist', playlist)
if (err) return event.reply("You don't have a playlist.")
const match = playlist.split('\n').find(p => p.match(new RegExp(uri)))
if (!match) return event.reply("This track wasn't found in your playlist.")
const filteredPlaylist = playlist.split('\n').filter(t => t !== match)
fs.writeFile(
`./playlists/${'notnull'}`,
filteredPlaylist.join('\n'),
err => {
if (err) return handleError(err)
return event.reply(
`${trackName} by ${artistName} was successfully deleted from your playlist.`
)
}
)
})
}
module.exports = {
getPlaylist,
getCurrentTrack,
skipTrack,
insertTrack,
removeTrack,
reset,
poll,
}

View File

@ -1,7 +1,10 @@
const Spotify = require('node-spotify-api')
const chalk = require('chalk')
require('dotenv').config()
const { insertTrack } = require('./mpc-commands')
const { insertTrack, removeTrack } = require('./mpc-commands')
let request = ''
const handleSpam = require('./handleSpam')
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
@ -11,30 +14,30 @@ const spotify = new Spotify({
secret: CLIENT_SECRET,
})
const addToPlaylist = (nick, string) => {
require('fs').appendFileSync(`playlists/${nick}`, string)
}
const parseTrackData = tracks => {
const { items } = tracks
printTrackNames(items)
//printTrackNames(items)
const { album, artists, uri, name } = items[0]
const artist = artists[0].name
const albumName = album.name
return { trackName: name, artistName: artist, albumName, uri }
}
const addToPlaylist = (nick, string) => {
require('fs').appendFileSync(`playlists/${nick}`, string + '\n')
}
const searchTrack = event => {
let pattern = /^!search "(.+?)"( artist "(.+?)")?/
let pattern = /^!search ["“](.+?)["”]( artist ["“](.+?)["”])?/
let match = event.message.match(pattern)
if (!match)
return event.reply(
'Incorrect syntax. Use !search "track name" artist "artist name" (artist and artist name optional)'
'Use !search "track name" artist "artist name" (artist and artist name optional)'
)
let query = match[1]
if (match[3]) query += ` artist:${match[3]}`
printQuery(query)
//printQuery(query)
spotify.search({ type: 'track', query }).then(data => {
if (!data || !data.tracks) return event.reply('Something went wrong.')
if (data.tracks.total === 0) return event.reply('No results.')
@ -47,17 +50,19 @@ const searchTrack = event => {
}
const requestTrack = event => {
let pattern = /^!request "(.+?)"( artist "(.+?)")?/
if (event.message === request) return handleSpam(event)
request = event.message
let pattern = /^!request ["“](.+?)["”]( artist ["“](.+?)["”])?/
let match = event.message.match(pattern)
if (!match)
return event.reply(
'Incorrect syntax. Use !request "track name" artist "artist name" (artist and artist name optional)'
'Use !request "track name" artist "artist name" (artist and artist name optional)'
)
let query = match[1]
if (match[3]) query += ` artist:${match[3]}`
printQuery(query)
addToPlaylist(event.nick, query)
//printQuery(query)
spotify.search({ type: 'track', query }).then(data => {
if (!data || !data.tracks) return event.reply('Something went wrong.')
if (data.tracks.total === 0) return event.reply('No results.')
@ -67,7 +72,29 @@ const requestTrack = event => {
printTrackData(trackName, artistName, albumName, uri)
const { error } = insertTrack(uri)
if (error) return event.reply('Something went wrong.')
event.reply(`Requested '${trackName}' by ${artistName}`)
const request = `"${trackName}" artist "${artistName}"`
addToPlaylist(event.nick, `${uri} # ${request}`)
event.reply(`Requested ${request}`)
})
}
const deleteTrack = event => {
console.log('Deleting', event.message)
let pattern = /^!delete ["“](.+?)["”]( artist ["“](.+?)["”])?/
let match = event.message.match(pattern)
if (!match)
return event.reply(
'Use !delete "track name" artist "artist name" (artist and artist name optional)'
)
let query = match[1]
if (match[3]) query += ` artist:${match[3]}`
printQuery(query)
spotify.search({ type: 'track', query }).then(data => {
if (!data || !data.tracks) return event.reply('Something went wrong.')
if (data.tracks.total === 0) return event.reply('Not found.')
const { trackName, artistName, uri } = parseTrackData(data.tracks)
removeTrack(trackName, artistName, uri, event)
})
}
@ -77,14 +104,15 @@ const printQuery = query => {
console.log(query)
console.log(chalk.red('\n*******************************************\n'))
}
const printTrackNames = items => {
console.log(chalk.yellow('\n*******************************************\n'))
console.log('Search returned the following tracks:')
items.map(i => console.log(`'${i.name}' by ${i.artists[0].name}`))
console.log(chalk.yellow('\n*******************************************\n'))
}
const printTrackData = (name, artist, albumName, uri) => {
// const printTrackNames = items => {
// console.log(chalk.yellow('\n*******************************************\n'))
// console.log('Search returned the following tracks:')
// items.map(i => console.log(`'${i.name}' by ${i.artists[0].name}`))
// console.log(chalk.yellow('\n*******************************************\n'))
// }
const printTrackData = (name, artist, albumName, uri, message) => {
console.log(chalk.blue('\n*******************************************\n'))
if (message) console.log(message)
console.log('name:', name)
console.log('artist:', artist)
console.log('album:', albumName)
@ -92,4 +120,4 @@ const printTrackData = (name, artist, albumName, uri) => {
console.log(chalk.blue('\n*******************************************\n'))
}
module.exports = { searchTrack, requestTrack }
module.exports = { searchTrack, requestTrack, deleteTrack }