Basic extension working
3
extension/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"presets": ["es2015"]
|
||||||
|
}
|
||||||
3
extension/.bowerrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"directory": "app/bower_components"
|
||||||
|
}
|
||||||
24
extension/.editorconfig
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# Change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# We recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
1
extension/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
||||||
10
extension/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
temp
|
||||||
|
.tmp
|
||||||
|
dist
|
||||||
|
.sass-cache
|
||||||
|
app/bower_components
|
||||||
|
test/bower_components
|
||||||
|
package
|
||||||
|
app/scripts
|
||||||
|
|
||||||
6
extension/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"workbench.colorTheme": "Atom One Light",
|
||||||
|
"files.exclude": {
|
||||||
|
"app/scripts": true
|
||||||
|
}
|
||||||
|
}
|
||||||
59
extension/.working/index.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Pod::Noms Test</title>
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starter-template {
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main role="main"
|
||||||
|
class="container">
|
||||||
|
<div class="starter-template">
|
||||||
|
<h1>Pod::Noms test page</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<audio controls=""
|
||||||
|
preload="none">
|
||||||
|
<source type="audio/mpeg"
|
||||||
|
src="https://podnomscdn.blob.core.windows.net/audio/d34c8cd4-22cd-4a01-a140-6a9cf470051b.mp3">
|
||||||
|
<a target="_blank"
|
||||||
|
href="https://podnomscdn.blob.core.windows.net/audio/d34c8cd4-22cd-4a01-a140-6a9cf470051b.mp3">
|
||||||
|
Fish Go Deep Radio 2017-24-320</a>
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<a target="_blank"
|
||||||
|
href="https://podnomscdn.blob.core.windows.net/audio/d34c8cd4-22cd-4a01-a140-6a9cf470051b.mp3">
|
||||||
|
Here is some audio
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<a target="_blank"
|
||||||
|
href="https://podnomscdn.blob.core.windows.net/audio/183ae2b3-2bec-4726-aa29-925f92b23ba3.mp3">
|
||||||
|
Here is some short audio
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
|
||||||
|
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
6
extension/.yo-rc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"generator-mocha": {
|
||||||
|
"ui": "tdd",
|
||||||
|
"rjs": false
|
||||||
|
}
|
||||||
|
}
|
||||||
10
extension/app/_locales/en/messages.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"appName": {
|
||||||
|
"message": "podnoms",
|
||||||
|
"description": "The name of the application"
|
||||||
|
},
|
||||||
|
"appDescription": {
|
||||||
|
"message": "Robot Powered Podcasts",
|
||||||
|
"description": "The description of the application"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
extension/app/env.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
window.env = {
|
||||||
|
AUTH0_DOMAIN: 'podnoms.eu.auth0.com',
|
||||||
|
AUTH0_CLIENT_ID: 'Fx6Z3kZoheEpXhZO97ioAg1asbHDdCtr'
|
||||||
|
};
|
||||||
BIN
extension/app/images/icon-128.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
extension/app/images/icon-16.png
Normal file
|
After Width: | Height: | Size: 964 B |
BIN
extension/app/images/icon-19-disabled.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
extension/app/images/icon-19.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
extension/app/images/icon-38-disabled.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
extension/app/images/icon-38.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
extension/app/images/icon-48.png
Executable file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
extension/app/images/icon.png
Executable file
|
After Width: | Height: | Size: 8.2 KiB |
38
extension/app/manifest.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "__MSG_appName__",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"description": "__MSG_appDescription__",
|
||||||
|
"icons": {
|
||||||
|
"16": "images/icon-16.png",
|
||||||
|
"128": "images/icon-128.png"
|
||||||
|
},
|
||||||
|
"default_locale": "en",
|
||||||
|
"permissions": ["tabs", "identity", "notifications", "storage"],
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"./env.js",
|
||||||
|
"scripts/chromereload.js",
|
||||||
|
"scripts/auth.js",
|
||||||
|
"node_modules/auth0-chrome/dist/auth0chrome.min.js",
|
||||||
|
"scripts/background.js"
|
||||||
|
],
|
||||||
|
"persistent": false
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["http://*/*", "https://*/*"],
|
||||||
|
"js": ["scripts/lib/jquery.js", "scripts/contentscript.js"],
|
||||||
|
"run_at": "document_end",
|
||||||
|
"all_frames": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"browser_action": {
|
||||||
|
"default_icon": {
|
||||||
|
"19": "images/icon-19.png",
|
||||||
|
"38": "images/icon-38.png"
|
||||||
|
},
|
||||||
|
"default_title": "podnoms",
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
extension/app/options.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- build:css styles/options-vendor.css -->
|
||||||
|
<!-- bower:css -->
|
||||||
|
<!-- endbower -->
|
||||||
|
<!-- endbuild -->
|
||||||
|
<!-- build:css styles/main.css -->
|
||||||
|
<link href="styles/main.css" rel="stylesheet">
|
||||||
|
<!-- endbuild -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>'Allo, 'Allo!</h1>
|
||||||
|
|
||||||
|
<!-- build:js scripts/options-vendor.js -->
|
||||||
|
<!-- bower:js -->
|
||||||
|
<!-- endbower -->
|
||||||
|
<!-- endbuild -->
|
||||||
|
|
||||||
|
<!-- build:js scripts/options.js -->
|
||||||
|
<script src="scripts/options.js"></script>
|
||||||
|
<!-- endbuild -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
119
extension/app/popup.html
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="../styles/bootstrap.css">
|
||||||
|
<style>
|
||||||
|
.hidden {
|
||||||
|
display: None;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 0px;
|
||||||
|
animation: loading 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(1) {
|
||||||
|
background-color: #eb5424;
|
||||||
|
animation-delay: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(2) {
|
||||||
|
background-color: #44c7f4;
|
||||||
|
animation-delay: 0.09s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(3) {
|
||||||
|
background-color: #16214d;
|
||||||
|
animation-delay: .18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar:nth-child(4) {
|
||||||
|
background-color: #eaca2c;
|
||||||
|
animation-delay: .27s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: scale(1, 2.2);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="width: 320px;">
|
||||||
|
<nav class="navbar navbar-default"
|
||||||
|
role="navigation">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand"
|
||||||
|
href="#">Pod::Noms</a>
|
||||||
|
<button class="btn btn-success pull-right retry-button"><i class="fa fa-refresh"></i></button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="container">
|
||||||
|
<div class="error alert alert-danger">
|
||||||
|
There was an error communicating with the PodNoms API
|
||||||
|
</div>
|
||||||
|
<div class="loggedin">
|
||||||
|
Choose podcast:
|
||||||
|
<select class="combobox input-large form-control">
|
||||||
|
</select>
|
||||||
|
<hr />
|
||||||
|
<div id="no-audio">
|
||||||
|
No audio found on page
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped"
|
||||||
|
id="results-table">
|
||||||
|
<thead>
|
||||||
|
<td>Title</td>
|
||||||
|
<td>Size</td>
|
||||||
|
<td>Action</td>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div class="loading">
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
<div class="loading-bar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="logout">
|
||||||
|
<div class="text-center">
|
||||||
|
<button class="btn btn-danger logout">Log Out</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login">
|
||||||
|
<div class="text-center">
|
||||||
|
<button class="btn btn-success login-button">Log In</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<script src="../../env.js"></script>
|
||||||
|
<script src="./scripts/lib/jquery.js"></script>
|
||||||
|
<script src="./scripts/lib/fontawesome.js"></script>
|
||||||
|
<script src="./scripts/popup.js"></script>
|
||||||
|
<script src="../../node_modules/jwt-decode/build/jwt-decode.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
28
extension/app/scripts.babel/auth.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
console.log('auth.js');
|
||||||
|
chrome.runtime.onMessage.addListener(function(event, sender, sendResponse) {
|
||||||
|
if (event.type === 'authenticate') {
|
||||||
|
let options = {
|
||||||
|
scope: 'openid profile offline_access',
|
||||||
|
device: 'chrome-extension'
|
||||||
|
};
|
||||||
|
new Auth0Chrome(env.AUTH0_DOMAIN, env.AUTH0_CLIENT_ID)
|
||||||
|
.authenticate(options)
|
||||||
|
.then(function(authResult) {
|
||||||
|
localStorage.authResult = JSON.stringify(authResult);
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
iconUrl: 'images/icon-128.png',
|
||||||
|
title: 'Login Successful',
|
||||||
|
message: 'You can use the app now'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
title: 'Login Failed',
|
||||||
|
message: err.message,
|
||||||
|
iconUrl: 'images/icon-128.png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
25
extension/app/scripts.babel/background.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
'use strict';
|
||||||
|
console.log('Background');
|
||||||
|
chrome.runtime.onMessage.addListener(function(event, sender, sendResponse) {
|
||||||
|
if (event.type === 'updatebadge') {
|
||||||
|
chrome.tabs.get(sender.tab.id, function(tab) {
|
||||||
|
// chrome.pageAction.show(tab.id);
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tab.index >= 0) {
|
||||||
|
chrome.browserAction.setBadgeBackgroundColor({ tabId: tab.id, color: [255, 0, 0, 255] });
|
||||||
|
chrome.browserAction.setBadgeText({ tabId: tab.id, text: event.data });
|
||||||
|
} else {
|
||||||
|
var tabId = sender.tab.id,
|
||||||
|
text = event.data;
|
||||||
|
chrome.webNavigation.onCommitted.addListener(function update(details) {
|
||||||
|
if (details.tabId == tabId) {
|
||||||
|
chrome.browserAction.setBadgeText({ tabId: tab.id, text: event.data });
|
||||||
|
chrome.webNavigation.onCommitted.removeListener(update);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
35
extension/app/scripts.babel/chromereload.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Reload client for Chrome Apps & Extensions.
|
||||||
|
// The reload client has a compatibility with livereload.
|
||||||
|
// WARNING: only supports reload command.
|
||||||
|
|
||||||
|
const LIVERELOAD_HOST = 'localhost:';
|
||||||
|
const LIVERELOAD_PORT = 35729;
|
||||||
|
const connection = new WebSocket('ws://' + LIVERELOAD_HOST + LIVERELOAD_PORT + '/livereload');
|
||||||
|
|
||||||
|
var lastReload = false;
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener(function(details) {
|
||||||
|
lastReload = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.onerror = error => {
|
||||||
|
console.log('reload connection got error:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onmessage = e => {
|
||||||
|
if (e.data) {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
if (data && data.command === 'reload') {
|
||||||
|
var currentTime = Date.now();
|
||||||
|
if (lastReload && currentTime - lastReload > 60000) {
|
||||||
|
// don't reload more than once a minute
|
||||||
|
chrome.runtime.reload();
|
||||||
|
chrome.developerPrivate.reload(chrome.runtime.id,
|
||||||
|
{failQuietly: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
17
extension/app/scripts.babel/contentscript.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
console.log('Content script', chrome.runtime.id);
|
||||||
|
|
||||||
|
function onRequest(request, sender, sendResponse) {
|
||||||
|
if (request.action === 'process-page') {
|
||||||
|
const array = $.map(links, (n, i) => {
|
||||||
|
return { href: n.href, text: n.text };
|
||||||
|
});
|
||||||
|
sendResponse(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chrome.runtime.onMessage.addListener(onRequest);
|
||||||
|
|
||||||
|
const links = $(document).find('a[href$="mp3"]');
|
||||||
|
if (links && links.length !== 0) {
|
||||||
|
chrome.runtime.sendMessage({ type: 'updatebadge', data: '' + links.length });
|
||||||
|
}
|
||||||
3
extension/app/scripts.babel/options.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
console.log('\'Allo \'Allo! Option');
|
||||||
149
extension/app/scripts.babel/popup.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
function isLoggedIn(token) {
|
||||||
|
return jwt_decode(token).exp > Date.now() / 1000;
|
||||||
|
}
|
||||||
|
function logout() {
|
||||||
|
localStorage.clear();
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
function humanFileSize(bytes, si) {
|
||||||
|
var thresh = si ? 1000 : 1024;
|
||||||
|
if (Math.abs(bytes) < thresh) {
|
||||||
|
return bytes + ' B';
|
||||||
|
}
|
||||||
|
var units = si
|
||||||
|
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
|
var u = -1;
|
||||||
|
do {
|
||||||
|
bytes /= thresh;
|
||||||
|
++u;
|
||||||
|
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||||
|
return bytes.toFixed(1) + ' ' + units[u];
|
||||||
|
}
|
||||||
|
function postPodcast(event) {
|
||||||
|
const authResult = JSON.parse(localStorage.authResult || '{}');
|
||||||
|
const title = $(event.currentTarget).data('title');
|
||||||
|
const url = $(event.currentTarget).data('url');
|
||||||
|
const podcastId = $('.combobox').val();
|
||||||
|
const data = {
|
||||||
|
podcastId: podcastId,
|
||||||
|
title: title,
|
||||||
|
sourceUrl: url,
|
||||||
|
processed: false
|
||||||
|
};
|
||||||
|
fetch(`https://www.podnoms.com/api/entry/`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${authResult.id_token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(r => {
|
||||||
|
if (r.status === 200) {
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
iconUrl: 'images/icon-128.png',
|
||||||
|
title: 'Podcast Added Succesfully',
|
||||||
|
message: 'It should show up in your podcaster soon.'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
iconUrl: 'images/icon-128.png',
|
||||||
|
title: 'Error adding podcast',
|
||||||
|
message: 'Please try adding manually on the site.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function renderLinks(links) {
|
||||||
|
// const links = JSON.parse(data);
|
||||||
|
if (links && links.length !== 0) {
|
||||||
|
$('#no-audio').hide();
|
||||||
|
$('#results-table').show();
|
||||||
|
$('table tbody')
|
||||||
|
.children()
|
||||||
|
.remove();
|
||||||
|
$(links).each((i, d) => {
|
||||||
|
var xhr = $.ajax({
|
||||||
|
type: 'HEAD',
|
||||||
|
url: d.href,
|
||||||
|
success: function(msg) {
|
||||||
|
const title = (d.text || d.href).trim();
|
||||||
|
var markup =
|
||||||
|
'<tr><td>' +
|
||||||
|
title +
|
||||||
|
'</td><td>' +
|
||||||
|
humanFileSize(xhr.getResponseHeader('Content-Length'), true) +
|
||||||
|
'</td><td><button class="btn btn-small add-podcast" data-url="' +
|
||||||
|
d.href +
|
||||||
|
'" data-title="' +
|
||||||
|
title +
|
||||||
|
'"><i class="fa fa-plus"></i></button></td></tr>';
|
||||||
|
$('table tbody').append(markup);
|
||||||
|
$('.add-podcast').click(postPodcast);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function startRenderLinks() {
|
||||||
|
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
|
||||||
|
chrome.tabs.sendMessage(tabs[0].id, { action: 'process-page' }, renderLinks);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function renderProfileView(authResult) {
|
||||||
|
$('.login').hide();
|
||||||
|
$('.loading').show();
|
||||||
|
|
||||||
|
$('.logout-button').show();
|
||||||
|
$('.logout-button').click(logout);
|
||||||
|
$('.retry-button').click(startRenderLinks);
|
||||||
|
|
||||||
|
fetch(`https://www.podnoms.com/api/podcast/`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${authResult.id_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(resp => resp.json())
|
||||||
|
.then(podcasts => {
|
||||||
|
podcasts.forEach(podcast => {
|
||||||
|
$('.combobox').append(new Option(podcast['title'], podcast['id']));
|
||||||
|
});
|
||||||
|
console.log(podcasts);
|
||||||
|
startRenderLinks();
|
||||||
|
$('.loading').hide();
|
||||||
|
$('.loggedin').show();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
$('.error').show();
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDefaultView() {
|
||||||
|
$('.login-button').click(() => {
|
||||||
|
$('.login').hide();
|
||||||
|
$('.loading').show();
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'authenticate'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
$('.loading').hide();
|
||||||
|
$('.loggedin').hide();
|
||||||
|
$('.error').hide();
|
||||||
|
$('.logout').hide();
|
||||||
|
|
||||||
|
const authResult = JSON.parse(localStorage.authResult || '{}');
|
||||||
|
const token = authResult.id_token;
|
||||||
|
if (token && isLoggedIn(token)) {
|
||||||
|
renderProfileView(authResult);
|
||||||
|
} else {
|
||||||
|
renderDefaultView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', main);
|
||||||
7
extension/app/styles/bootstrap.css
vendored
Normal file
3
extension/app/styles/main.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
7
extension/bower.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "podnoms",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {}
|
||||||
|
}
|
||||||
140
extension/gulpfile.babel.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// generated on 2017-12-10 using generator-chrome-extension 0.7.0
|
||||||
|
import gulp from 'gulp';
|
||||||
|
import gulpLoadPlugins from 'gulp-load-plugins';
|
||||||
|
import del from 'del';
|
||||||
|
import runSequence from 'run-sequence';
|
||||||
|
import {stream as wiredep} from 'wiredep';
|
||||||
|
|
||||||
|
const $ = gulpLoadPlugins();
|
||||||
|
|
||||||
|
gulp.task('extras', () => {
|
||||||
|
return gulp.src([
|
||||||
|
'app/*.*',
|
||||||
|
'app/_locales/**',
|
||||||
|
'!app/scripts.babel',
|
||||||
|
'!app/*.json',
|
||||||
|
'!app/*.html',
|
||||||
|
], {
|
||||||
|
base: 'app',
|
||||||
|
dot: true
|
||||||
|
}).pipe(gulp.dest('dist'));
|
||||||
|
});
|
||||||
|
|
||||||
|
function lint(files, options) {
|
||||||
|
return () => {
|
||||||
|
return gulp.src(files)
|
||||||
|
.pipe($.eslint(options))
|
||||||
|
.pipe($.eslint.format());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('lint', lint('app/scripts.babel/**/*.js', {
|
||||||
|
env: {
|
||||||
|
es6: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
gulp.task('images', () => {
|
||||||
|
return gulp.src('app/images/**/*')
|
||||||
|
.pipe($.if($.if.isFile, $.cache($.imagemin({
|
||||||
|
progressive: true,
|
||||||
|
interlaced: true,
|
||||||
|
// don't remove IDs from SVGs, they are often used
|
||||||
|
// as hooks for embedding and styling
|
||||||
|
svgoPlugins: [{cleanupIDs: false}]
|
||||||
|
}))
|
||||||
|
.on('error', function (err) {
|
||||||
|
console.log(err);
|
||||||
|
this.end();
|
||||||
|
})))
|
||||||
|
.pipe(gulp.dest('dist/images'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('html', () => {
|
||||||
|
return gulp.src('app/*.html')
|
||||||
|
.pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
|
||||||
|
.pipe($.sourcemaps.init())
|
||||||
|
.pipe($.if('*.js', $.uglify()))
|
||||||
|
.pipe($.if('*.css', $.cleanCss({compatibility: '*'})))
|
||||||
|
.pipe($.sourcemaps.write())
|
||||||
|
.pipe($.if('*.html', $.htmlmin({
|
||||||
|
collapseWhitespace: true,
|
||||||
|
minifyCSS: true,
|
||||||
|
minifyJS: true,
|
||||||
|
removeComments: true
|
||||||
|
})))
|
||||||
|
.pipe(gulp.dest('dist'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('chromeManifest', () => {
|
||||||
|
return gulp.src('app/manifest.json')
|
||||||
|
.pipe($.chromeManifest({
|
||||||
|
buildnumber: true,
|
||||||
|
background: {
|
||||||
|
target: 'scripts/background.js',
|
||||||
|
exclude: [
|
||||||
|
'scripts/chromereload.js'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe($.if('*.css', $.cleanCss({compatibility: '*'})))
|
||||||
|
.pipe($.if('*.js', $.sourcemaps.init()))
|
||||||
|
.pipe($.if('*.js', $.uglify()))
|
||||||
|
.pipe($.if('*.js', $.sourcemaps.write('.')))
|
||||||
|
.pipe(gulp.dest('dist'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('babel', () => {
|
||||||
|
return gulp.src('app/scripts.babel/**/*.js')
|
||||||
|
.pipe($.babel({
|
||||||
|
presets: ['es2015']
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('app/scripts'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
|
||||||
|
|
||||||
|
gulp.task('watch', ['lint', 'babel'], () => {
|
||||||
|
$.livereload.listen();
|
||||||
|
|
||||||
|
gulp.watch([
|
||||||
|
'app/*.html',
|
||||||
|
'app/scripts/**/*.js',
|
||||||
|
'app/images/**/*',
|
||||||
|
'app/styles/**/*',
|
||||||
|
'app/_locales/**/*.json'
|
||||||
|
]).on('change', $.livereload.reload);
|
||||||
|
|
||||||
|
gulp.watch('app/scripts.babel/**/*.js', ['lint', 'babel']);
|
||||||
|
gulp.watch('bower.json', ['wiredep']);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('size', () => {
|
||||||
|
return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('wiredep', () => {
|
||||||
|
gulp.src('app/*.html')
|
||||||
|
.pipe(wiredep({
|
||||||
|
ignorePath: /^(\.\.\/)*\.\./
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('app'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('package', function () {
|
||||||
|
var manifest = require('./dist/manifest.json');
|
||||||
|
return gulp.src('dist/**')
|
||||||
|
.pipe($.zip('podnoms-' + manifest.version + '.zip'))
|
||||||
|
.pipe(gulp.dest('package'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build', (cb) => {
|
||||||
|
runSequence(
|
||||||
|
'lint', 'babel', 'chromeManifest',
|
||||||
|
['html', 'images', 'extras'],
|
||||||
|
'size', cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', ['clean'], cb => {
|
||||||
|
runSequence('build', cb);
|
||||||
|
});
|
||||||
8062
extension/package-lock.json
generated
Normal file
51
extension/package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "podnoms",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-core": "^6.7.2",
|
||||||
|
"babel-preset-es2015": "^6.6.0",
|
||||||
|
"del": "^2.2.0",
|
||||||
|
"gulp": "^3.9.1",
|
||||||
|
"gulp-babel": "^6.1.2",
|
||||||
|
"gulp-cache": "^0.4.3",
|
||||||
|
"gulp-chrome-manifest": "0.0.13",
|
||||||
|
"gulp-clean-css": "^2.0.3",
|
||||||
|
"gulp-eslint": "^2.0.0",
|
||||||
|
"gulp-htmlmin": "^2.0.0",
|
||||||
|
"gulp-if": "^2.0.0",
|
||||||
|
"gulp-imagemin": "^2.4.0",
|
||||||
|
"gulp-livereload": "^3.8.1",
|
||||||
|
"gulp-load-plugins": "^1.2.0",
|
||||||
|
"gulp-size": "^2.1.0",
|
||||||
|
"gulp-sourcemaps": "^1.6.0",
|
||||||
|
"gulp-uglify": "^1.5.3",
|
||||||
|
"gulp-useref": "^3.0.8",
|
||||||
|
"gulp-zip": "^3.2.0",
|
||||||
|
"main-bower-files": "^2.11.1",
|
||||||
|
"run-sequence": "^1.1.5",
|
||||||
|
"wiredep": "^4.0.0"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"chrome": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"eol-last": 0,
|
||||||
|
"quotes": [
|
||||||
|
2,
|
||||||
|
"single"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"auth0-chrome": "^0.1.2",
|
||||||
|
"jwt-decode": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
extension/test/index.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Mocha Spec Runner</title>
|
||||||
|
<link rel="stylesheet" href="../bower_components/mocha/mocha.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mocha"></div>
|
||||||
|
<script src="../bower_components/mocha/mocha.js"></script>
|
||||||
|
<script>mocha.setup('tdd');</script>
|
||||||
|
<script src="../bower_components/chai/chai.js"></script>
|
||||||
|
<script>
|
||||||
|
var assert = chai.assert;
|
||||||
|
var expect = chai.expect;
|
||||||
|
var should = chai.should();
|
||||||
|
</script>
|
||||||
|
<!-- bower:js -->
|
||||||
|
<!-- endbower -->
|
||||||
|
<!-- include source files here... -->
|
||||||
|
<!-- include spec files here... -->
|
||||||
|
<script src="spec/test.js"></script>
|
||||||
|
<script>
|
||||||
|
if (navigator.userAgent.indexOf('PhantomJS') === -1) {
|
||||||
|
mocha.run();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
extension/test/spec/test.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('Give it some context', function () {
|
||||||
|
describe('maybe a bit more context here', function () {
|
||||||
|
it('should run here few assertions', function () {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
4442
extension/yarn.lock
Normal file
@@ -14,11 +14,9 @@ using PodNoms.Api.Persistence;
|
|||||||
using PodNoms.Api.Services.Processor;
|
using PodNoms.Api.Services.Processor;
|
||||||
using PodNoms.Api.Services.Storage;
|
using PodNoms.Api.Services.Storage;
|
||||||
|
|
||||||
namespace PodNoms.Api.Controllers
|
namespace PodNoms.Api.Controllers {
|
||||||
{
|
[Route ("[controller]")]
|
||||||
[Route("[controller]")]
|
public class EntryController : Controller {
|
||||||
public class EntryController : Controller
|
|
||||||
{
|
|
||||||
private readonly IPodcastRepository _podcastRepository;
|
private readonly IPodcastRepository _podcastRepository;
|
||||||
private readonly IEntryRepository _repository;
|
private readonly IEntryRepository _repository;
|
||||||
private readonly IUnitOfWork _uow;
|
private readonly IUnitOfWork _uow;
|
||||||
@@ -26,75 +24,69 @@ namespace PodNoms.Api.Controllers
|
|||||||
private readonly IUrlProcessService _processor;
|
private readonly IUrlProcessService _processor;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly AudioFileStorageSettings _audioFileStorageSettings;
|
private readonly AudioFileStorageSettings _audioFileStorageSettings;
|
||||||
|
private readonly StorageSettings _storageSettings;
|
||||||
|
|
||||||
public EntryController(IEntryRepository repository,
|
public EntryController (IEntryRepository repository,
|
||||||
IPodcastRepository podcastRepository,
|
IPodcastRepository podcastRepository,
|
||||||
IUnitOfWork unitOfWork, IMapper mapper,
|
IUnitOfWork unitOfWork, IMapper mapper, IOptions<StorageSettings> storageSettings,
|
||||||
IOptions<AudioFileStorageSettings> audioFileStorageSettings,
|
IOptions<AudioFileStorageSettings> audioFileStorageSettings,
|
||||||
IUrlProcessService processor, ILoggerFactory logger)
|
IUrlProcessService processor, ILoggerFactory logger) {
|
||||||
{
|
this._logger = logger.CreateLogger<EntryController> ();
|
||||||
this._logger = logger.CreateLogger<EntryController>();
|
|
||||||
this._podcastRepository = podcastRepository;
|
this._podcastRepository = podcastRepository;
|
||||||
this._repository = repository;
|
this._repository = repository;
|
||||||
|
this._storageSettings = storageSettings.Value;
|
||||||
this._uow = unitOfWork;
|
this._uow = unitOfWork;
|
||||||
this._audioFileStorageSettings = audioFileStorageSettings.Value;
|
this._audioFileStorageSettings = audioFileStorageSettings.Value;
|
||||||
this._mapper = mapper;
|
this._mapper = mapper;
|
||||||
this._processor = processor;
|
this._processor = processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _processEntry(PodcastEntry entry)
|
private void _processEntry (PodcastEntry entry) {
|
||||||
{
|
try {
|
||||||
try
|
var infoJobId = BackgroundJob.Enqueue<IUrlProcessService> (
|
||||||
{
|
service => service.GetInformation (entry.Id));
|
||||||
var infoJobId = BackgroundJob.Enqueue<IUrlProcessService>(
|
var extract = BackgroundJob.ContinueWith<IUrlProcessService> (
|
||||||
service => service.GetInformation(entry.Id));
|
infoJobId, service => service.DownloadAudio (entry.Id));
|
||||||
var extract = BackgroundJob.ContinueWith<IUrlProcessService>(
|
var upload = BackgroundJob.ContinueWith<IAudioUploadProcessService> (
|
||||||
infoJobId, service => service.DownloadAudio(entry.Id));
|
extract, service => service.UploadAudio (entry.Id, entry.AudioUrl));
|
||||||
var upload = BackgroundJob.ContinueWith<IAudioUploadProcessService>(
|
} catch (InvalidOperationException ex) {
|
||||||
extract, service => service.UploadAudio(entry.Id, entry.AudioUrl));
|
_logger.LogError ($"Failed submitting job to processor\n{ex.Message}");
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
_logger.LogError($"Failed submitting job to processor\n{ex.Message}");
|
|
||||||
entry.ProcessingStatus = ProcessingStatus.Failed;
|
entry.ProcessingStatus = ProcessingStatus.Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[HttpGet("all/{podcastSlug}")]
|
[HttpGet ("all/{podcastSlug}")]
|
||||||
public async Task<IActionResult> GetAllForSlug(string podcastSlug)
|
public async Task<IActionResult> GetAllForSlug (string podcastSlug) {
|
||||||
{
|
var entries = await _repository.GetAllAsync (podcastSlug);
|
||||||
var entries = await _repository.GetAllAsync(podcastSlug);
|
var results = _mapper.Map<List<PodcastEntry>, List<PodcastEntryViewModel>> (entries.ToList ());
|
||||||
var results = _mapper.Map<List<PodcastEntry>, List<PodcastEntryViewModel>>(entries.ToList());
|
|
||||||
|
|
||||||
return Ok(results);
|
return Ok (results);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Post([FromBody] PodcastEntryViewModel item)
|
public async Task<IActionResult> Post ([FromBody] PodcastEntryViewModel item) {
|
||||||
{
|
var entry = _mapper.Map<PodcastEntryViewModel, PodcastEntry> (item);
|
||||||
var entry = _mapper.Map<PodcastEntryViewModel, PodcastEntry>(item);
|
if (entry.ProcessingStatus == ProcessingStatus.Accepted) {
|
||||||
if (entry.ProcessingStatus == ProcessingStatus.Accepted)
|
var podcast = await _podcastRepository.GetAsync (item.PodcastId);
|
||||||
{
|
entry.ImageUrl = $"{_storageSettings.CdnUrl}static/images/default-entry.png";
|
||||||
var podcast = await _podcastRepository.GetAsync(item.PodcastId);
|
|
||||||
entry.Podcast = podcast;
|
entry.Podcast = podcast;
|
||||||
entry.Processed = false;
|
entry.Processed = false;
|
||||||
}
|
}
|
||||||
await _repository.AddOrUpdateAsync(entry);
|
await _repository.AddOrUpdateAsync (entry);
|
||||||
await _uow.CompleteAsync();
|
await _uow.CompleteAsync ();
|
||||||
|
|
||||||
if (entry.ProcessingStatus.Equals(ProcessingStatus.Accepted) && entry.Id != 0)
|
if (entry.ProcessingStatus.Equals (ProcessingStatus.Accepted) && entry.Id != 0) {
|
||||||
{
|
_processEntry (entry);
|
||||||
_processEntry(entry);
|
|
||||||
}
|
}
|
||||||
var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel>(entry);
|
var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel> (entry);
|
||||||
return Ok(result);
|
return Ok (result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete ("{id}")]
|
||||||
public async Task<IActionResult> Delete(int id)
|
public async Task<IActionResult> Delete (int id) {
|
||||||
{
|
await this._repository.DeleteAsync (id);
|
||||||
await this._repository.DeleteAsync(id);
|
await _uow.CompleteAsync ();
|
||||||
await _uow.CompleteAsync();
|
return Ok ();
|
||||||
return Ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ namespace PodNoms.Api.Services.Processor {
|
|||||||
|
|
||||||
public ApplicationsSettings _applicationsSettings { get; }
|
public ApplicationsSettings _applicationsSettings { get; }
|
||||||
|
|
||||||
|
|
||||||
public UrlProcessService (IEntryRepository repository, IUnitOfWork unitOfWork,
|
public UrlProcessService (IEntryRepository repository, IUnitOfWork unitOfWork,
|
||||||
IFileUploader fileUploader, IOptions<ApplicationsSettings> applicationsSettings,
|
IFileUploader fileUploader, IOptions<ApplicationsSettings> applicationsSettings,
|
||||||
ILoggerFactory logger, IMapper mapper, IRealTimeUpdater pusher) : base (logger, mapper, pusher) {
|
ILoggerFactory logger, IMapper mapper, IRealTimeUpdater pusher) : base (logger, mapper, pusher) {
|
||||||
@@ -44,7 +45,9 @@ namespace PodNoms.Api.Services.Processor {
|
|||||||
_logger.LogError ("Unable to process item");
|
_logger.LogError ("Unable to process item");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (entry.SourceUrl.EndsWith (".mp3") || entry.SourceUrl.EndsWith (".wav") || entry.SourceUrl.EndsWith (".aif")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
var downloader = new AudioDownloader (entry.SourceUrl, _applicationsSettings.Downloader);
|
var downloader = new AudioDownloader (entry.SourceUrl, _applicationsSettings.Downloader);
|
||||||
downloader.DownloadInfo ();
|
downloader.DownloadInfo ();
|
||||||
if (downloader.Properties != null) {
|
if (downloader.Properties != null) {
|
||||||
|
|||||||