Home

Awesome

jellyfin-avatars

keyupdate: it no longer needs the user to download the image and manually add it, it just updates the current users image with a simple tap or click

adds a html page and a button to jellyfin 10.9.x and higher for users to get more avatars

this guide was thrown together as a more in depth way of getting avatars on jellyfin from my jellyfin-mods repo.. if you like this head there and maybe you will see some other things you like.

Thanks NetfreaksShadow for the extra packs of avatars


installation

go to your web root (usually /usr/share/jellyfin/web) now run these commands

sudo wget https://github.com/BobHasNoSoul/jellyfin-avatars/archive/refs/heads/main.zip
sudo unzip main.zip
sudo mv jellyfin-avatars-main/avatars ./avatars

10.9.x version

and now we need to edit the profile tab to enable the button :D

sudo nano user-userprofile.5*.js

now replace the entire files content with the following

"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[16325,96084],{55858:function(e,r,t){t.r(r);var a=t(62540),i=t(64680),n=t(63696),o=t(9055),l=t(89100),s=t(9482),c=t(73233),d=t(22622),u=t(40532),g=t(76165),m=t(41600),f=t(56869),y=t(50764),p=t(7397),h=function(e,r){var t="function"==typeof Symbol&&e[Symbol.iterator];if(!t)return e;var a,i,n=t.call(e),o=[];try{for(;(void 0===r||r-- >0)&&!(a=n.next()).done;)o.push(a.value)}catch(e){i={error:e}}finally{try{a&&!a.done&&(t=n.return)&&t.call(n)}finally{if(i)throw i.error}}return o};r.default=function(){var e=h((0,o.ok)(),1)[0].get("userId"),r=h((0,n.useState)(""),2),t=r[0],v=r[1],A=(0,n.useRef)(null),b=(0,n.useCallback)((function(){var r=A.current;r?e?(f.Ay.show(),window.ApiClient.getUser(e).then((function(e){if(!e.Name||!e.Id)throw new Error("Unexpected null user name or id");v(e.Name),c.default.setTitle(e.Name);var t="assets/img/avatar.png";e.PrimaryImageTag&&(t=window.ApiClient.getUserImageUrl(e.Id,{tag:e.PrimaryImageTag,type:"Primary"})),r.querySelector("#image").style.backgroundImage="url("+t+")",l.default.getCurrentUser().then((function(t){var a;if(!e.Policy)throw new Error("Unexpected null user.Policy");e.PrimaryImageTag?(r.querySelector("#btnAddImage").classList.add("hide"),r.querySelector("#btnDeleteImage").classList.remove("hide")):d.g.supports("fileinput")&&((null===(a=null==t?void 0:t.Policy)||void 0===a?void 0:a.IsAdministrator)||e.Policy.EnableUserPreferenceAccess)&&(r.querySelector("#btnDeleteImage").classList.add("hide"),r.querySelector("#btnAddImage").classList.remove("hide"))})).catch((function(e){console.error("[userprofile] failed to get current user",e)})),f.Ay.hide()})).catch((function(e){console.error("[userprofile] failed to load data",e)}))):console.error("[userprofile] missing user id"):console.error("[userprofile] Unexpected null page reference")}),[e]);return(0,n.useEffect)((function(){var r=A.current;if(r){b();var t=function(e){var r,t;switch(f.Ay.hide(),null===(t=null===(r=e.target)||void 0===r?void 0:r.error)||void 0===t?void 0:t.code){case DOMException.NOT_FOUND_ERR:(0,y.A)(s.Ay.translate("FileNotFound"));break;case DOMException.ABORT_ERR:a();break;default:(0,y.A)(s.Ay.translate("FileReadError"))}},a=function(){f.Ay.hide(),(0,y.A)(s.Ay.translate("FileReadCancelled"))};r.querySelector("#btnDeleteImage").addEventListener("click",(function(){e?(0,u.A)(s.Ay.translate("DeleteImageConfirmation"),s.Ay.translate("DeleteImage")).then((function(){f.Ay.show(),window.ApiClient.deleteUserImage(e,i.y.Primary).then((function(){f.Ay.hide(),b()})).catch((function(e){console.error("[userprofile] failed to delete image",e)}))})).catch((function(){})):console.error("[userprofile] missing user id")})),r.querySelector("#btnAddImage").addEventListener("click",(function(){var e=r.querySelector("#uploadImage");e.value="",e.click()})),r.querySelector("#uploadImage").addEventListener("change",(function(n){!function(n){var o=r.querySelector("#image"),l=n.target.files[0];if(!l||!/image.*/.exec(l.type))return!1;var s=new FileReader;s.onerror=t,s.onabort=a,s.onload=function(){e?(o.style.backgroundImage="url("+s.result+")",window.ApiClient.uploadUserImage(e,i.y.Primary,l).then((function(){f.Ay.hide(),b()})).catch((function(e){console.error("[userprofile] failed to upload image",e)}))):console.error("[userprofile] missing user id")},s.readAsDataURL(l)}(n)}));

        var btnMoreAvatars = document.createElement("button");
        btnMoreAvatars.id = "btnMoreAvatars";
        btnMoreAvatars.className = "raised emby-button"; // Match the style of Delete Image button
        btnMoreAvatars.innerHTML = s.Ay.translate("More Avatars");
        btnMoreAvatars.addEventListener("click", function() {
            window.location.href = "/web/avatars/index.html";
        });

        var btnDeleteImage = r.querySelector("#btnDeleteImage");
        if (btnDeleteImage && btnDeleteImage.parentNode) {
            btnDeleteImage.parentNode.insertBefore(btnMoreAvatars, btnDeleteImage.nextSibling);
        }
    } else console.error("[userprofile] Unexpected null page reference")}),[b,e]),(0,a.jsx)(p.A,{id:"userProfilePage",title:s.Ay.translate("Profile"),className:"mainAnimatedPage libraryPage userPreferencesPage userPasswordPage noSecondaryNavPage",children:(0,a.jsxs)("div",{ref:A,className:"padded-left padded-right padded-bottom-page",children:[(0,a.jsxs)("div",{className:"readOnlyContent",style:{margin:"0 auto",marginBottom:"1.8em",padding:"0 1em",display:"flex",flexDirection:"row",alignItems:"center"},children:[(0,a.jsxs)("div",{className:"imagePlaceHolder",style:{position:"relative",display:"inline-block",maxWidth:200},children:[(0,a.jsx)("input",{id:"uploadImage",type:"file",accept:"image/*",style:{position:"absolute",right:0,width:"100%",height:"100%",opacity:0,cursor:"pointer"}}),(0,a.jsx)("div",{id:"image",style:{width:200,height:200,backgroundRepeat:"no-repeat",backgroundPosition:"center",borderRadius:"100%",backgroundSize:"cover"}})]}),(0,a.jsxs)("div",{style:{verticalAlign:"top",margin:"1em 2em",display:"flex",flexDirection:"column",alignItems:"center"},children:[(0,a.jsx)("h2",{className:"username",style:{margin:0,fontSize:"xx-large"},children:t}),(0,a.jsx)("br",{}),(0,a.jsx)(g.A,{type:"button",id:"btnAddImage",className:"raised emby-button hide",title:"ButtonAddImage"}),(0,a.jsx)(g.A,{type:"button",id:"btnDeleteImage",className:"raised emby-button hide",title:"DeleteImage"})]})]}),(0,a.jsx)(m.A,{userId:e})]})})}}}]);

