RJH Solutions

SEO and Website Development Internet Marketing Blog in Toronto

Tool to Bulk Publish YouTube Videos

Tool to Bulk Publish YouTube Videos 2084 2084 rhecht

Place this in your Google Chrome Console tab, after selecting all videos. Shoutout to Kashif Mahmood for this script. It’s proven to be a huge timesaver.

(() => {
// -----------------------------------------------------------------
// CONFIG (you're safe to edit this)
// -----------------------------------------------------------------
// ~ GLOBAL CONFIG
// -----------------------------------------------------------------
const MODE = 'publish_drafts'; // 'publish_drafts' / 'sort_playlist';
const DEBUG_MODE = true; // true / false, enable for more context
// -----------------------------------------------------------------
// ~ PUBLISH CONFIG
// -----------------------------------------------------------------
const MADE_FOR_KIDS = false; // true / false;
const VISIBILITY = 'Public'; // 'Public' / 'Private' / 'Unlisted'
// -----------------------------------------------------------------
// ~ SORT PLAYLIST CONFIG
// -----------------------------------------------------------------
const SORTING_KEY = (one, other) => {
const numberRegex = /\d+/;
const number = (name) => name.match(numberRegex)[0];
if (number(one.name) === undefined || number(other.name) === undefined) {
return one.name.localeCompare(other.name);
}
return number(one.name) - number(other.name);
};
// END OF CONFIG (not safe to edit stuff below)
// -----------------------------------------------------------------
// ----------------------------------
// COMMON STUFF
// ---------------------------------
const TIMEOUT_STEP_MS = 20;
const DEFAULT_ELEMENT_TIMEOUT_MS = 10000;
function debugLog(...args) {
if (!DEBUG_MODE) {
return;
}
console.debug(...args);
}
const sleep = (ms) => new Promise((resolve, _) => setTimeout(resolve, ms));
async function waitForElement(selector, baseEl, timeoutMs) {
if (timeoutMs === undefined) {
timeoutMs = DEFAULT_ELEMENT_TIMEOUT_MS;
}
if (baseEl === undefined) {
baseEl = document;
}
let timeout = timeoutMs;
while (timeout > 0) {
let element = baseEl.querySelector(selector);
if (element !== null) {
return element;
}
await sleep(TIMEOUT_STEP_MS);
timeout -= TIMEOUT_STEP_MS;
}
debugLog(`could not find ${selector} inside`, baseEl);
return null;
}
function click(element) {
const event = document.createEvent('MouseEvents');
event.initMouseEvent('mousedown', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
element.click();
debugLog(element, 'clicked');
}
// ----------------------------------
// PUBLISH STUFF
// ----------------------------------
const VISIBILITY_PUBLISH_ORDER = {
'Private': 0,
'Unlisted': 1,
'Public': 2,
};
// SELECTORS
// ---------
const VIDEO_ROW_SELECTOR = 'ytcp-video-row';
const DRAFT_MODAL_SELECTOR = '.style-scope.ytcp-uploads-dialog';
const DRAFT_BUTTON_SELECTOR = '.edit-draft-button';
const MADE_FOR_KIDS_SELECTOR = '#made-for-kids-group';
const RADIO_BUTTON_SELECTOR = 'tp-yt-paper-radio-button';
const VISIBILITY_STEPPER_SELECTOR = '#step-badge-3';
const VISIBILITY_PAPER_BUTTONS_SELECTOR = 'tp-yt-paper-radio-group';
const SAVE_BUTTON_SELECTOR = '#done-button';
const SUCCESS_ELEMENT_SELECTOR = 'ytcp-video-thumbnail-with-info';
const DIALOG_SELECTOR = 'ytcp-dialog.ytcp-video-share-dialog > tp-yt-paper-dialog:nth-child(1)';
const DIALOG_CLOSE_BUTTON_SELECTOR = 'tp-yt-iron-icon';
class SuccessDialog {
constructor(raw) {
this.raw = raw;
}
async closeDialogButton() {
return await waitForElement(DIALOG_CLOSE_BUTTON_SELECTOR, this.raw);
}
async close() {
click(await this.closeDialogButton());
await sleep(50);
debugLog('closed');
}
}
class VisibilityModal {
constructor(raw) {
this.raw = raw;
}
async radioButtonGroup() {
return await waitForElement(VISIBILITY_PAPER_BUTTONS_SELECTOR, this.raw);
}
async visibilityRadioButton() {
const group = await this.radioButtonGroup();
const value = VISIBILITY_PUBLISH_ORDER[VISIBILITY];
return [...group.querySelectorAll(RADIO_BUTTON_SELECTOR)][value];
}
async setVisibility() {
click(await this.visibilityRadioButton());
debugLog(`visibility set to ${VISIBILITY}`);
await sleep(50);
}
async saveButton() {
return await waitForElement(SAVE_BUTTON_SELECTOR, this.raw);
}
async isSaved() {
await waitForElement(SUCCESS_ELEMENT_SELECTOR, document);
}
async dialog() {
return await waitForElement(DIALOG_SELECTOR);
}
async save() {
click(await this.saveButton());
await this.isSaved();
debugLog('saved');
const dialogElement = await this.dialog();
const success = new SuccessDialog(dialogElement);
return success;
}
}
class DraftModal {
constructor(raw) {
this.raw = raw;
}
async madeForKidsToggle() {
return await waitForElement(MADE_FOR_KIDS_SELECTOR, this.raw);
}
async madeForKidsPaperButton() {
const nthChild = MADE_FOR_KIDS ? 1 : 2;
return await waitForElement(`${RADIO_BUTTON_SELECTOR}:nth-child(${nthChild})`, this.raw);
}
async selectMadeForKids() {
click(await this.madeForKidsPaperButton());
await sleep(50);
debugLog('"Made for kids" set as ${MADE_FOR_KIDS}');
}
async visibilityStepper() {
return await waitForElement(VISIBILITY_STEPPER_SELECTOR, this.raw);
}
async goToVisibility() {
debugLog('going to Visibility');
await sleep(50);
click(await this.visibilityStepper());
const visibility = new VisibilityModal(this.raw);
await sleep(50);
await waitForElement(VISIBILITY_PAPER_BUTTONS_SELECTOR, visibility.raw);
return visibility;
}
}
class VideoRow {
constructor(raw) {
this.raw = raw;
}
get editDraftButton() {
return waitForElement(DRAFT_BUTTON_SELECTOR, this.raw, 20);
}
async openDraft() {
debugLog('focusing draft button');
click(await this.editDraftButton);
return new DraftModal(await waitForElement(DRAFT_MODAL_SELECTOR));
}
}
function allVideos() {
return [...document.querySelectorAll(VIDEO_ROW_SELECTOR)].map((el) => new VideoRow(el));
}
async function editableVideos() {
let editable = [];
for (let video of allVideos()) {
if ((await video.editDraftButton) !== null) {
editable = [...editable, video];
}
}
return editable;
}
async function publishDrafts() {
const videos = await editableVideos();
debugLog(`found ${videos.length} videos`);
debugLog('starting in 1000ms');
await sleep(1000);
for (let video of videos) {
const draft = await video.openDraft();
debugLog({
draft
});
await draft.selectMadeForKids();
const visibility = await draft.goToVisibility();
await visibility.setVisibility();
const dialog = await visibility.save();
await dialog.close();
await sleep(100);
}
}
// ----------------------------------
// SORTING STUFF
// ----------------------------------
const SORTING_MENU_BUTTON_SELECTOR = 'button';
const SORTING_ITEM_MENU_SELECTOR = 'paper-listbox#items';
const SORTING_ITEM_MENU_ITEM_SELECTOR = 'ytd-menu-service-item-renderer';
const MOVE_TO_TOP_INDEX = 4;
const MOVE_TO_BOTTOM_INDEX = 5;
class SortingDialog {
constructor(raw) {
this.raw = raw;
}
async anyMenuItem() {
const item = await waitForElement(SORTING_ITEM_MENU_ITEM_SELECTOR, this.raw);
if (item === null) {
throw new Error("could not locate any menu item");
}
return item;
}
menuItems() {
return [...this.raw.querySelectorAll(SORTING_ITEM_MENU_ITEM_SELECTOR)];
}
async moveToTop() {
click(this.menuItems()[MOVE_TO_TOP_INDEX]);
}
async moveToBottom() {
click(this.menuItems()[MOVE_TO_BOTTOM_INDEX]);
}
}
class PlaylistVideo {
constructor(raw) {
this.raw = raw;
}
get name() {
return this.raw.querySelector('#video-title').textContent;
}
async dialog() {
return this.raw.querySelector(SORTING_MENU_BUTTON_SELECTOR);
}
async openDialog() {
click(await this.dialog());
const dialog = new SortingDialog(await waitForElement(SORTING_ITEM_MENU_SELECTOR));
await dialog.anyMenuItem();
return dialog;
}
}
async function playlistVideos() {
return [...document.querySelectorAll('ytd-playlist-video-renderer')]
.map((el) => new PlaylistVideo(el));
}
async function sortPlaylist() {
debugLog('sorting playlist');
const videos = await playlistVideos();
debugLog(`found ${videos.length} videos`);
videos.sort(SORTING_KEY);
const videoNames = videos.map((v) => v.name);
let index = 1;
for (let name of videoNames) {
debugLog({index, name});
const video = videos.find((v) => v.name === name);
const dialog = await video.openDialog();
await dialog.moveToBottom();
await sleep(1000);
index += 1;
}
}
// ----------------------------------
// ENTRY POINT
// ----------------------------------
({
'publish_drafts': publishDrafts,
'sort_playlist': sortPlaylist,
})[MODE]();
})();

 

Shoutout to https://www.pioneerstrikes.com/publish-youtube-videos-in-bulk/ for this.

Top 10 Ways to Compromise Your WordPress Website Security Today

Top 10 Ways to Compromise Your WordPress Website Security Today 1190 1190 rhecht

It’s mid-November and, for some reason, WordPress websites tend to get hacked en masse at around the turn of the new year. You have a choice: either ensure your website is very secure this year or risk getting hacked. If you have a masochistic urge to opt for the latter, here’s what you should do.

  1. Keep wp-admin and wp-login.php directly accessible

    This allows hackers to brute-force into the website without any effort. Even if they don’t log in, they can overwhelm the website 

  2. Keep the primary Admin username as “admin.”

    Many WordPress websites stick with the default username “admin.” The fact this username exists solves half the battle and it’s then a matter of hacking the password.

  3. Have all passwords in as regular text

    Passwords generally have to do with the website owner in question. Many hackers use a curated password list based on ones personal information. Also, doing only the first letter as capital with “123!” at the end, in order to fulfill the bare minimum requirement of having at least one uppercase character, one special character, and one number doesn’t cut it.

  4. Place the FTP details in a plugin

    Some years ago we encountered a website (non-Wordpress) that had at the time a nifty carousel. We searched the source code to see what slider it was, and if this could be replicated as a plugin (it was). We were in for a nice surprise in that the user’s FTP details to manually upload slider images were there, plain and center!

  5. Making the “wp-content” folder 777 writable

    This allows a hacker to inject malware files into a plugin, theme, or uploads folder, without the website owner knowing the difference. What’s worse is giving all files 777 access since one can then download the wp-config.php file which has database access.

  6. Enable directory browsing

    This allows a hacker complete access to all files.

  7. Give unlimited remote database access

    With database credentials from wp-config, providing unlimited remote database access gives a hacker free reign to add/remove whatever is in his hearts’ desire.

  8. Store .sql database dumps in the site’s public_html folder

    WordPress websites are powered by MySQL databases. To transfer a website one would need to generate a SQL “dump” file. Sometimes a developer will keep a SQL file copy stored for backup reasons in the public_html folder. This can allow a hacker direct access to all the site’s content, a complete list of usernames and associated email addresses (for brute-force hacking, email blasting, etc.) and more.

  9. Not updating core, plugins and themes

    There’s a reason why there are always theme, plugin and core updates. In addition to there being fancy features, many times there are discovered security holes seen in old plugins. If there is any customization done on the plugin or parent theme level, then that’s moreso a result of bad coding and should be addressed. Create a child theme of the parent theme and place all custom code there.

  10. Having an insecure contact form

    This especially applies with a file uploader, combined with an insecure contact form database plugin. Lots of Contact Form 7 database plugins for example, have fallen victim to this.

  11. Extra: not backing up your website

    This normally should be #1: BACK UP YOUR DATA!!!

Looking to move away from all that? We can help. Contact rafi at rjhsolutions dot ca today for a quote on malware removal as well as website security hardening.

8 Microsoft Visual Studio Keyboard Shortcuts You Need to Know Today

8 Microsoft Visual Studio Keyboard Shortcuts You Need to Know Today 150 150 rhecht

Here are eight of my favorite keyboard shortcuts in Visual Studio. There’s a good chance at least one of them will be new to you.

1. Move Code Alt+Up/Down

This keyboard shortcut is new in Visual Studio 2013. If you put the cursor on a line of code and use the Alt+Up Arrow keys, the line of code you’ve selected moves up. If you use the Alt+Down Arrow keys, the line of code selected moves down.

2. Create Collapsible Region Ctrl+M+H/Ctrl+M+U

Chances are you’ve noticed the “+” and “-” symbols in the margins that let you collapse and expand your classes and functions. Did you know you can create your own collapsible regions? If you select a section of code and then use the key sequence Ctrl+M+H, you turn that region into a collapsible/expandable region. The key sequence Ctrl+M+U will remove the collapsible region. It doesn’t delete the code, it just removes the icon that lets you expand and collapse.

3. Comment Code Block Ctrl+K+C/Ctrl+K+U

Whether it’s because you’re trying to track down a “but,” or experimenting with code change, from time to time you’ll want to comment and uncomment blocks of code. If you select a block of code and use the key sequence Ctrl+K+C, you’ll comment out the section of code. Ctrl+K+U will uncomment the code.

4. Peek Definition Alt+F12

When you’re going through your code and you want to examine the code in the method you’re calling, many programmers will use the F12 key or the pop-up menu option Go To Definition. Go To Definition will navigate to the called method; however, many times you don’t need to navigate to the code. Sometimes, you just want a quick look at the method. If you’ve installed Visual Studio 2013, there’s a new keyboard shortcut — Alt+F12 — that will give you a preview of the method being called inline. You can use the Esc key to close the preview.

5. Navigate Forward/Backward Ctrl+–/Ctrl+Shift+–

When you have multiple files open at the same time, you might want a way to quickly move back and forth between two or three different locations in your code. If you’ve moved from one location to another you can use the keyboard sequence <Ctrl>+– to move to the previous location and then you can return using Ctrl+Shift+–.

6. Ctrl-Shift-S

Saves all documents and projects. Very useful when you have many items open and are too lazy to go to the top left “File->Save All.”

7. F7

Switches from the design view to the code view in the editor.

8. Shift-F7

Switches from the code view to the design view in the editor.

Credits:

https://vslive.com/Blogs/News-and-Tips/2015/04/5-VS-Keyboard-Shortcuts.aspx
http://www.dofactory.com/reference/visual-studio-shortcuts

How Do I Remove a Background in GIMP?

How Do I Remove a Background in GIMP? 150 150 rhecht

layer—>transparency–> add alpha channel
use fuzzy select tool (magic wand looking thing) to select white background
edit—> cut
in your layers window verify there are no other layers (if not visible its accessible through windows->dockable dialogues–>layers

How to Convert DRM-Protected Kindle eBooks Using Calibre

How to Convert DRM-Protected Kindle eBooks Using Calibre rhecht

I’ve personally found this method to work (Windows + Kindle desktop app) in 2016calibre-logo:

1. Install all the software you require (Kindle Windows app, Calibre, DeDRM plugin, Python, and pycrypto)
2. Open the Kindle app for windows and login. let it download a copy of your book to the computer. This book will be in the “My Kindle Content” folder that you’ll find in your Documents folder.
3. Open Calibre and then install the DeDRM Plugin from Preferences>Plugins>Load plugin from file
4. After it’s installed, it will be highlighted. Click on “Customize plugin” and a Configuration dialogue box will open.
5. Select “Kindle for Mac/PC ebooks” and another dialogue box will open. It will most likely be blank (no keys shown.)
6. Click the green + button to create a new key. a dialogue box will open with an already suggested “default_key” in the input field. click OK.
It will be added to the list of keys and it will be the only one you see. Click Close and then OK to exit both dialogues.
7. Now we have a key added to Calibre. try opening Calibre with debug mode (so you can see if DeDRM worked) by opening Command Prompt and typing “calibre-debug -g”. Calibre will open normally, but command prompt will remain running too.
8. Locate the book you downloaded (“My Kindle Content” folder) Drag and drop it to calibre. Once it is imported, go to command prompt and you should see something like
“Decrypting. Please wait . . . . done
Decryption succeeded after 0.1 seconds”
Then as a proof of success, your ebook should open up when you double-click on it in Calbre!

Source: https://apprenticealf.wordpress.com/2012/09/10/drm-removal-tools-for-ebooks/

Review: Sylvania 9″ Tablet PC and Portable DVD Combo (SLTDV9200)

Review: Sylvania 9″ Tablet PC and Portable DVD Combo (SLTDV9200) 2560 1990 rhecht

SLTDV9200Recently I came across a unique combination: an Android Tablet/DVD player combo by Curtis/Sylvania . DVDs nowadays are practically obsolete and that the ones I previously purchased from Walmart are gathering dust. I therefore saw this as a way to watch DVDs that aren’t available on Netflix, or anywhere else for that matter. In addition, when it comes to road trips this would be handy since we don’t utilize Wifi to stream shows and it would give enable my kids to watch videos while on the road. Intrigued, I purchased a copy since the price seemed right at $118 CAD.

So far I’ve been very happy with it, considering the price. Here are some of the specs :

Functionality

It’s a practical device that does what I need it to do. In addition to playing DVDs via an app on the device, it also allows for streaming via a Netflix app or an SD card (though no microSD).

Speed

At 1 GB RAM the speed is perfect for the version of Android running (5.1)

Audio

Audio works great. I was able to increase the volume enough to hear what I wanted.

Storage

At 8 GB storage, there aren’t too many apps that can be installed. An SD card can address that but don’t install too many apps on it with data pointing to the SD card. From experience, a) the core App files still remain on the tablet, and b) when Google Play issues an update to an app, sometimes the app data goes back to the tablet (instead of the SD card). The tablet is meant to play DVDs and videos moreso than being used for other purposes. As long as you’re aware of this you will be fine.

