about summary refs log tree commit diff
path: root/users/Profpatsch/lyric/extension/src/upload-lrc.ts
blob: 8627e382726b8c18984fd44aa683c229bf7f53cc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import * as crypto from 'crypto';

const USER_AGENT =
  'lyric tool (source: https://code.tvl.fyi/tree/users/Profpatsch/lyric, contact: https://mastodon.xyz/@Profpatsch)';

// Helper function to convert a hex string to a Buffer
function hexToBytes(hex: string): Buffer {
  return Buffer.from(hex, 'hex');
}

// Function to verify the nonce
function verifyNonce(result: Buffer, target: Buffer): boolean {
  if (result.length !== target.length) {
    return false;
  }

  for (let i = 0; i < result.length - 1; i++) {
    if (result[i] > target[i]) {
      return false;
    } else if (result[i] < target[i]) {
      break;
    }
  }

  return true;
}

// Function to solve the challenge
function solveChallenge(prefix: string, targetHex: string): string {
  let nonce = 0;
  let hashed: Buffer;
  const target = hexToBytes(targetHex);

  while (true) {
    const input = `${prefix}${nonce}`;
    hashed = crypto.createHash('sha256').update(input).digest();

    if (verifyNonce(hashed, target)) {
      break;
    } else {
      nonce += 1;
    }
  }

  return nonce.toString();
}

async function getUploadNonce() {
  try {
    const response = await fetch('https://lrclib.net/api/request-challenge', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': USER_AGENT,
        'Lrclib-Client': USER_AGENT,
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const challengeData = (await response.json()) as { prefix: string; target: string };

    return {
      prefix: challengeData.prefix,
      nonce: solveChallenge(challengeData.prefix, challengeData.target),
    };
  } catch (error) {
    console.error('Error fetching the challenge:', error);
  }
}

// Interface for the request body
/**
 * Represents a request to publish a track with its associated information.
 */
export interface PublishRequest {
  trackName: string;
  artistName: string;
  albumName: string;
  /** In seconds? Milliseconds? mm:ss? */
  duration: number;
  plainLyrics: string;
  syncedLyrics: string;
}

// Function to publish lyrics using the solved challenge
export async function publishLyrics(
  requestBody: PublishRequest,
): Promise<true | undefined> {
  const challenge = await getUploadNonce();
  if (!challenge) {
    return;
  }
  const publishToken = `${challenge.prefix}:${challenge.nonce}`;

  const response = await fetch('https://lrclib.net/api/publish', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'User-Agent': USER_AGENT,
      'Lrclib-Client': USER_AGENT,
      'X-Publish-Token': publishToken,
    },
    body: JSON.stringify(requestBody),
  });

  if (response.status === 201) {
    console.log('Lyrics successfully published.');
    return true;
  } else {
    const errorResponse = (await response.json()) as { [key: string]: string };
    console.error('Failed to publish lyrics:', errorResponse);
    return;
  }
}