now you can just simply save this file

one extra edit is needed to make this work (or you will get an api error)

simply edit index.html and add find and replace </body></html> with

<script>
// Function to save credentials to sessionStorage
function saveCredentialsToSessionStorage(credentials) {
  try {
    // Store the credentials in sessionStorage
    sessionStorage.setItem('json-credentials', JSON.stringify(credentials));
    console.log('Credentials saved to sessionStorage.');
  } catch (error) {
    console.error('Error saving credentials:', error);
  }
}

// Function to save the API key to sessionStorage
function saveApiKey(apiKey) {
  try {
    sessionStorage.setItem('api-key', apiKey);
    console.log('API key saved to sessionStorage.');
  } catch (error) {
    console.error('Error saving API key:', error);
  }
}

// Override the default console.log function
(function() {
  var originalConsoleLog = console.log;

  console.log = function(message) {
    // Call the original console.log method
    originalConsoleLog.apply(console, arguments);

    // Check if the message contains the JSON credentials
    if (typeof message === 'string' && message.startsWith('Stored JSON credentials:')) {
      try {
        // Extract the JSON credentials from the message
        var jsonString = message.substring('Stored JSON credentials: '.length);
        var credentials = JSON.parse(jsonString);

        // Save the credentials to sessionStorage
        saveCredentialsToSessionStorage(credentials);
      } catch (error) {
        console.error('Error parsing credentials:', error);
      }
    }

    // Check if the message contains the WebSocket URL with api_key
    if (typeof message === 'string' && message.startsWith('opening web socket with url:')) {
      try {
        // Extract the API key from the message
        var url = message.split('url:')[1].trim();
        var urlParams = new URL(url).searchParams;
        var apiKey = urlParams.get('api_key');

        if (apiKey) {
          saveApiKey(apiKey);
        }
      } catch (error) {
        console.error('Error extracting API key:', error);
      }
    }
  };
})();
</script>
</body></html>