Screen quality

Screen quality also isn’t the greatest. At 800×480 resolution there is some pixilation. However, for the price of a budget tablet screen quality is expected to suffer.

Battery life

Battery life is a bummer. The DVD specs state that operation time is 2 hours fully charged and indeed, watching two DVDs back-to-back brought the tablet from 100% charge to 5%. This is worth noting when taking it “on the road” to make sure it stays charged during the way. Connecting it to a multi-USB car charger (see below) should address that .

 

Warranty

Warranty is only 90 days, as is in all budget brands.

Conclusion

Overall, what amazes me is that other manufacturers have not made a similar product. Samsung released a DVD player that connects to a tablet, but that’s not the same. For what you pay, you are getting a solid product.

Budget Tablet Rating: 9/10

sylvania sltdvd9200

Simpsons Tapped Out – “Where’s Maggie” Fix

Simpsons Tapped Out – “Where’s Maggie” Fix rhecht

maggie-floating-with-balloonTo those that were fed up with Maggie not being tappable in the “Where’s Maggie” minigame, I found on tstoaddicts.com that the best advice is to complain by filling out this form, then select “Technical support” followed by “Game performance” in the drop down menus, at which point the advice you will likely get is to tap the building and other areas around her prior to tapping her. It apparently works.

Also, if you’re on with support, ask for some donuts for your inconvenience. You will not be denied :-)

