As we finally managed to get through the Chase IVR only to be told that the expected wait time is ten minutes … in addition to the eight minutes we’ve already spent trying to convince their IVR that we didn’t need to pay our bill or check out balance, I wonder how feasible it would be to drop the caller into the CS rep queue when the call is first answered, let them spend some of their hold time telling the IVR what exactly they need (possibly moving their placeholder to another queue if, say, they seem to want tech support and not general CS), and either deleting the placeholder because the caller’s problem actually got resolved by the computer or adding a “ready to talk” attribute to the placeholder so the call is ready to transfer out to the next available agent. Bummer that I haven’t done IVR call route development in decades because I’d love to prototype that logic and see if it actually works (it’s possible we’d just end up with hundreds of pointers to people who are still navigating the IVR instead of actually reducing wait times).
Exporting Microsoft Stream Transcripts with Timecode URLs
I’d created a couple of quick code snippets to export Microsoft Stream transcripts & someone asked if you could include a way for users to click on a hyperlink and pop into the video at the right spot for the line in the transcript they clicked. Seemed like a good idea — I’ve searched though my meeting transcript & now I want to see/hear that important part in the original video.
The method I’m using to grab the transcript text actually grabs a LOT of information that’s thrown into an object being called ‘t”:

I was only using t.eventData.text to build my transcript. What do you need in order to create a jump-to-this-timecode URL for a Stream video? I had no idea! Luckily, MS supplied an easy answer — if you share a video, one of the options is to start the video at a specific time. If you pass in “st” (which I assume stands for ‘start time’) and the number of seconds ( (17 * 60) + 39 = 1059, so the 17:39 from my video matches up with 1059 seconds in the st)

We still need the unique ID assigned to the video, but … I’m exporting the transcript from MS’s Stream site, which includes the ID in the URL. So I’m able to use window.location.href to get the URL, then strip everything past the ? … now I’ve got a way to create timecoded links to video content. I just need to glom that into the code I am using to export the transcript.
Question is … how to display it to the user? Clicking on a link for 1059 seconds doesn’t really mean anything. If I were doing this at work, I might pass the number of seconds through a “pretty time” function to convert that number of seconds back into hour:minute:second format so the user clicks on 17:39 … but, as a quick example, this builds hyperlinks with the integer number of seconds as text:
var strURL = window.location.href;
strURLBase = strURL.substring(0, strURL.indexOf('?'));
var arrayTranscriptionLines = window.angular.element(window.document.querySelectorAll('.transcript-list')).scope().$ctrl.transcriptLines.map((t) => {
var strTimecodeURL = '<a href="' + strURLBase + '?st=' + t.startSeconds + '">' + t.startSeconds + '</a>'
return strTimecodeURL + "&nbps;&nbps;&nbps;&nbps;" + t.eventData.text;
});
console.log(arrayTranscriptionLines);
I might also just link the transcript text to the appropriate URL. Then clicking on the text “I want you to remember this” would jump you to the right place in the video where that line occurs:
var strURL = window.location.href;
strURLBase = strURL.substring(0, strURL.indexOf('?'));
var arrayTranscriptionLines = window.angular.element(window.document.querySelectorAll('.transcript-list')).scope().$ctrl.transcriptLines.map((t) => {
var strResult = '<a href="' + strURLBase + '?st=' + t.startSeconds + '">' + t.eventData.text + '</a>'
return strResult;
});
console.log(arrayTranscriptionLines);
And we’ve got hyperlinked text that jumps to the right spot:

