You've already forked random-scripts
Compare commits
7 Commits
472bb1962c
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
39d339a5d7 | ||
![]() |
db2c5f7676 | ||
![]() |
833b32dc2b | ||
![]() |
2af98808e0 | ||
![]() |
dfd7dfa5f0 | ||
![]() |
f5e33ee9eb | ||
![]() |
863246f644 |
237
better-radio/LICENSE
Normal file
237
better-radio/LICENSE
Normal file
File diff suppressed because one or more lines are too long
52
better-radio/README.md
Normal file
52
better-radio/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
**Development has unfortunately stopped**
|
||||
|
||||
# better-radio
|
||||
|
||||
I got fed up with the radio station playing at work. It's pretty much a 2 hour playlist on a loop with ads inbetween songs.
|
||||
My idea of what this software will do is create a simple internet audio stream that can be played by any device.
|
||||
|
||||
The initial idea goes like this:
|
||||
|
||||
- Have a raspberry pi connected via AUX to the radio at work.
|
||||
- The raspberry pi loads a audio stream generated by the software described below.
|
||||
- A icecast server which distributes an opus audio stream.
|
||||
- A audio source with dynamic playlist support which plays audio into the icecast server.
|
||||
- yt-dlp downloading songs on command by the software below.
|
||||
- A web-gui where employees can request songs based on music on Youtube Music.
|
||||
- Optionally link the orderpicking system with this so no phone is needed.
|
||||
- Optionally link the IBM i system so that no phone is needed.
|
||||
- When no songs are requested, keep playing the youtube Radio playlist until a song is requested.
|
||||
- An admin portal where every aspect can be changed.
|
||||
|
||||
# Fricking commercial licensing
|
||||
So apperantly in The Netherlands you need to pay a copyright holding company hefty sums of money a year if you play radio on the workfloor.
|
||||
This is the reason why we have the same radio station on the whole time. Because it's paid for that one and that one only.
|
||||
This is an amazing project but realistically it will never be rolled out on the workfloor because of this.
|
||||
|
||||
Changing work is much easier at this point.
|
||||
|
||||
|
||||
# How it should work
|
||||
|
||||
This software will be built in four components.
|
||||
|
||||
- A container which automatically plays this audio stream running on the raspberry pi, this also makes it easier to update remotely.
|
||||
- A audio streamer which supports dynamic playlists, this is so that a "background" playlist can be interrupted by a request.
|
||||
- A yt-dlp script which gets commands by the GUI listed below, downloads songs and playlists, keeps up a simple library and such
|
||||
- A web-gui component which handles user interaction, this can be optionally split up to frontend and backend which then controls the components listed above.
|
||||
|
||||
# Milestones
|
||||
## First milesone (done)
|
||||
Have a working concept, just change the music already. No need for youtube music searching and such, just change the
|
||||
fricking radio station. Being able to change the radio station from the desktop is already miles better.
|
||||
|
||||
- A working raspberry pi player which can be controlled remotely
|
||||
- A web frontend where users can select the radio station
|
||||
|
||||
## Second milestone
|
||||
Add some Youtube components and an administrator page wich can override some site functions.
|
||||
|
||||
- Add youtube-dlp backend wich can fetch search results and show them
|
||||
- Add youtube-dlp backend wich can download songs for streaming, no file cache yet
|
||||
- Add administrator page which can lock some functions which can be controlled publicly (done)
|
||||
- Add function for site administrators to add radio stations by themselves (done)
|
9
better-radio/components/frontend/README.md
Normal file
9
better-radio/components/frontend/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Radiostream frontend
|
||||
This piece of software will be the HTTP frontend for the employees.
|
||||
|
||||
# What it must do
|
||||
Show what radiostation is currently playing, and offer the option to change it.
|
||||
|
||||
# What it could do
|
||||
Have an employee section where employees can select the radio station. There could also be a seperate admin portal
|
||||
where options can be configured.
|
130
better-radio/components/frontend/app.py
Normal file
130
better-radio/components/frontend/app.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from flask import Flask, render_template, request, flash
|
||||
import secrets
|
||||
from mango import Mango
|
||||
from bson import ObjectId
|
||||
from helpers import *
|
||||
|
||||
debug = True
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = secrets.token_hex(22)
|
||||
|
||||
|
||||
@app.route('/location')
|
||||
def locations():
|
||||
locs = mango.locations_get_locations()
|
||||
return render_template('locations.html', locations=locs)
|
||||
|
||||
|
||||
@app.route('/location/<locid>')
|
||||
def location(locid):
|
||||
loc = mango.locations_get_location(locid)
|
||||
return render_template('location.html', location=loc)
|
||||
|
||||
|
||||
@app.route('/location/<locid>/config', methods=['GET', 'POST'])
|
||||
def location_config(locid):
|
||||
if request.method == "POST":
|
||||
request_type = request.form.get('type')
|
||||
|
||||
if request_type == 'enabled-stations':
|
||||
sta = request.form.getlist('station')
|
||||
mango.locations_update_enabled_stations(locid, sta)
|
||||
elif request_type == 'enable-requests':
|
||||
ena = bool(request.form.get('enable', default=False))
|
||||
mango.locations_update_enable_requests(locid, ena)
|
||||
elif request_type == 'enable-radio':
|
||||
ena = bool(request.form.get('enable', default=False))
|
||||
mango.locations_update_enable_radio(locid, ena)
|
||||
else:
|
||||
flash('You posted a form but no such call exists.')
|
||||
|
||||
loc = mango.locations_get_location(locid)
|
||||
sta = mango.stations_get_stations()
|
||||
pla = mango.player_get_state(loc['config']['player'])
|
||||
|
||||
off = check_player_offline(pla['last_update'])
|
||||
|
||||
return render_template('location_config.html', location=loc, stations=sta, ObjectId=ObjectId, offline=off)
|
||||
|
||||
|
||||
@app.route('/location/<locid>/request', methods=['GET', 'POST'])
|
||||
def location_request(locid):
|
||||
loc = mango.locations_get_location(locid)
|
||||
return render_template('location_request.html', location=loc)
|
||||
|
||||
|
||||
@app.route('/location/<locid>/radio', methods=['GET', 'POST'])
|
||||
def location_radio(locid):
|
||||
loc = mango.locations_get_location(locid)
|
||||
sta = mango.stations_get_stations()
|
||||
pla = mango.player_get_state(loc['config']['player'])
|
||||
|
||||
if request.method == "POST":
|
||||
request_type = request.form.get('type')
|
||||
|
||||
if request_type == 'select-station':
|
||||
station = request.form.get('station')
|
||||
mango.player_set_radio(loc['config']['player'], station)
|
||||
flash('Enqueued change request')
|
||||
elif request_type == 'stop-stream':
|
||||
mango.player_pause_radio(loc['config']['player'])
|
||||
flash('Enqueued change request')
|
||||
else:
|
||||
flash('You posted a form but no such call exists.')
|
||||
|
||||
# report current playing radio or false
|
||||
if pla['radio']['playing'] is False:
|
||||
cur = False
|
||||
else:
|
||||
cur = pla['radio']['stationId']
|
||||
|
||||
# set location radio control config enabled
|
||||
enabled = loc['config']['enable_radio']
|
||||
|
||||
# check if player has not been offline for too long
|
||||
if check_player_offline(pla['last_update']):
|
||||
enabled = False
|
||||
|
||||
return render_template('location_radio.html', location=loc, stations=sta, current=cur, ObjectId=ObjectId, enabled=enabled)
|
||||
|
||||
|
||||
@app.route('/players/<player>/config', methods=['POST'])
|
||||
def players(player):
|
||||
data = request.get_json()
|
||||
new_state = mango.player_get_config(player)
|
||||
|
||||
mango.player_update_state(player, data)
|
||||
|
||||
if data['radio'].get('cookie') == new_state['radio']['cookie']: # save time by comparing last change cookie
|
||||
# one caviat, the cookie may not match up when the last station was paused then unpaused
|
||||
return {}
|
||||
|
||||
def generate_radio_return(): # define function to generate response
|
||||
station = str(new_state['radio']['station'])
|
||||
url = mango.stations_get_station(new_state['radio']['station'])['stream']['url']
|
||||
cookie = new_state['radio']['cookie']
|
||||
return {'type': 'radio', 'stationId': station, 'url': url, 'cookie': cookie}
|
||||
|
||||
# checking for playing bools
|
||||
if new_state['radio']['playing'] is False and data['radio']['playing'] is True:
|
||||
return {'type': 'silence'}
|
||||
elif new_state['radio']['playing'] is True and data['radio']['playing'] is False:
|
||||
return generate_radio_return()
|
||||
|
||||
# checking if station is correct
|
||||
if str(new_state['radio']['station']) != data['radio'].get('stationId'):
|
||||
return generate_radio_return()
|
||||
|
||||
return {} # last resort in case everything matched but cookie did not, and to not status 500
|
||||
|
||||
|
||||
@app.route('/stations')
|
||||
def stations():
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
mango = Mango('mongodb://root:test@192.168.66.113:27017')
|
||||
app.run(debug=debug, host='0.0.0.0')
|
14
better-radio/components/frontend/helpers.py
Normal file
14
better-radio/components/frontend/helpers.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def utc_time_now():
|
||||
return int(datetime.now(timezone.utc).timestamp())
|
||||
|
||||
|
||||
def check_player_offline(time):
|
||||
delta = utc_time_now() - time
|
||||
|
||||
if delta > 60:
|
||||
return True
|
||||
else:
|
||||
return False
|
150
better-radio/components/frontend/mango.py
Normal file
150
better-radio/components/frontend/mango.py
Normal file
@@ -0,0 +1,150 @@
|
||||
from pymongo import MongoClient
|
||||
from bson.objectid import ObjectId
|
||||
from secrets import token_hex
|
||||
|
||||
from helpers import *
|
||||
|
||||
|
||||
class Mango:
|
||||
# holy shit, just found out a major bug, find_one and then replace_one is really bad with concurrency.
|
||||
def __init__(self, connect):
|
||||
try:
|
||||
self.client = MongoClient(connect)
|
||||
self.locations = self.client['radio']['locations']
|
||||
self.players = self.client['radio']['players']
|
||||
self.stations = self.client['radio']['stations']
|
||||
except ConnectionError:
|
||||
print('MongoDB connection error')
|
||||
|
||||
def player_pause_radio(self, plaid):
|
||||
found = self.players.find_one({"_id": ObjectId(plaid)})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
found['new_state']['radio']['playing'] = False
|
||||
found['new_state']['radio']['cookie'] = token_hex(8)
|
||||
|
||||
self.players.replace_one({"_id": ObjectId(plaid)}, found)
|
||||
return True
|
||||
|
||||
def player_set_radio(self, plaid, staid):
|
||||
found = self.players.find_one({"_id": ObjectId(plaid)})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
if self.stations_get_station(staid):
|
||||
staid = ObjectId(staid)
|
||||
|
||||
found['new_state']['radio']['playing'] = True
|
||||
found['new_state']['radio']['station'] = staid
|
||||
found['new_state']['radio']['cookie'] = token_hex(8)
|
||||
|
||||
self.players.replace_one({"_id": ObjectId(plaid)}, found)
|
||||
return True
|
||||
|
||||
def player_update_state(self, plaid, state):
|
||||
"""
|
||||
:param plaid: str
|
||||
:param state: {'radio': {'playing': bool, 'url': str}, 'request': {'playing': bool, 'url': str}}
|
||||
:return: True on sucess, False on failure
|
||||
"""
|
||||
found = self.players.find_one({"_id": ObjectId(plaid)})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
state['last_update'] = utc_time_now()
|
||||
found['last_state'] = state
|
||||
|
||||
self.players.replace_one({"_id": ObjectId(plaid)}, found)
|
||||
return True
|
||||
|
||||
def player_get_state(self, plaid):
|
||||
found = self.players.find_one({"_id": ObjectId(plaid)})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
return found['last_state']
|
||||
|
||||
def player_get_config(self, plaid):
|
||||
found = self.players.find_one({"_id": ObjectId(plaid)})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
return found['new_state']
|
||||
|
||||
def locations_get_locations(self):
|
||||
locations = {}
|
||||
for x in self.locations.find({}):
|
||||
locations[str(x['_id'])] = x['name']
|
||||
return locations
|
||||
|
||||
def locations_get_location(self, locid):
|
||||
locid = ObjectId(locid)
|
||||
found = self.locations.find_one({'_id': locid})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
return found
|
||||
|
||||
def locations_update_enable_radio(self, locid, ena):
|
||||
locid = ObjectId(locid)
|
||||
found = self.locations.find_one({'_id': locid})
|
||||
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
found['config']['enable_radio'] = ena
|
||||
self.locations.replace_one({"_id": locid}, found)
|
||||
return True
|
||||
|
||||
def locations_update_enable_requests(self, locid, ena):
|
||||
locid = ObjectId(locid)
|
||||
found = self.locations.find_one({'_id': locid})
|
||||
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
found['config']['enable_requests'] = ena
|
||||
self.locations.replace_one({"_id": locid}, found)
|
||||
return True
|
||||
|
||||
def locations_update_enabled_stations(self, locid, lst):
|
||||
sta = []
|
||||
locid = ObjectId(locid)
|
||||
found = self.locations.find_one({'_id': locid})
|
||||
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
for x in lst:
|
||||
if self.stations_get_station(x):
|
||||
x = ObjectId(x)
|
||||
sta.append(x)
|
||||
|
||||
found['config']['enabled_stations'] = sta
|
||||
self.locations.replace_one({"_id": locid}, found)
|
||||
return True
|
||||
|
||||
def stations_get_stations(self):
|
||||
stations = self.stations.find({})
|
||||
stations_dict = {}
|
||||
|
||||
for x in stations:
|
||||
i = str(x['_id'])
|
||||
x.pop('_id')
|
||||
stations_dict[i] = x
|
||||
|
||||
return stations_dict
|
||||
|
||||
def stations_get_station(self, staid):
|
||||
staid = ObjectId(staid)
|
||||
found = self.stations.find_one({'_id': staid})
|
||||
if found is None:
|
||||
return False
|
||||
|
||||
return found
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
mango = Mango('mongodb://root:test@192.168.66.113:27017')
|
||||
print(mango.stations_get_stations())
|
10907
better-radio/components/frontend/static/bootstrap.css
vendored
Normal file
10907
better-radio/components/frontend/static/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
better-radio/components/frontend/static/bootstrap.min.css
vendored
Normal file
7
better-radio/components/frontend/static/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
146
better-radio/components/frontend/stations.json
Normal file
146
better-radio/components/frontend/stations.json
Normal file
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"stop": {
|
||||
"title": "Playing nothing",
|
||||
"country": "NaN",
|
||||
"genre": "",
|
||||
"description": "Select a radio station to play from one of the cards below",
|
||||
"stream": {
|
||||
"url": "NaN",
|
||||
"codec": "NaN",
|
||||
"bitrate": "NaN",
|
||||
"type": "NaN"
|
||||
}
|
||||
},
|
||||
"radio_swiss_classic": {
|
||||
"title": "Radio Swiss Classic",
|
||||
"country": "Switzerland",
|
||||
"genre": "Classic",
|
||||
"description": "Classical music with no ads, station based in Switzerland.",
|
||||
"stream": {
|
||||
"url": "https://stream.srg-ssr.ch/m/rsc_de/mp3_128",
|
||||
"codec": "mp3",
|
||||
"bitrate": "128k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"radio_swiss_jazz": {
|
||||
"title": "Radio Swiss Jazz",
|
||||
"country": "Switzerland",
|
||||
"genre": "Jazz",
|
||||
"description": "Jazz music with no ads, station based in Switzerland.",
|
||||
"stream": {
|
||||
"url": "https://stream.srg-ssr.ch/m/rsj/mp3_128",
|
||||
"codec": "mp3",
|
||||
"bitrate": "128k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"radio_swiss_pop": {
|
||||
"title": "Radio Swiss Pop",
|
||||
"country": "Switzerland",
|
||||
"genre": "Pop",
|
||||
"description": "Pop music with no ads, station based in Switzerland.",
|
||||
"stream": {
|
||||
"url": "https://stream.srg-ssr.ch/m/rsp/mp3_128",
|
||||
"codec": "mp3",
|
||||
"bitrate": "128k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"bbc_radio_1_dance": {
|
||||
"title": "BBC Radio 1 Dance",
|
||||
"country": "United Kingdom",
|
||||
"genre": "Dance",
|
||||
"description": "Populair dance radio station based in the United Kingdom. No ads but repetitive",
|
||||
"stream": {
|
||||
"url": "https://as-hls-ww-live.akamaized.net/pool_904/live/ww/bbc_radio_one_dance/bbc_radio_one_dance.isml/bbc_radio_one_dance-audio%3d320000.m3u8",
|
||||
"codec": "aaclc",
|
||||
"bitrate": "320k",
|
||||
"type": "hls"
|
||||
}
|
||||
},
|
||||
"public_domain_jazz": {
|
||||
"title": "Public Domain Jazz",
|
||||
"country": "Switzerland",
|
||||
"genre": "Jazz",
|
||||
"description": "Internet radio playing swing jazz in the public domain.",
|
||||
"stream": {
|
||||
"url": "http://relay.publicdomainradio.org/jazz_swing.aac",
|
||||
"codec": "aache",
|
||||
"bitrate": "64k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"sky_radio": {
|
||||
"title": "Sky Radio",
|
||||
"country": "The Netherlands",
|
||||
"genre": "Pop",
|
||||
"description": "Pop radio station with loads of ads. Why listen to this?",
|
||||
"stream": {
|
||||
"url": "https://playerservices.streamtheworld.com/api/livestream-redirect/SKYRADIO.mp3",
|
||||
"codec": "mp3",
|
||||
"bitrate": "128k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"slam": {
|
||||
"title": "SLAM!",
|
||||
"country": "The Netherlands",
|
||||
"genre": "Pop",
|
||||
"description": "Pop/Dance radio station with loads of ads.",
|
||||
"stream": {
|
||||
"url": "https://25693.live.streamtheworld.com/SLAM_AAC.aac",
|
||||
"codec": "aache",
|
||||
"bitrate": "96k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"qmusic": {
|
||||
"title": "Qmusic",
|
||||
"country": "The Netherlands",
|
||||
"genre": "Pop",
|
||||
"description": "Pop/Dance radio station with loads of ads.",
|
||||
"stream": {
|
||||
"url": "https://icecast-qmusicnl-cdp.triple-it.nl/Qmusic_nl_live_high.aac",
|
||||
"codec": "aache",
|
||||
"bitrate": "96k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"tomorrowland_owr": {
|
||||
"title": "Tomorrowland One World Radio",
|
||||
"country": "United Kingdom",
|
||||
"genre": "Pop",
|
||||
"description": "Modern pop music with some dance",
|
||||
"stream": {
|
||||
"url": "https://22353.live.streamtheworld.com/OWR_WORLD_RADIO_NL.mp3",
|
||||
"codec": "mp3",
|
||||
"bitrate": "128k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"radio_538": {
|
||||
"title": "Radio 538",
|
||||
"country": "The Netherlands",
|
||||
"genre": "Pop",
|
||||
"description": "Idk here is just some filler text for the card",
|
||||
"stream": {
|
||||
"url": "https://25533.live.streamtheworld.com/RADIO538.mp3",
|
||||
"codec": "mp3",
|
||||
"bitrate": "128k",
|
||||
"type": "direct"
|
||||
}
|
||||
},
|
||||
"radio_decibel": {
|
||||
"title": "Radio Decibel",
|
||||
"country": "The Netherlands",
|
||||
"genre": "Pop",
|
||||
"description": "Mostly playing 90's songs which is very nice",
|
||||
"stream": {
|
||||
"url": "https://23553.live.streamtheworld.com/RADIODECIBEL.mp3",
|
||||
"codec": "mp3",
|
||||
"bitrate": "192k",
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
}
|
29
better-radio/components/frontend/templates/base.html
Normal file
29
better-radio/components/frontend/templates/base.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dashboard - BS Vianen</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar navbar-inverse">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<h1>Song control</h1>
|
||||
</div>
|
||||
{% if location %}
|
||||
<div class="navbar-right">
|
||||
<a href="{{ url_for('location', locid=location['_id']) }}"><p class="lead">{{ location['name'] }}</p></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="container">
|
||||
{% include 'messages.html' %}
|
||||
</div>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
24
better-radio/components/frontend/templates/location.html
Normal file
24
better-radio/components/frontend/templates/location.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h3>Select submenu</h3>
|
||||
<div class="card-group">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Radio control</h5>
|
||||
<a href="{{ url_for('location_radio', locid=location['_id']) }}"><button type="button" class="btn btn-primary">Go</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Song request</h5>
|
||||
<a href="{{ url_for('location_request', locid=location['_id']) }}"><button type="button" class="btn btn-primary">Go</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Location config</h5>
|
||||
<a href="{{ url_for('location_config', locid=location['_id']) }}"><button type="button" class="btn btn-primary">Go</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,40 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h4>Configuration for {{ location.name }}</h4>
|
||||
{% if offline %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
Player has not been online for more than one minute, public controls have been disabled. Settings in here can still be changed.
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<h5>Enable radio control</h5>
|
||||
<form method="POST">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" name='enable' type="checkbox" role="switch" id="enable-radio" {% if location.config.enable_radio %}checked{% endif %}>
|
||||
</div>
|
||||
<input type="hidden" value="enable-radio" name="type">
|
||||
<input type="submit" value="Apply" name="request" class="btn btn-primary">
|
||||
</form>
|
||||
<hr>
|
||||
<h5>Enabled radio stations</h5>
|
||||
<form method="POST">
|
||||
{% for station in stations %}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" name='station' value="{{ station }}" type="checkbox" role="switch" id="{{ station }}" {% if ObjectId(station) in location.config.enabled_stations %}checked{% endif %}>
|
||||
<label class="form-check-label" for="{{ station }}">{{ stations[station]['title'] }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<input type="hidden" value="enabled-stations" name="type">
|
||||
<input type="submit" value="Apply" name="request" class="btn btn-primary">
|
||||
</form>
|
||||
<hr>
|
||||
<h5>Enable song requests</h5>
|
||||
<form method="POST">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" name='enable' type="checkbox" role="switch" id="enable-requests" {% if location.config.enable_requests %}checked{% endif %} disabled>
|
||||
<label class="form-check-label" for="enable-requests">Feature is not available yet</label>
|
||||
</div>
|
||||
<input type="hidden" value="enable-requests" name="type">
|
||||
<input type="submit" value="Apply" name="request" class="btn btn-primary">
|
||||
</form>
|
||||
{% endblock %}
|
@@ -0,0 +1,59 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<meta http-equiv="refresh" content="60">
|
||||
<h5>Radio station control</h5>
|
||||
{% if not enabled %}
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
{% if current %}Should be playing{% else %}Should be playing nothing{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% if current %}{{ stations[current]['title'] }}{% else %}No control until player is enabled{% endif %}</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">{% if current %}{{ stations[current]['genre'] }}{% endif %}</h6>
|
||||
<p class="card-text">{% if current %}{{ stations[current]['description'] }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="card-footer bg-warning">
|
||||
<p>Control of player has been disabled or connection to player has been lost</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
{% if current %}Now playing{% else %}Playing nothing{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% if current %}{{ stations[current]['title'] }}{% else %}Select a radio station from below to play{% endif %}</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">{% if current %}{{ stations[current]['genre'] }}{% endif %}</h6>
|
||||
<p class="card-text">{% if current %}{{ stations[current]['description'] }}{% endif %}</p>
|
||||
{% if current %}
|
||||
<form method="POST">
|
||||
<input type="hidden" value="stop-stream" name="type">
|
||||
<input type="submit" value="Stop" name="request" class="btn btn-danger">
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-2">
|
||||
{% for station in stations %}
|
||||
{% if ObjectId(station) in location.config.enabled_stations and station != current %}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ stations[station]['title'] }}</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">{{ stations[station]['genre'] }}</h6>
|
||||
<p class="card-text">{{ stations[station]['description'] }}</p>
|
||||
<form method="POST">
|
||||
<input type="hidden" value="select-station" name="type">
|
||||
<input type="hidden" value="{{ station }}" name="station">
|
||||
<input type="submit" value="Play" name="request" class="btn btn-primary">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@@ -0,0 +1,9 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h4>Song request for {{ location.name }}</h4>
|
||||
{% if location.config.enable_requests is false %}
|
||||
<p>Song request is disabled by site administrator</p>
|
||||
{% else %}
|
||||
<p>Page content here</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
13
better-radio/components/frontend/templates/locations.html
Normal file
13
better-radio/components/frontend/templates/locations.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="card-columns">
|
||||
{% for location in locations %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ locations[location] }}</h5>
|
||||
<a href="/location/{{ location }}"><button type="button" class="btn btn-primary">Go</button></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
7
better-radio/components/frontend/templates/messages.html
Normal file
7
better-radio/components/frontend/templates/messages.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% for message in get_flashed_messages() %}
|
||||
<meta http-equiv="refresh" content="6">
|
||||
<div class="alert alert-primary" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
<hr>
|
||||
{% endfor %}
|
6
better-radio/components/player/controller/Dockerfile
Normal file
6
better-radio/components/player/controller/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM python:3
|
||||
WORKDIR /usr/src/app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY control.py .
|
||||
CMD ["python3", "./control.py"]
|
189
better-radio/components/player/controller/control.py
Normal file
189
better-radio/components/player/controller/control.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import time
|
||||
import requests
|
||||
from random import randint
|
||||
import logging
|
||||
from os import environ
|
||||
|
||||
radio_name = environ.get('radio_name', default='controlled_radio')
|
||||
player_name = environ.get('player_name', default='controlled_player')
|
||||
player_id = environ.get('playerid', default='6307651e660410d3678d1fa3')
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, name):
|
||||
import docker
|
||||
|
||||
self.name = name
|
||||
self.ct = None
|
||||
self.docker = docker
|
||||
|
||||
logging.debug('Setting up docker sock')
|
||||
self.client = docker.DockerClient(base_url='unix:///var/run/docker.sock')
|
||||
|
||||
self.check_exists()
|
||||
|
||||
def start_new(self, stream, restart='no', labels=None):
|
||||
if labels is None:
|
||||
labels = {}
|
||||
if self.ct:
|
||||
logging.info('Not starting a new container under this class object because a container already exists')
|
||||
return
|
||||
|
||||
if restart != 'no' and restart != 'always':
|
||||
logging.info('Not a valid restart policy, must be always or no')
|
||||
return
|
||||
|
||||
logging.debug('Running new container')
|
||||
self.ct = self.client.containers.run('4grxfq/player',
|
||||
name=self.name,
|
||||
devices=['/dev/snd'],
|
||||
environment={'FF_URL': stream},
|
||||
restart_policy={'Name': restart},
|
||||
labels=labels,
|
||||
detach=True)
|
||||
|
||||
def start(self):
|
||||
if not self.ct:
|
||||
logging.info('Cannot start container under this class because no container has been created yet')
|
||||
return
|
||||
|
||||
logging.debug('Starting existing container object')
|
||||
self.ct.start()
|
||||
|
||||
def stop(self):
|
||||
if not self.ct:
|
||||
logging.info('Not stopping container because no such object exists')
|
||||
return
|
||||
|
||||
logging.debug('Stopping container')
|
||||
self.ct.stop()
|
||||
|
||||
def remove(self):
|
||||
if not self.ct:
|
||||
logging.info('Not removing container because no such object exists')
|
||||
return
|
||||
|
||||
logging.debug('Deleting container')
|
||||
self.ct.remove(force=True)
|
||||
self.ct = None
|
||||
|
||||
def check_exists(self):
|
||||
try:
|
||||
logging.debug('Checking if container already exists under given name')
|
||||
self.ct = self.client.containers.get(self.name)
|
||||
return True
|
||||
except self.docker.errors.NotFound:
|
||||
logging.debug('Container does not exist continuing clean')
|
||||
self.ct = None
|
||||
return False
|
||||
|
||||
def check_status(self):
|
||||
if not self.ct:
|
||||
logging.info('Cannot query container because no such object exists')
|
||||
return None
|
||||
|
||||
self.ct.reload()
|
||||
if self.ct.status == 'running':
|
||||
logging.info('Container is running')
|
||||
return True
|
||||
else:
|
||||
logging.info('Container is not running')
|
||||
return False
|
||||
|
||||
def get_labels(self):
|
||||
if not self.ct:
|
||||
logging.info('Cannot query container because no such object exists')
|
||||
return None
|
||||
|
||||
self.ct.reload()
|
||||
return self.ct.labels
|
||||
|
||||
|
||||
def request_config(p_id, stat):
|
||||
try:
|
||||
logging.debug('Requesting new config from controller')
|
||||
r = requests.post(f'http://192.168.99.20:5000/players/{p_id}/config', timeout=9, json=stat)
|
||||
|
||||
if r.status_code != 200:
|
||||
logging.error('Config request not code 200, reporting')
|
||||
return None
|
||||
|
||||
logging.debug(r.json())
|
||||
return r.json()
|
||||
|
||||
except requests.exceptions.RequestException as exc:
|
||||
logging.error(f'Requesting config excepted: {exc}')
|
||||
|
||||
|
||||
# def generate_status(play, radi):
|
||||
def generate_status(radi):
|
||||
# give the player and radio objects to generate the status from
|
||||
# stat = {'radio': {}, 'player': {}}
|
||||
stat = {'radio': {}}
|
||||
|
||||
# if play.check_status(): # if player container exists, it removes itself otherwise
|
||||
# stat['player']['playing'] = True
|
||||
# else:
|
||||
# stat['player']['playing'] = False
|
||||
#
|
||||
# if play.get_labels() is not None:
|
||||
# stat['player']['labels']['stationId'] = play.get_labels().get('requestId')
|
||||
# stat['player']['labels']['url'] = play.get_labels().get('url')
|
||||
# stat['player']['labels']['cookie'] = play.get_labels().get('cookie')
|
||||
# else:
|
||||
# stat['player']['labels'] = None
|
||||
|
||||
if radi.check_status(): # check if radio is playing
|
||||
stat['radio']['playing'] = True
|
||||
else:
|
||||
stat['radio']['playing'] = False
|
||||
|
||||
radi_labels = radi.get_labels()
|
||||
if radi_labels is not None:
|
||||
stat['radio']['stationId'] = radi_labels.get('stationId', '')
|
||||
stat['radio']['url'] = radi_labels.get('url', '')
|
||||
stat['radio']['cookie'] = radi_labels.get('cookie', '')
|
||||
|
||||
logging.debug(stat)
|
||||
return stat
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
radio = Player(radio_name)
|
||||
# player = Player(player_name)
|
||||
|
||||
while True:
|
||||
# status = generate_status(player, radio)
|
||||
status = generate_status(radio)
|
||||
config = request_config(player_id, status)
|
||||
|
||||
if config:
|
||||
if config.get('type') == 'radio':
|
||||
# player.remove()
|
||||
|
||||
if config.get('stationId') == status['radio'].get('stationId'):
|
||||
radio.start()
|
||||
continue
|
||||
else:
|
||||
radio.remove()
|
||||
radio.start_new(config.get('url'), restart='always', labels={'url': config.get('url'),
|
||||
'stationId': config.get('stationId'),
|
||||
'cookie': config.get('cookie')})
|
||||
continue
|
||||
|
||||
# elif config.get('type') == 'request':
|
||||
# radio.stop()
|
||||
# player.start_new(config.get('url'), restart='no', labels={'url': config.get('url'),
|
||||
# 'requestId': config.get('requestId'),
|
||||
# 'cookie': config.get('cookie')})
|
||||
|
||||
elif config.get('type') == 'silence':
|
||||
# player.remove()
|
||||
radio.stop()
|
||||
continue
|
||||
|
||||
else:
|
||||
logging.debug('Nothing changed from previous request')
|
||||
|
||||
logging.debug('Sleeping')
|
||||
time.sleep(randint(3, 6))
|
@@ -0,0 +1,9 @@
|
||||
version: "2"
|
||||
services:
|
||||
controller:
|
||||
image: 4grxfq/controller:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
playerid: '6307651e660410d3678d1fa3'
|
||||
restart: always
|
@@ -0,0 +1,2 @@
|
||||
docker
|
||||
requests
|
3
better-radio/components/player/controller/update.sh
Normal file
3
better-radio/components/player/controller/update.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
docker build . -t 4grxfq/controller:latest
|
||||
docker push 4grxfq/controller:latest
|
3
better-radio/components/player/player/Dockerfile
Normal file
3
better-radio/components/player/player/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM alpine:latest
|
||||
RUN apk add --update alsa-utils ffmpeg && rm -rf /var/cache/apk/*
|
||||
CMD sh -c 'ffmpeg -i $FF_URL -c copy -f asf - | ffplay - -nodisp -autoexit'
|
@@ -4,7 +4,7 @@ from argon2 import PasswordHasher # PIP: argon2-cffi
|
||||
sys.tracebacklimit = 0 # disable tracebacks to hide potential secrets
|
||||
|
||||
ph = PasswordHasher() # setup password hasher object with default parameters
|
||||
hash = "$argon2id$v=19$m=65536,t=32,p=4$OvFXwX3buE9xYLKPGs2NOA$UEtxEwOI8VZ9WwWMGaySAhBdgP+zuLJIzCz4+Xt1YDw" example hash
|
||||
hash = "$argon2id$v=19$m=65536,t=32,p=4$OvFXwX3buE9xYLKPGs2NOA$UEtxEwOI8VZ9WwWMGaySAhBdgP+zuLJIzCz4+Xt1YDw" # example hash
|
||||
peers = {'CORE1': {'endpoint': '1.1.1.1', 'status': True}, 'CORE2': {'endpoint': '1.1.1.2', 'status': False}} # temporary testing dict
|
||||
|
||||
|
||||
|
25
docker-mutual-tls/README.md
Normal file
25
docker-mutual-tls/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Docker Mutual TLS
|
||||
This script generates a linked mutual TLS keys and certificates
|
||||
|
||||
## Usage
|
||||
Run the script and give the fqdn of the host you are setting up. Then the script will leave 4 files. server-key.pem server-cert.pem client-key.pem client-cert.pem. The other files are automatically cleaned up
|
||||
|
||||
## Requirements
|
||||
Bash and openssl
|
||||
|
||||
## Install
|
||||
- First of all make sure that systemd is not hijacking the commandline options, if so on Debian the system service is at /usr/lib/systemd/system/docker.service and remove the commandline options which are overridden in the configuration file below.
|
||||
- Copy the configuration of the codeblock below to /etc/docker/daemon.json
|
||||
- Install the certificates in the correct locations
|
||||
- restart the systemd service (all containers will be restarted as well!!!)
|
||||
|
||||
```
|
||||
{
|
||||
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
|
||||
"tls": true,
|
||||
"tlscacert": "/etc/docker/ca.pem",
|
||||
"tlscert": "/etc/docker/server-cert.pem",
|
||||
"tlskey": "/etc/docker/server-key.pem",
|
||||
"tlsverify": true
|
||||
}
|
||||
```
|
18
docker-mutual-tls/gen_mutual_tls.sh
Normal file
18
docker-mutual-tls/gen_mutual_tls.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
echo -n "What is the FQDN of the desired server?: "
|
||||
read fqdn
|
||||
echo "Using FQDN: $fqdn"
|
||||
|
||||
|
||||
openssl genrsa -out ca-key.pem 4096
|
||||
yes "" | openssl req -new -x509 -days 3650 -key ca-key.pem -sha256 -out ca.pem
|
||||
|
||||
openssl genrsa -out server-key.pem 4096
|
||||
openssl req -subj "/CN=$fqdn" -addext "subjectAltName = DNS:$fqdn" -addext "extendedKeyUsage = serverAuth" -sha256 -new -key server-key.pem -out server.csr
|
||||
openssl x509 -req -days 3650 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem
|
||||
|
||||
openssl genrsa -out client-key.pem 4096
|
||||
openssl req -subj "/CN=client" -addext "extendedKeyUsage = clientAuth" -new -key client-key.pem -out client.csr
|
||||
openssl x509 -req -days 3650 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -out client-cert.pem
|
||||
|
||||
rm -v client.csr server.csr ca.pem ca-key.pem
|
||||
echo "Finished creating certificate pairs"
|
BIN
makkelijk_uurlijks_rapport/dist/makkelijk_rapport.exe
vendored
Normal file
BIN
makkelijk_uurlijks_rapport/dist/makkelijk_rapport.exe
vendored
Normal file
Binary file not shown.
5
makkelijk_uurlijks_rapport/generate_exe.bat
Normal file
5
makkelijk_uurlijks_rapport/generate_exe.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
pyinstaller --clean --splash logo_s.png --onefile .\makkelijk_rapport.py
|
||||
rd /S /Q build
|
||||
del /F /Q makkelijk_rapport.spec
|
||||
pause
|
BIN
makkelijk_uurlijks_rapport/logo_s.png
Normal file
BIN
makkelijk_uurlijks_rapport/logo_s.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
@@ -1,6 +1,9 @@
|
||||
import pyperclip
|
||||
import pyi_splash
|
||||
from time import sleep
|
||||
from sys import exit
|
||||
|
||||
gebied = ['FUS', 'BLK', 'DBP', 'DKW', 'KKP', 'PAL'] # positioning matters!
|
||||
gebied = ['FUS', 'BLK', 'DBP', 'DKW', 'KKP', 'PAL'] # positioning matters for excel sheet!
|
||||
|
||||
def ring_the_bell(n=1):
|
||||
for n in range(n):
|
||||
@@ -8,68 +11,107 @@ def ring_the_bell(n=1):
|
||||
|
||||
def check_correct_copy(clip):
|
||||
# check if copied correctly
|
||||
if clip[0:4] == '0000':
|
||||
if clip[0:3] == '000':
|
||||
return True
|
||||
print('!!! You copied the menu incorrectly. See https://git.ventilaar.nl/ventilaar/slg_tools/src/branch/master/makkelijk_uurlijks_rapport/kopie_voorbeeld.png for an example !!!')
|
||||
print('!!! Incorrect copy, please start at 000 and finish at PAL colli !!!')
|
||||
return False
|
||||
|
||||
def nested_list_from_bare_copy(clip):
|
||||
# create nested list with values based on position
|
||||
cliplines = []
|
||||
for x in clip.split('\r\n'):
|
||||
line = []
|
||||
for i in x.split(' '):
|
||||
if i:
|
||||
count = 0
|
||||
line.append(i)
|
||||
elif count == 12:
|
||||
count = 0
|
||||
line.append('0')
|
||||
else:
|
||||
count = count + 1
|
||||
cliplines.append(line)
|
||||
return cliplines
|
||||
try:
|
||||
for x in clip.split('\r\n'): # voor elke nieuwe regel
|
||||
line = [] # lege regel aanmaken
|
||||
|
||||
for i in x.split(' '): #
|
||||
if i: # positie is niet leeg
|
||||
count = 0
|
||||
line.append(i)
|
||||
elif count == 12: # te veel lege posities ontvangen, kolom is leeg en dus 0
|
||||
count = 0
|
||||
line.append('0')
|
||||
else: # positie is leeg
|
||||
count = count + 1
|
||||
|
||||
cliplines.append(line) # regel is compleet toevoegen aan geneste lijst
|
||||
|
||||
return cliplines # geneste lijst teruggeven
|
||||
except IndexError:
|
||||
print('!!! Incorrect copy, please start at 000 and finish at PAL colli !!!')
|
||||
return False
|
||||
|
||||
def nested_list_to_dict(clip):
|
||||
# create dictionary based on gebied for a better overview of the data
|
||||
# create dictionary based on gebied for a better overview of the data
|
||||
data = {}
|
||||
data['totaal_colli'] = 0
|
||||
colli = 0
|
||||
for x in clip:
|
||||
if x[2] in gebied:
|
||||
if len(x) != 8:
|
||||
print('!!! Something went wrong with assuming the empty rows. Please double check manually !!!')
|
||||
|
||||
data[x[2]] = {}
|
||||
data[x[2]]['totaal'] = int(x[3])
|
||||
data[x[2]]['gedaan'] = int(x[4])
|
||||
data[x[2]]['tedoen'] = int(x[5]) + int(x[6])
|
||||
data['totaal_colli'] = data['totaal_colli'] + int(x[-1])
|
||||
return data
|
||||
try:
|
||||
for x in clip: # voor elke gebied
|
||||
if x[2] in gebied: # als gebied van ons is
|
||||
if len(x) != 8: # totaal aantal colomen kloppen niet iets is fout gegeaan in nested_list_from_bare_copy()
|
||||
print('!!! Something went wrong with assuming the empty rows. Please double check manually !!!')
|
||||
|
||||
data[x[2]] = {} # genereer het verzamelgebied
|
||||
data[x[2]]['totaal'] = int(x[3]) # kopieer totaal regels kolom
|
||||
data[x[2]]['gedaan'] = int(x[4]) # kopieer ingenomen regels kolom
|
||||
data[x[2]]['tedoen'] = int(x[5]) + int(x[6]) # tel te plannen en gepland kolomen bijelkaar op
|
||||
data['totaal_colli'] = data['totaal_colli'] + int(x[-1]) # tel de collis van alle gebieden bijelkaar op
|
||||
|
||||
return data
|
||||
|
||||
except IndexError:
|
||||
print('!!! Incorrect copy, please start at 000 and finish at PAL colli !!!')
|
||||
return False
|
||||
|
||||
def generate_clipboard(data):
|
||||
# generate clipboard
|
||||
copyhand = ''
|
||||
for x in gebied:
|
||||
copyhand = f"{copyhand}{data[x]['gedaan']}\t{data[x]['tedoen']}\t"
|
||||
return f"{copyhand}{data['totaal_colli']}"
|
||||
copyhand = '' # start met een leeg hand
|
||||
|
||||
for x in gebied: # voor elk gebied
|
||||
if x not in data: # Gebied komt morgen (nog) niet voor
|
||||
copyhand = f"{copyhand}0\t0\t" # Zet hand bij (nog) niet voorkomend gebied op 0
|
||||
else:
|
||||
copyhand = f"{copyhand}{data[x]['gedaan']}\t{data[x]['tedoen']}\t" # voeg de juiste data toe in de hand
|
||||
|
||||
return f"{copyhand}{data['totaal_colli']}" # hand afsluiten met totaal colli
|
||||
|
||||
def main():
|
||||
try:
|
||||
paste = pyperclip.waitForNewPaste()
|
||||
if not check_correct_copy(paste):
|
||||
return
|
||||
|
||||
nested_list = nested_list_from_bare_copy(paste)
|
||||
if not nested_list:
|
||||
return
|
||||
|
||||
nested_dict = nested_list_to_dict(nested_list)
|
||||
if not nested_dict:
|
||||
return
|
||||
|
||||
copyhand = generate_clipboard(nested_dict)
|
||||
|
||||
print(copyhand)
|
||||
pyperclip.copy(copyhand)
|
||||
ring_the_bell()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('Exiting')
|
||||
exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Listening for new clipboard updates. When you hear a bell you copied correctly, the resulting conversion has replaced your clipboard')
|
||||
print('Welcome to the easy copy tool version 2.1!')
|
||||
print('Listening for copy events, when you copy correctly a bell will ring.')
|
||||
print('')
|
||||
print('Copy example: https://git.ventilaar.nl/ventilaar/random-scripts/src/branch/master/makkelijk_uurlijks_rapport/kopie_voorbeeld.png')
|
||||
print('Source code: https://git.ventilaar.nl/ventilaar/random-scripts/src/branch/master/makkelijk_uurlijks_rapport')
|
||||
print('-'*79)
|
||||
|
||||
pyi_splash.close()
|
||||
while True:
|
||||
try:
|
||||
paste = pyperclip.waitForNewPaste()
|
||||
if not check_correct_copy(paste):
|
||||
continue
|
||||
copyhand = generate_clipboard(nested_list_to_dict(nested_list_from_bare_copy(paste)))
|
||||
print(copyhand)
|
||||
pyperclip.copy(copyhand)
|
||||
ring_the_bell()
|
||||
except KeyboardInterrupt:
|
||||
print('Exiting')
|
||||
exit(0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
main()
|
||||
except pyperclip.PyperclipWindowsException:
|
||||
print('Windows is locked, retrying clipboard monitoring in 6 seconds')
|
||||
sleep(6)
|
8
matrix-notify/README.md
Normal file
8
matrix-notify/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Matrix Notify
|
||||
Simple script to notify me in a special m matrix room regarding push messages.
|
||||
|
||||
# Use
|
||||
change the roomId in the script and link to the correct homeserver. Change the AS token to the correct one generated and set-up in your Synapse application service.
|
||||
|
||||
# Requires python libraries
|
||||
- requests
|
47
matrix-notify/message.py
Normal file
47
matrix-notify/message.py
Normal file
@@ -0,0 +1,47 @@
|
||||
def send_message(service, message):
|
||||
import requests
|
||||
|
||||
asToken = 'private'
|
||||
roomId = '!3rhj02839hr023r:example.com'
|
||||
homeserver = 'matrix.ventilaar.net'
|
||||
|
||||
apiUrl = f'https://{homeserver}/_matrix/client/v3/rooms/{roomId}/send/m.room.message'
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {asToken}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
event = {
|
||||
'msgtype': 'm.text',
|
||||
'body': f'Message from: {service} | Message: {message}',
|
||||
'format': 'org.matrix.custom.html',
|
||||
'formatted_body': f'Message from: <b>{service}</b><br><code>{message}</code>'
|
||||
}
|
||||
|
||||
try:
|
||||
r = requests.post(apiUrl, headers=headers, json=event)
|
||||
|
||||
if r.status_code != 200:
|
||||
print(f'Failed to send message, got exit code {r.status_code}')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f'Some unexpected thing happened: {e}')
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
args = sys.argv[1:]
|
||||
|
||||
if len(args) != 2:
|
||||
print('Usage: scriptname.py "service" "message"')
|
||||
elif len(args) == 1:
|
||||
send_message('selftest', 'This is a message to test the script itself, it got not parameters!')
|
||||
|
||||
if send_message(args[0], args[1]):
|
||||
exit(0)
|
||||
|
||||
exit(1)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user