Initial LBRY First To Allow publishing to youtube channels.

This commit is contained in:
Mark Beamer Jr 2020-03-09 22:10:30 -04:00 committed by Sean Yesmunt
parent 8eb071d1e4
commit 0f80568acd
8 changed files with 547 additions and 5 deletions

View file

@ -12,4 +12,5 @@ module.name_mapper='^redux\(.*\)$' -> '<PROJECT_ROOT>/src/redux\1'
module.name_mapper='^util\(.*\)$' -> '<PROJECT_ROOT>/src/util\1'
module.name_mapper='^constants\(.*\)$' -> '<PROJECT_ROOT>/src/constants\1'
module.name_mapper='^lbry\(.*\)$' -> '<PROJECT_ROOT>/src/lbry\1'
module.name_mapper='^lbry-first\(.*\)$' -> '<PROJECT_ROOT>/src/lbry-first\1'
module.name_mapper='^lbryURI\(.*\)$' -> '<PROJECT_ROOT>/src/lbryURI\1'

169
dist/bundle.es.js vendored
View file

@ -1219,6 +1219,161 @@ const lbryProxy = new Proxy(Lbry, {
//
const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200;
//
// Basic LBRYFIRST connection config
// Offers a proxy to call LBRYFIRST methods
//
const LbryFirst = {
isConnected: false,
connectPromise: null,
lbryFirstConnectionString: 'http://localhost:1337/rpc',
apiRequestHeaders: { 'Content-Type': 'application/json' },
// Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb)
setLbryFirstConnectionString: value => {
LbryFirst.lbryFirstConnectionString = value;
},
setApiHeader: (key, value) => {
LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value });
},
unsetApiHeader: key => {
Object.keys(LbryFirst.apiRequestHeaders).includes(key) && delete LbryFirst.apiRequestHeaders['key'];
},
// Allow overriding Lbry methods
overrides: {},
setOverride: (methodName, newMethod) => {
LbryFirst.overrides[methodName] = newMethod;
},
getApiRequestHeaders: () => LbryFirst.apiRequestHeaders,
//
// LbryFirst Methods
//
status: (params = {}) => lbryFirstCallWithResult('status', params),
stop: () => lbryFirstCallWithResult('stop', {}),
version: () => lbryFirstCallWithResult('version', {}),
// Upload to youtube
upload: (params = {}) => {
// Only upload when originally publishing for now
if (!params.file_path) {
return {};
}
const uploadParams = {};
uploadParams.Title = params.title;
uploadParams.Description = params.description;
uploadParams.FilePath = params.file_path;
uploadParams.Category = '';
uploadParams.Keywords = '';
return lbryFirstCallWithResult('youtube.Upload', uploadParams);
},
hasYTAuth: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.HasAuth', emptyParams);
},
ytSignup: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.Signup', emptyParams);
},
// Connect to lbry-first
connect: () => {
if (LbryFirst.connectPromise === null) {
LbryFirst.connectPromise = new Promise((resolve, reject) => {
let tryNum = 0;
// Check every half second to see if the lbryFirst is accepting connections
function checkLbryFirstStarted() {
tryNum += 1;
LbryFirst.status().then(resolve).catch(() => {
if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) {
setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000);
} else {
reject(new Error('Unable to connect to LBRY'));
}
});
}
checkLbryFirstStarted();
});
}
// Flow thinks this could be empty, but it will always return a promise
// $FlowFixMe
return LbryFirst.connectPromise;
}
};
function checkAndParse$1(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
error = new Error(errorMessage);
} else {
error = new Error('Protocol error with unknown response signature');
}
return Promise.reject(error);
});
}
function apiCall$1(method, params, resolve, reject) {
const counter = new Date().getTime();
params = [params];
const options = {
method: 'POST',
headers: LbryFirst.apiRequestHeaders,
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: counter
})
};
return fetch(LbryFirst.lbryFirstConnectionString, options).then(checkAndParse$1).then(response => {
const error = response.error || response.result && response.result.error;
if (error) {
return reject(error);
}
return resolve(response.result);
}).catch(reject);
}
function lbryFirstCallWithResult(name, params = {}) {
console.log(`LbryFirst: calling ${name}`);
return new Promise((resolve, reject) => {
apiCall$1(name, params, result => {
resolve(result);
}, reject);
});
}
// This is only for a fallback
// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js
const lbryFirstProxy = new Proxy(LbryFirst, {
get(target, name) {
if (name in target) {
return target[name];
}
return (params = {}) => new Promise((resolve, reject) => {
apiCall$1(name, params, resolve, reject);
});
}
});
//
const DEFAULT_SEARCH_RESULT_FROM = 0;
const DEFAULT_SEARCH_SIZE = 20;
@ -4469,6 +4624,7 @@ const doPublish = (success, fail) => (dispatch, getState) => {
language,
license,
licenseUrl,
useLBRYUploader,
licenseType,
otherLicenseDescription,
thumbnail,
@ -4554,8 +4710,13 @@ const doPublish = (success, fail) => (dispatch, getState) => {
// Only pass file on new uploads, not metadata only edits.
// The sdk will figure it out
if (filePath) publishPayload.file_path = filePath;
return lbryProxy.publish(publishPayload).then(success, fail);
// if (useLBRYUploader) return LbryFirst.upload(publishPayload);
return lbryProxy.publish(publishPayload).then(response => {
if (!useLBRYUploader) {
return success(response);
}
return lbryFirstProxy.upload(publishPayload).then(success(response), success(response));
}, fail);
};
// Calls file_list until any reflecting files are done
@ -6227,7 +6388,8 @@ const defaultState$5 = {
publishing: false,
publishSuccess: false,
publishError: undefined,
optimize: false
optimize: false,
useLBRYUploader: false
};
const publishReducer = handleActions({
@ -6950,6 +7112,7 @@ exports.DEFAULT_FOLLOWED_TAGS = DEFAULT_FOLLOWED_TAGS;
exports.DEFAULT_KNOWN_TAGS = DEFAULT_KNOWN_TAGS;
exports.LICENSES = licenses;
exports.Lbry = lbryProxy;
exports.LbryFirst = lbryFirstProxy;
exports.MATURE_TAGS = MATURE_TAGS;
exports.PAGES = pages;
exports.SEARCH_OPTIONS = SEARCH_OPTIONS;

99
dist/flow-typed/LbryFirst.js vendored Normal file
View file

@ -0,0 +1,99 @@
// @flow
declare type StatusResponse = {
Version: string,
Message: string,
Running: boolean,
Commit: string,
};
declare type VersionResponse = {
build: string,
lbrynet_version: string,
os_release: string,
os_system: string,
platform: string,
processor: string,
python_version: string,
};
/* SAMPLE UPLOAD RESPONSE (FULL)
"Video": {
"etag": "\"Dn5xIderbhAnUk5TAW0qkFFir0M/xlGLrlTox7VFTRcR8F77RbKtaU4\"",
"id": "8InjtdvVmwE",
"kind": "youtube#video",
"snippet": {
"categoryId": "22",
"channelId": "UCXiVsGTU88fJjheB2rqF0rA",
"channelTitle": "Mark Beamer",
"liveBroadcastContent": "none",
"localized": {
"title": "my title"
},
"publishedAt": "2020-05-05T04:17:53.000Z",
"thumbnails": {
"default": {
"height": 90,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/default.jpg?sqp=CMTQw_UF&rs=AOn4CLB6dlhZMSMrazDlWRsitPgCsn8fVw",
"width": 120
},
"high": {
"height": 360,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/hqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLB-Je_7l6qvASRAR_bSGWZHaXaJWQ",
"width": 480
},
"medium": {
"height": 180,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/mqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLCvSnDLqVznRNMKuvJ_0misY_chPQ",
"width": 320
}
},
"title": "my title"
},
"status": {
"embeddable": true,
"license": "youtube",
"privacyStatus": "private",
"publicStatsViewable": true,
"uploadStatus": "uploaded"
}
}
*/
declare type UploadResponse = {
Video: {
id: string,
snippet: {
channelId: string,
},
status: {
uploadStatus: string,
},
},
};
declare type HasYTAuthResponse = {
HashAuth: boolean,
}
declare type YTSignupResponse = {}
//
// Types used in the generic LbryFirst object that is exported
//
declare type LbryFirstTypes = {
isConnected: boolean,
connectPromise: ?Promise<any>,
connect: () => void,
lbryFirstConnectionString: string,
apiRequestHeaders: { [key: string]: string },
setApiHeader: (string, string) => void,
unsetApiHeader: string => void,
overrides: { [string]: ?Function },
setOverride: (string, Function) => void,
// LbryFirst Methods
stop: () => Promise<string>,
status: () => Promise<StatusResponse>,
version: () => Promise<VersionResponse>,
upload: (params: {}) => Promise<UploadResponse>,
hasYTAuth: () =>Promise<HasYTAuthResponse>,
ytSignup: () =>Promise<YTSignupResponse>,
};

99
flow-typed/LbryFirst.js vendored Normal file
View file

@ -0,0 +1,99 @@
// @flow
declare type StatusResponse = {
Version: string,
Message: string,
Running: boolean,
Commit: string,
};
declare type VersionResponse = {
build: string,
lbrynet_version: string,
os_release: string,
os_system: string,
platform: string,
processor: string,
python_version: string,
};
/* SAMPLE UPLOAD RESPONSE (FULL)
"Video": {
"etag": "\"Dn5xIderbhAnUk5TAW0qkFFir0M/xlGLrlTox7VFTRcR8F77RbKtaU4\"",
"id": "8InjtdvVmwE",
"kind": "youtube#video",
"snippet": {
"categoryId": "22",
"channelId": "UCXiVsGTU88fJjheB2rqF0rA",
"channelTitle": "Mark Beamer",
"liveBroadcastContent": "none",
"localized": {
"title": "my title"
},
"publishedAt": "2020-05-05T04:17:53.000Z",
"thumbnails": {
"default": {
"height": 90,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/default.jpg?sqp=CMTQw_UF&rs=AOn4CLB6dlhZMSMrazDlWRsitPgCsn8fVw",
"width": 120
},
"high": {
"height": 360,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/hqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLB-Je_7l6qvASRAR_bSGWZHaXaJWQ",
"width": 480
},
"medium": {
"height": 180,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/mqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLCvSnDLqVznRNMKuvJ_0misY_chPQ",
"width": 320
}
},
"title": "my title"
},
"status": {
"embeddable": true,
"license": "youtube",
"privacyStatus": "private",
"publicStatsViewable": true,
"uploadStatus": "uploaded"
}
}
*/
declare type UploadResponse = {
Video: {
id: string,
snippet: {
channelId: string,
},
status: {
uploadStatus: string,
},
},
};
declare type HasYTAuthResponse = {
HashAuth: boolean,
}
declare type YTSignupResponse = {}
//
// Types used in the generic LbryFirst object that is exported
//
declare type LbryFirstTypes = {
isConnected: boolean,
connectPromise: ?Promise<any>,
connect: () => void,
lbryFirstConnectionString: string,
apiRequestHeaders: { [key: string]: string },
setApiHeader: (string, string) => void,
unsetApiHeader: string => void,
overrides: { [string]: ?Function },
setOverride: (string, Function) => void,
// LbryFirst Methods
stop: () => Promise<string>,
status: () => Promise<StatusResponse>,
version: () => Promise<VersionResponse>,
upload: (params: {}) => Promise<UploadResponse>,
hasYTAuth: () =>Promise<HasYTAuthResponse>,
ytSignup: () =>Promise<YTSignupResponse>,
};

View file

@ -15,6 +15,7 @@ import * as SHARED_PREFERENCES from 'constants/shared_preferences';
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
import { DEFAULT_KNOWN_TAGS, DEFAULT_FOLLOWED_TAGS, MATURE_TAGS } from 'constants/tags';
import Lbry, { apiCall } from 'lbry';
import LbryFirst from 'lbry-first';
import { selectState as selectSearchState } from 'redux/selectors/search';
// constants
@ -42,6 +43,7 @@ export {
// common
export { Lbry, apiCall };
export { LbryFirst };
export {
regexInvalidURI,
regexAddress,

168
src/lbry-first.js Normal file
View file

@ -0,0 +1,168 @@
// @flow
import 'proxy-polyfill';
const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200;
//
// Basic LBRYFIRST connection config
// Offers a proxy to call LBRYFIRST methods
//
const LbryFirst: LbryFirstTypes = {
isConnected: false,
connectPromise: null,
lbryFirstConnectionString: 'http://localhost:1337/rpc',
apiRequestHeaders: { 'Content-Type': 'application/json' },
// Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb)
setLbryFirstConnectionString: (value: string) => {
LbryFirst.lbryFirstConnectionString = value;
},
setApiHeader: (key: string, value: string) => {
LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value });
},
unsetApiHeader: key => {
Object.keys(LbryFirst.apiRequestHeaders).includes(key) && delete LbryFirst.apiRequestHeaders['key'];
},
// Allow overriding Lbry methods
overrides: {},
setOverride: (methodName, newMethod) => {
LbryFirst.overrides[methodName] = newMethod;
},
getApiRequestHeaders: () => LbryFirst.apiRequestHeaders,
//
// LbryFirst Methods
//
status: (params = {}) => lbryFirstCallWithResult('status', params),
stop: () => lbryFirstCallWithResult('stop', {}),
version: () => lbryFirstCallWithResult('version', {}),
// Upload to youtube
upload: (params = {}) => {
// Only upload when originally publishing for now
if (!params.file_path) {
return {};
}
const uploadParams = {};
uploadParams.Title = params.title;
uploadParams.Description = params.description;
uploadParams.FilePath = params.file_path;
uploadParams.Category = '';
uploadParams.Keywords = '';
return lbryFirstCallWithResult('youtube.Upload', uploadParams);
},
hasYTAuth: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.HasAuth', emptyParams);
},
ytSignup: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.Signup', emptyParams);
},
// Connect to lbry-first
connect: () => {
if (LbryFirst.connectPromise === null) {
LbryFirst.connectPromise = new Promise((resolve, reject) => {
let tryNum = 0;
// Check every half second to see if the lbryFirst is accepting connections
function checkLbryFirstStarted() {
tryNum += 1;
LbryFirst.status()
.then(resolve)
.catch(() => {
if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) {
setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000);
} else {
reject(new Error('Unable to connect to LBRY'));
}
});
}
checkLbryFirstStarted();
});
}
// Flow thinks this could be empty, but it will always return a promise
// $FlowFixMe
return LbryFirst.connectPromise;
},
};
function checkAndParse(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
error = new Error(errorMessage);
} else {
error = new Error('Protocol error with unknown response signature');
}
return Promise.reject(error);
});
}
export function apiCall(method: string, params: ?{}, resolve: Function, reject: Function) {
const counter = new Date().getTime();
params = [params];
const options = {
method: 'POST',
headers: LbryFirst.apiRequestHeaders,
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: counter,
}),
};
return fetch(LbryFirst.lbryFirstConnectionString, options)
.then(checkAndParse)
.then(response => {
const error = response.error || (response.result && response.result.error);
if (error) {
return reject(error);
}
return resolve(response.result);
})
.catch(reject);
}
function lbryFirstCallWithResult(name: string, params: ?{} = {}) {
console.log(`LbryFirst: calling ${name}`);
return new Promise((resolve, reject) => {
apiCall(
name,
params,
result => {
resolve(result);
},
reject
);
});
}
// This is only for a fallback
// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js
const lbryFirstProxy = new Proxy(LbryFirst, {
get(target: LbryFirstTypes, name: string) {
if (name in target) {
return target[name];
}
return (params = {}) =>
new Promise((resolve, reject) => {
apiCall(name, params, resolve, reject);
});
},
});
export default lbryFirstProxy;