Smoked Pork Picnic
Shotgun Shopping
We want to get a semi-auto 12 gauge shotgun for hunting waterfowl (and maybe deer). I like the box mags, so there are a couple of options we’re considering:
TriStar KRX — cheap gun available at Cabelas. Fixed stock.
Panzer KMR — A little more expensive. Can replace stock, but not telescoping.
UTAS looks like they’ve got a gun with an adjustable stock in the 500 dollar range
Remington 870 DM looks great too
Where did THAT come from? (PHP class)
I’ve got some code that is cobbled together from a couple of different places & it’s got namespace collisions that wouldn’t exist if I’d been starting from scratch. But I’ve got what I’ve got … and, occasionally, new code falls over because a class has already been declared.
Luckily, there’s a way to find out from where a class was loaded:
$strClassName = "Oracle_Cred";
if( class_exists($strClassName) ){
$reflector = new \ReflectionClass($strClassName);
echo "Class $strClassName was loaded from " . $reflector->getFileName();
}
else{
echo "Class $strClassName does not exist yet";
}
Spatchcock butchering method
We butchered our broilers and ducks for the year. In a larger household, a whole bird is probably a perfectly reasonable amount of food. But, for us? It’s too much food. Half a bird is a lot more reasonable.
In looking at techniques for grilling and smoking poultry, we came across spatchcocking — basically splitting the whole bird along the spine so it lays flat. It looked like a much quicker way to butcher — and, if we didn’t want to have a whole bird in the end anyway it isn’t like the approach would be counterproductive.
So we’ve been butchering by detaching the crop, airway, and throat. Placing the bird so the backbone is up and the neck facing you, cut along the spine. It’s a little tricky to cut at the hip joint — you’ve got to find the right spot to snip, but the oyster is always included with the leg using this method — and be careful not to pierce intestines. You can leave the spine with one half or cut down the other side of the spine. Cut around the vent, then clear out all of the innards — one entire mass is removed. Either finish spatchcocking to store a whole bird or use shears to cut along the breastbone and have two halves. I’ve found this approach to be a lot quicker than the normal technique — and, since the carcass is open, removing the innards is very easy.
Bash – Spaces, Quotes, and String Replacement
Had to figure out how to do string replacement (Scott wanted to convert WMA files to similarly named MP3 files) and pass a single parameter that has spaces into a shell script.
${original_string/thing_to_find/thing_to_replace_there} does string replacement. And $@ is the unexpanded parameter set. So this wouldn’t work if we were trying to pass in more than one parameter (well, it *would* … I’d just have to custom-handle parameter expansion in the script)
Maple Mustard Dressing
Maple Mustard Dressing
Course: SaladsCuisine: AmericanDifficulty: Easy4
servings10
minutesIngredients
1/4 cup maple syrup
1/4 c cider vinegar
1/4 c olive oil
3 T Heinz’s Spicy Brown mustard
1 tsp salt
Method
- Mix it all together to create an emulsion
SSO In Apache HTTPD – OAuth2
PingID is another external authentication source that looks to be replacing ADFS at work in the not-too-distant future. Unfortunately, I’ve not been able to get anyone to set up the “other side” of this authentication method … so the documentation is untested. There is an Apache Integration Kit available from PingID (https://www.pingidentity.com/en/resources/downloads/pingfederate.html). Documentation for setup is located at https://docs.pingidentity.com/bundle/pingfederate-apache-linux-ik/page/kxu1563994990311.html
Alternately, you can use OAuth2 through Apache HTTPD to authenticate users against PingID. To set up OAuth, you’ll need the mod_auth_openidc module (this is also available from the RedHat dnf repository). You’ll also need the client ID and secret that make up the OAuth2 client credentials. The full set of configuration parameters used in /etc/httpd/conf.d/auth_openidc.conf (or added to individual site-httpd.conf files) can be found at https://github.com/zmartzone/mod_auth_openidc/blob/master/auth_openidc.conf
As I am not able to register to use PingID, I am using an alternate OAUTH2 provider for authentication. The general idea should be the same for PingID – get the metadata URL, client ID, and secret added to the oidc configuration.
Setting up Google OAuth Client:
Register OAuth on Google Cloud Platform (https://console.cloud.google.com/) – Under “API & Services”, select “OAuth Consent Screen”. Build a testing app – you can use URLs that don’t go anywhere interesting, but if you want to publish the app for real usage, you’ll need real stuff.
Under “API & Services”, select “Credentials”. Select “Create Credentials” and select “OAuth Client ID”
Select the application type “Web application” and provide a name for the connection
You don’t need any authorized JS origins. Add the authorized redirect URI(s) appropriate for your host. In this case, the internal URI is my docker host, off port on 7443. The generally used URI is my reverse proxy server. I’ve had redirect URI mismatch errors when the authorized URIs don’t both include and exclude the trailing slash. Click “Create” to complete the operation.
You’ll see a client ID and secret – stash those as we’ll need to drop them into the openidc config file. Click “OK” and we’re ready to set up the web server.
Setting Up Apache HTTPD to use mod_auth_openidc
Clone the mod_auth_openidc repo (https://github.com/zmartzone/mod_auth_openidc.git) – I made one change to the Dockerfile. I’ve seen general guidance that using ENV to set DEBIAN_FRONTEND to noninteractive is not ideal, so I replaced that line with the transient form of the directive:
ARG DEBIAN_FRONTEND=noninteractive
I also changed the index.php file to
RUN echo "<html><head><title>Sample OAUTH Site</title><head><body><?php print $_SERVER['OIDC_CLAIM_email'] ; ?><pre><?php print_r(array_map(\"htmlentities\", apache_request_headers())); ?></pre><a href=\"/protected/?logout=https%3A%2F%2Fwww.rushworth.us%2Floggedout.html\">Logout</a></body></html>" > /var/www/html/protected/index.php
Build an image:
docker build -t openidc:latest .
Create an openidc.conf file on your file system. We’ll bind this file into the container so our config is in place instead of the default one. In my example, I have created “/opt/openidc.conf”. File content included below (although you’ll need to use your client ID and secret and your hostname). I’ve added a few claims so we have access to the name and email address (email address is the logon ID)
Then run a container using the image. My sandbox is fronted by a reverse proxy, so the port used doesn’t have to be well known.
docker run --name openidc -p 7443:443 -v /opt/openidc.conf:/etc/apache2/conf-available/openidc.conf -it openidc /bin/bash -c "source /etc/apache2/envvars && valgrind --leak-check=full /usr/sbin/apache2 -X"
* In my case, the docker host is not publicly available. I’ve also added the following lines to the reverse proxy at www.rushworth.us
ProxyPass /protected https://docker.rushworth.us:7443/protected ProxyPassReverse /protected https://docker.rushworth.us:7443/protected
Access https://www.rushworth.us/protected/index.php (I haven’t published my app for Google’s review, so it’s locked down to use by registered accounts only … at this time, that’s only my ID. I can register others too.) You’ll be bounced over to Google to provide authentication, then handed back to my web server.
We can then use the OIDC_CLAIM_email — $_SERVER[‘OIDC_CLAIM_email’] – to continue in-application authorization steps (if needed).
openidc.conf content:
LogLevel auth_openidc:debug LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so OIDCSSLValidateServer On OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration OIDCClientID uuid-thing.apps.googleusercontent.com OIDCClientSecret uuid-thingU4W OIDCCryptoPassphrase S0m3S3cr3tPhrA53 OIDCRedirectURI https://www.rushworth.us/protected OIDCAuthNHeader X-LJR-AuthedUser OIDCScope "openid email profile" <Location /protected> AuthType openid-connect Require valid-user </Location> OIDCOAuthSSLValidateServer On OIDCOAuthRemoteUserClaim Username
SSO In Apache HTTPD — ADFS
Active Directory Federated Services (ADFS) can be used by servers inside or outside of the company network. This makes it an especially attractive authentication option for third party companies as no B2B connectivity is required to just authenticate the user base. Many third-party vendors are starting to support ADFS authentication in their out-of-the-box solution (in which case they should be able to provide config documentation), but anything hosted on Apache HTTPD can be configured using these directions:
This configuration uses the https://github.com/UNINETT/mod_auth_mellon module — I’ve built this from the repo. Once mod_auth_mellon is installed, create a directory for the configuration
mkdir /etc/httpd/mellon
Then cd into the directory and run the config script:
/usr/libexec/mod_auth_mellon/mellon_create_metadata.sh urn:samplesite:site.example.com "https://site.example.com/auth/endpoint/"
You will now have three files in the config directory – an XML file along with a cert/key pair. You’ll also need the FederationMetadata.xml from the IT group – it should be
Now configure the module – e.g. a file /etc/httpd/conf.d/20-mellon.conf – with the following:
MellonCacheSize 100 MellonLockFile /var/run/mod_auth_mellon.lock MellonPostTTL 900 MellonPostSize 1073741824 MellonPostCount 100 MellonPostDirectory "/var/cache/mod_auth_mellon_postdata"
To authenticate users through the ADFS directory, add the following to your site config
MellonEnable "auth" Require valid-user AuthType "Mellon" MellonVariable "cookie" MellonSPPrivateKeyFile /etc/httpd/mellon/urn_samplesite_site.example.com.key MellonSPCertFile /etc/httpd/mellon/urn_samplesite_site.example.com.cert MellonSPMetadataFile /etc/httpd/mellon/urn_samplesite_site.example.com.xml MellonIdPMetadataFile /etc/httpd/mellon/FederationMetadata.xml MellonMergeEnvVars On ":" MellonEndpointPath /auth/endpoint
Provide the XML file and certificate to the IT team that manages ADFS to configure the relying party trust.