now save the file and clear cache and reload in your client browser / app

all set :D

10.10.x version

for 10.10.x

sudo nano user-userprofile.9cdbcbd8b4ed7a184e73.chunk.js

find className:"raised hide",title:"DeleteImage"})

replace it with


className:"raised hide",title:"DeleteImage"}),
(0, n.jsx)("a", {
    href: "/web/avatars/index.html",
    className: "raised button-submit",
    style: { marginTop: "1em", display: "inline-block", textDecoration: "none", padding: "0.5em 1em", backgroundColor: "#007BFF", color: "#fff", borderRadius: "5px", textAlign: "center" },
    children: "More Avatars"
})

now you can just simply save this file

one extra edit is needed to make this work (or you will get an api error)

simply edit index.html and add find and replace </body></html> with

<script>
// Function to save credentials to sessionStorage
function saveCredentialsToSessionStorage(credentials) {
  try {
    // Store the credentials in sessionStorage
    sessionStorage.setItem('json-credentials', JSON.stringify(credentials));
    console.log('Credentials saved to sessionStorage.');
  } catch (error) {
    console.error('Error saving credentials:', error);
  }
}

// Function to save the API key to sessionStorage
function saveApiKey(apiKey) {
  try {
    sessionStorage.setItem('api-key', apiKey);
    console.log('API key saved to sessionStorage.');
  } catch (error) {
    console.error('Error saving API key:', error);
  }
}

// Override the default console.log function
(function() {
  var originalConsoleLog = console.log;

  console.log = function(message) {
    // Call the original console.log method
    originalConsoleLog.apply(console, arguments);

    // Check if the message contains the JSON credentials
    if (typeof message === 'string' && message.startsWith('Stored JSON credentials:')) {
      try {
        // Extract the JSON credentials from the message
        var jsonString = message.substring('Stored JSON credentials: '.length);
        var credentials = JSON.parse(jsonString);

        // Save the credentials to sessionStorage
        saveCredentialsToSessionStorage(credentials);
      } catch (error) {
        console.error('Error parsing credentials:', error);
      }
    }

    // Check if the message contains the WebSocket URL with api_key
    if (typeof message === 'string' && message.startsWith('opening web socket with url:')) {
      try {
        // Extract the API key from the message
        var url = message.split('url:')[1].trim();
        var urlParams = new URL(url).searchParams;
        var apiKey = urlParams.get('api_key');

        if (apiKey) {
          saveApiKey(apiKey);
        }
      } catch (error) {
        console.error('Error extracting API key:', error);
      }
    }
  };
})();
</script>
</body></html>

now save the file and clear cache and reload in your client browser / app

all set :D