Skip to content

Commit

Permalink
feat: Improve hash and base32 encoding functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Leizhenpeng committed Jul 6, 2024
1 parent 2df5414 commit 549746e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 60 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,38 @@
[![JSDocs][jsdocs-src]][jsdocs-href]
[![License][license-src]][license-href]

PRNG readable crypto in the style of license plates
generate readable hash or base32 in the style of license plates

`platecode` 是一个 JavaScript 库,用于生成类似车牌的可读哈希或 Base32 编码,使其更加有趣和直观。

## Features
- **车牌风格的哈希**: 使用 `hash` 函数可以生成带有或不带有表情符号的车牌风格哈希。
- **Base32 编码和解码**: 支持通过 `encode``decode` 函数对字符串进行车牌风格的 Base32 编码和解码。
- **定制选项**: 支持在编码和哈希时选择是否包含表情符号。

## Usage

### Hash
```js
import { hash } from 'platecode'

const hash = hash('hello world')
console.log(hash1) // '🍢 渝F·WGVA2 🪣

const encoded = encode('hello world', { emoji: false })
console.log(encoded) // '渝F·WGVA2'
```

### Base32
```js
import { decode, encode } from 'platecode'

const result = encode('hello world')
console.log(result) // 🎤 辽U·JBSWY 🥚 藏P·3DPFQ 🐟 苏H·QFO33 👞 湘U·SNRSC 🚿 琼M·CAAAA 🐱

const decoded = decode(result)
console.log(decoded) // 'hello world'
```
## Credit

