Browse Source

first commit

Rich Brown 1 month ago
parent
commit
a5af8ce635
9 changed files with 2349 additions and 2 deletions
  1. BIN
      .DS_Store
  2. 19
    0
      .eslintrc.js
  3. 10
    0
      jsconfig.json
  4. 2141
    0
      package-lock.json
  5. 18
    2
      package.json
  6. 18
    0
      src/getColors.js
  7. 76
    0
      src/graphql.js
  8. 51
    0
      src/index.js
  9. 16
    0
      src/lastfm.js

BIN
.DS_Store View File


+ 19
- 0
.eslintrc.js View File

@@ -0,0 +1,19 @@
1
+module.exports = {
2
+  env: {
3
+    commonjs: true,
4
+    es6: true,
5
+    node: true
6
+  },
7
+  extends: [
8
+    "plugin:prettier/recommended"
9
+  ],
10
+  globals: {
11
+    Atomics: 'readonly',
12
+    SharedArrayBuffer: 'readonly'
13
+  },
14
+  parserOptions: {
15
+    ecmaVersion: 2018
16
+  },
17
+  rules: {
18
+  }
19
+}

+ 10
- 0
jsconfig.json View File

@@ -0,0 +1,10 @@
1
+{
2
+  "compilerOptions": {
3
+      "target": "ES5",
4
+      "module": "commonjs",
5
+  },
6
+  "exclude": [
7
+      "dist",
8
+      "node_modules"
9
+  ]
10
+}

+ 2141
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 18
- 2
package.json View File

@@ -2,11 +2,27 @@
2 2
   "name": "vibrance",
3 3
   "version": "1.0.0",
4 4
   "description": "",
5
-  "main": "index.js",
5
+  "main": "src/index.js",
6 6
   "scripts": {
7 7
     "test": "echo \"Error: no test specified\" && exit 1"
8 8
   },
9 9
   "keywords": [],
10 10
   "author": "RL Brown <rlbrown72@gmail.com> (https://www.rich-text.net)",
11
-  "license": "MIT"
11
+  "license": "MIT",
12
+  "devDependencies": {
13
+    "eslint": "^6.2.2",
14
+    "eslint-config-prettier": "^6.1.0",
15
+    "eslint-config-standard": "^14.0.1",
16
+    "eslint-plugin-import": "^2.18.2",
17
+    "eslint-plugin-node": "^9.1.0",
18
+    "eslint-plugin-prettier": "^3.1.0",
19
+    "eslint-plugin-promise": "^4.2.1",
20
+    "eslint-plugin-standard": "^4.0.1",
21
+    "prettier": "^1.18.2"
22
+  },
23
+  "dependencies": {
24
+    "axios": "^0.19.0",
25
+    "dotenv": "^8.1.0",
26
+    "node-vibrant": "^3.1.4"
27
+  }
12 28
 }

+ 18
- 0
src/getColors.js View File

@@ -0,0 +1,18 @@
1
+const Vibrant = require("node-vibrant");
2
+
3
+module.exports = imgPath =>
4
+  Vibrant.from(imgPath)
5
+    .getPalette()
6
+    .then(palette => {
7
+      const total = Object.keys(palette).reduce(
8
+        (acc, key) => acc + palette[key].population,
9
+        0
10
+      );
11
+      const shapedArray = Object.keys(palette).map(key => {
12
+        const rgb = palette[key]._rgb;
13
+        const hex = Vibrant.Util.rgbToHex(rgb[0], rgb[1], rgb[2]);
14
+        const pop = Math.floor((palette[key].population / total) * 100);
15
+        return { key, hex, pop };
16
+      });
17
+      return shapedArray.filter(a => a.pop > 0).sort((a, b) => b.pop - a.pop);
18
+    });

+ 76
- 0
src/graphql.js View File