oops-out-of-time-wheres-maggie

A Case for Time Docketing – Does it Make Sense?

A Case for Time Docketing – Does it Make Sense? rhecht
Tag Heuer Stopwatch

Tag Heuer Stopwatch

In business, relationships between the client and vendor are not only built on trust, but also transparency. Thus, when providing time-based services for a client, time docketing or recording is essential.  Time docketing isn’t perfect, but it is what it is to ensure proper client billing. The docketing system has been designed so that clients don’t take up too much time from the vendor and vice versa.

Which time docketing system is ideal? The two most common systems are the quarterly, 15 minute dockets and the 6 minute dockets more commonly seen in law firms. I’ve personally been in work situations where the docketing system has been in quarterly increments and in 6-minute increments, where ten of those equal an hour. Over time, I’ve realized that both systems are fallible. In the quarterly method for example, if one is looking at a client file, just to ensure that everything is operational, which is more accurate, the 15 minute system of 6 minute system? On the flip side, if every task is a 5-6 minute task and one doesn’t feel it’s worthwhile to docket each incident, won’t it eventually add up, leading to lots of lost revenue? And, if each 6 minute task is recorded, at what point is it considered micromanaging?

There is no clear answer as to which is better. The 6 minute system works for lawyers since they charge more per hour. At an average of $500/hour, every 6 minutes translates into another $50 dollars. Did you just think of a cool idea to win a court case while in the washroom? Bam! That’s another $50 right there.

One thing is certain: time docketing in any form is essential. I’ve also been in situations where there was no system at all (as it was based on trust) and clients took advantage of that trust. In one project there were “one last thing” changes that turned into 6 months of unbillable changes. This was a travesty that should never again happen as it can easily ruin a business relationship from growing.

So, whether you are telling a client you are keeping a docket or not, make a personal one so that, in the event that things get “out of hand” you can show the client what changes have been made in order to demonstrate that anything else constitutes going “above and beyond.” In this manner you might be able to save some otherwise-interesting business relationships, as well as ultimately save yourself.

Microsoft Excel: How to Count Number of Spaces in Cell String

Microsoft Excel: How to Count Number of Spaces in Cell String 388 321 rhecht

excel

B1: =LEN(A1)-LEN(SUBSTITUTE(A1,” “,””))

Notice the space there. You can replace that with any other letter to see how many instances of that character are present. It’s simple and genius.

Excel on Mac – How to Delete Contents of Multiple Cells

Excel on Mac – How to Delete Contents of Multiple Cells 256 256 rhecht

excel_logo

fn+delete

Also, you can do control+B

I know, with Excel on Windows it’s easier :-)

Kudos to ExcelJet for this gem.