View file

@ -4,6 +4,7 @@ import { SPEECH_STATUS, SPEECH_PUBLISH } from 'constants/speech_urls';
import * as ACTIONS from 'constants/action_types';
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
import Lbry from 'lbry';
import LbryFirst from 'lbry-first';
import { batchActions } from 'util/batch-actions';
import { creditsToString } from 'util/format-credits';
import { doError } from 'redux/actions/notifications';
@ -245,6 +246,7 @@ export const doPublish = (success: Function, fail: Function) => (
language,
license,
licenseUrl,
useLBRYUploader,
licenseType,
otherLicenseDescription,
thumbnail,
@ -350,8 +352,14 @@ export const doPublish = (success: Function, fail: Function) => (
// Only pass file on new uploads, not metadata only edits.
// The sdk will figure it out
if (filePath) publishPayload.file_path = filePath;
return Lbry.publish(publishPayload).then(success, fail);
// if (useLBRYUploader) return LbryFirst.upload(publishPayload);
return Lbry.publish(publishPayload)
.then((response) => {
if (!useLBRYUploader) {
return success(response);
}
return LbryFirst.upload(publishPayload).then(success(response), success(response));
}, fail);
};
// Calls file_list until any reflecting files are done

View file

@ -33,6 +33,7 @@ type PublishState = {
licenseUrl: string,
tags: Array<string>,
optimize: boolean,
useLBRYUploader: boolean,
};
const defaultState: PublishState = {
@ -68,6 +69,7 @@ const defaultState: PublishState = {
publishSuccess: false,
publishError: undefined,
optimize: false,
useLBRYUploader: false,
};
export const publishReducer = handleActions(