@@ -0,0 +1,76 @@
1
+// @ts-check
2
+
3
+require("dotenv").config();
4
+
5
+const { default: axios } = require("axios");
6
+/**
7
+ * @param {string} v
8
+ */
9
+const colorValidator = v => /^#([0-9a-f]{3}){1,2}$/i.test(v);
10
+
11
+const url =
12
+  process.env.NODE_ENV === "production"
13
+    ? process.env.GRAPHQL_URL
14
+    : "http://localhost:3000/graphql";
15
+
16
+/**
17
+ * @param {string} query
18
+ * @param {{ timestamp?: string; color?: string; item?: string; value?: string; }} variables
19
+ * @param {string} token
20
+ */
21
+const graphQuery = (query, variables, token) =>
22
+  // @ts-ignore
23
+  axios({
24
+    method: "POST",
25
+    url,
26
+    headers: {
27
+      "Content-Type": "application/json",
28
+      Authorization: `Bearer ${token}`
29
+    },
30
+    data: { query, variables }
31
+  })
32
+    .then(response => response)
33
+    .catch(e => console.log(e.response.data.errors.message));
34
+
35
+/**
36
+ * @param {string} token
37
+ * @param {string} color
38
+ * @param {string} timestamp
39
+ */
40
+const updateColor = (token, color, timestamp) => {
41
+  if (!colorValidator(color)) return new Error("Not a valid #color");
42
+  const query = `mutation DoIt($timestamp: String!, $color: String!) {
43
+    updateColor(timestamp: $timestamp, color: $color) {
44
+      username
45
+      current {
46
+        color
47
+      }
48
+    }
49
+  }`;
50
+  const variables = {
51
+    timestamp,
52
+    color
53
+  };
54
+  return graphQuery(query, variables, token);
55
+};
56
+
57
+/**
58
+ * @param {string} token
59
+ * @param {string} string
60
+ */
61
+const updateDisplayName = (token, string) => {
62
+  if (!string || typeof string !== "string") return new Error("Not a string");
63
+  const query = `mutation DoDisplayNameUpdate($item: String!, $value: String!) {
64
+    updateProfileItem(item: $item, value: $value) {
65
+      username
66
+      displayName
67
+    }
68
+  }`;
69
+  const variables = { item: "DISPLAYNAME", value: string };
70
+  return graphQuery(query, variables, token);
71
+};
72
+
73
+module.exports = {
74
+  updateColor,
75
+  updateDisplayName
76
+};

+ 51
- 0
src/index.js View File

@@ -0,0 +1,51 @@
1
+require("dotenv").config();
2
+
3
+const { updateDisplayName, updateColor } = require("./graphql");
4
+
5
+const getColors = require("./getColors");
6
+const getTracksForUser = require("./lastfm");
7
+const key = process.env.LASTFM_KEY;
8
+
9
+const threeMin = 1000 * 60 * 3;
10
+const nowPlaying = {};
11
+
12
+/**
13
+ * @param {string} scrobbleName
14
+ */
15
+const watchTracks = async scrobbleName => {
16
+  const tracks = await getTracksForUser(scrobbleName, key);
17
+  // although limit=1, getTracks still sends array
18
+  const array = tracks.map(async t => {
19
+    const image = t.image.filter(i => i.size === "large")[0]["#text"];
20
+    const c = await getColors(image);
21
+    return {
22
+      artist: t.artist["#text"],
23
+      album: t.album["#text"],
24
+      track: t.name,
25
+      timestamp: t.date ? t.date.uts : Math.floor(Date.now() / 1000),
26
+      color: c[0].hex
27
+    };
28
+  });
29
+  return Promise.all(array);
30
+};
31
+
32
+/**
33
+ * @param {string} username
34
+ * @param {string} token
35
+ */
36
+const watchLastFM = (username, token) => {
37
+  if (!nowPlaying[username]) nowPlaying[username] = "";
38
+  setInterval(async () => {
39
+    const track = await watchTracks(username);
40
+    const songName = track[0].track;
41
+    const color = track[0].color;
42
+    const timestamp = track[0].timestamp.toString();
43
+    if (nowPlaying[username] === songName) return null;
44
+    nowPlaying[username] = songName;
45
+    updateDisplayName(token, songName)
46
+      .then(() => updateColor(token, color, timestamp))
47
+      .catch(e => console.log("update Failed"));
48
+  }, threeMin);
49
+};
50
+
51
+watchLastFM("rlbgator", "test4"); // TODO: update token to match a better co10rs user.

+ 16
- 0
src/lastfm.js View File

@@ -0,0 +1,16 @@
1
+// @ts-check
2
+
3
+const { default: axios } = require("axios");
4
+const root = "http://ws.audioscrobbler.com/2.0";
5
+
6
+/**
7
+ * @param {string} user
8
+ * @param {string} key
9
+ */
10
+const getTracksForUser = async (user, key) => {
11
+  const getTracksUrl = `${root}/?method=user.getrecenttracks&user=${user}&limit=1&api_key=${key}&format=json`;
12
+  const data = await axios.get(getTracksUrl);
13
+  return data.data.recenttracks.track;
14
+};
15
+
16
+module.exports = getTracksForUser;