Thanks to my friend [cunzaizhuyi](https://github.com/cunzaizhuyi) for the new repository [hashplate-cn](https://github.com/cunzaizhuyi/hashplate-cn), which I find quite interesting.
Expand Down
86 changes: 38 additions & 48 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,26 @@ import { getSeedFromString, splitMix32 } from './prng'
import { emojiDictionary } from './emoji'

export class PlateCode {
private province: string
private alphabet: string
private alphanumeric: string
emojis: string[]

constructor() {
this.province = '黑吉辽京津晋冀鲁豫蒙沪渝苏浙皖闽湘赣鄂桂琼川贵云藏陕甘宁青新粤'
this.alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ' // 去掉了 "I" 和 "O"
this.alphanumeric = 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789' // 去掉了 "I" 和 "O"
this.emojis = emojiDictionary
}

public encode = (input: string): string => {
const encoded = base32.encode(input).replace(/=/g, '') // Base32 编码,删除填充
return this.generateFullLicensePlates(encoded, input)
private readonly province = '黑吉辽京津晋冀鲁豫蒙沪渝苏浙皖闽湘赣鄂桂琼川贵云藏陕甘宁青新粤'
private readonly alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ' // Excludes "I" and "O"
private readonly alphanumeric = 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789' // Excludes "I" and "O"
private readonly emojis = emojiDictionary

public encode = (input: string, options?: { emoji?: boolean }): string => {
const encoded = base32.encode(input).replace(/=/g, '') // Base32 encode and remove padding
return this.generateFullLicensePlates(encoded, input, options?.emoji ?? true)
}

public decode = (licensePlates: string): string => {
const encoded = this.extractEncodedText(licensePlates)
return base32.decode(encoded)
}

public hash = (value: string, options?: { hasEmoji: boolean }): string => {
return this.generateHashLicensePlate(value, options?.hasEmoji ?? true)
public hash = (value: string, options?: { emoji?: boolean }): string => {
return this.generateHashLicensePlate(value, options?.emoji ?? true)
}

private generateFullLicensePlates(encoded: string, originalInput: string): string {
private generateFullLicensePlates(encoded: string, originalInput: string, includeEmoji: boolean): string {
const seed = getSeedFromString(originalInput)
const random = splitMix32(seed)

Expand All @@ -42,12 +35,10 @@ export class PlateCode {
return `${this.province[provinceIndex]}${this.alphabet[letterIndex]}·${chunk}`
})

const emojis = [] as string[]
while (emojis.length < plates.length + 2) {
const emoji = this.emojis[Math.floor(random() * this.emojis.length)]
if (!emojis.includes(emoji))
emojis.push(emoji)
}
if (!includeEmoji)
return plates.join(' ')

const emojis = this.generateUniqueEmojis(random, plates.length + 2)

const startEmoji = emojis.shift()!
const separatorEmojis = emojis
Expand All @@ -64,41 +55,40 @@ export class PlateCode {
const seed = getSeedFromString(value)
const random = splitMix32(seed)

const rand_p = Math.floor(random() * this.province.length)
const rand_alphabet = Math.floor(random() * this.alphabet.length)
const randArr = []
for (let i = 0; i < 5; i++)
randArr.push(Math.floor(random() * this.alphanumeric.length))
const provinceIndex = Math.floor(random() * this.province.length)
const letterIndex = Math.floor(random() * this.alphabet.length)
const randomAlphanumerics = this.generateRandomAlphanumeric(random, 5)

const result_no_emoji = [
`${this.province[rand_p]}${this.alphabet[rand_alphabet]}`,
`${this.alphanumeric[randArr[0]]}${this.alphanumeric[randArr[1]]}${this.alphanumeric[randArr[2]]}${this.alphanumeric[randArr[3]]}${this.alphanumeric[randArr[4]]}`,
].join('·')
const basePlate = `${this.province[provinceIndex]}${this.alphabet[letterIndex]}·${randomAlphanumerics}`

if (!hasEmoji)
return result_no_emoji
return basePlate

const result_emoji = [
this.emojis[Math.floor(random() * this.emojis.length)],
result_no_emoji,
this.emojis[Math.floor(random() * this.emojis.length)],
].join(' ')
const startEmoji = this.emojis[Math.floor(random() * this.emojis.length)]
const endEmoji = this.emojis[Math.floor(random() * this.emojis.length)]

return result_emoji
return `${startEmoji} ${basePlate} ${endEmoji}`
}

private extractEncodedText(licensePlates: string): string {
return licensePlates.split(' ').map((plate) => {
const parts = plate.split('·')
if (parts.length > 1)
return parts[1].replace(/A+$/, '')
return '' // 或者可以根据你的需求返回一个默认值或抛出错误
return parts.length > 1 ? parts[1].replace(/A+$/, '') : ''
}).join('')
}

private generateRandomAlphanumeric(random: () => number, length: number): string {
return Array.from({ length }, () => this.alphanumeric[Math.floor(random() * this.alphanumeric.length)]).join('')
}

private generateUniqueEmojis(random: () => number, count: number): string[] {
const uniqueEmojis = new Set<string>()
while (uniqueEmojis.size < count) {
const emoji = this.emojis[Math.floor(random() * this.emojis.length)]
uniqueEmojis.add(emoji)
}
return Array.from(uniqueEmojis)
}
}

export const {
decode,
encode,
hash,
} = new PlateCode()
export const { decode, encode, hash } = new PlateCode()
36 changes: 25 additions & 11 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,35 @@ describe('hash', () => {
expect(hash(input)).toBe('🍢 渝F·WGVA2 🪣')
})
it('should hash a string without emoji', () => {
expect(hash(input, { hasEmoji: false })).toBe('渝F·WGVA2')
expect(hash(input, { emoji: false })).toBe('渝F·WGVA2')
})
})

describe('base32', () => {
const answer = '🎤 辽U·JBSWY 🥚 藏P·3DPFQ 🐟 苏H·QFO33 👞 湘U·SNRSC 🚿 琼M·CAAAA 🐱'
it('should encode str with ', () => {
const input = 'Hello, World!'
const result = encode(input)
expect(result)
.toBe(answer)
describe('with emoji', () => {
const answer = '🎤 辽U·JBSWY 🥚 藏P·3DPFQ 🐟 苏H·QFO33 👞 湘U·SNRSC 🚿 琼M·CAAAA 🐱'
it('should encode str with', () => {
const input = 'Hello, World!'
const result = encode(input)
expect(result).toBe(answer)
})
it('should decode str with ', () => {
const input = answer
const result = decode(input)
expect(result).toBe('Hello, World!')
})
})
it('should decode str with ', () => {
const input = answer
const result = decode(input)
expect(result).toBe('Hello, World!')
describe('without emoji', () => {
const answer = '辽U·JBSWY 藏P·3DPFQ 苏H·QFO33 湘U·SNRSC 琼M·CAAAA'
it('should encode str without emoji', () => {
const input = 'Hello, World!'
const result = encode(input, { emoji: false })
expect(result).toBe(answer)
})
it('should decode str without emoji', () => {
const input = answer
const result = decode(input)
expect(result).toBe('Hello, World!')
})
})
})

0 comments on commit 549746e

Please sign in to comment.