Skip to content

Commit

Permalink
Merge pull request #1 from disgoorg/refactor/uint64-snowflakes
Browse files Browse the repository at this point in the history
update to unit64 from strings and v2
  • Loading branch information
topi314 authored May 2, 2022
2 parents cc26341 + 04ad071 commit 06f0dcc
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 66 deletions.
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,53 @@
# snowflake

snowflake is a golang library for parsing [snowflake IDs](https://docs.snowflake.com) from discord.
This package provides a custom `snowflake` type which has various utility methods for parsing snowflake IDs.
This package provides a custom `snowflake.ID` type which has various utility methods for parsing discord snowflakes.

### Installing

```sh
go get github.com/disgoorg/snowflake
go get github.com/disgoorg/snowflake/v2
```

## Usage

```go

id := Snowflake("123456789012345678")
id := snowflake.ID(123456789012345678)

// deconstructs the snowflake ID into its components timestamp, worker ID, process ID, and increment
id.Deconstruct()

// time.Time when the snowflake was generated
// the time.Time when the snowflake ID was generated
id.Time()

// the worker ID which the snowflake ID was generated
id.WorkerID()

// the process ID which the snowflake ID was generated
id.ProcessID()

// tje sequence when the snowflake ID was generated
id.Sequence()

// returns the string representation of the snowflake ID
id.String()

// returns the int64 representation of the snowflake ID
id.Int64()

// returns a new snowflake with worker ID, process ID, and increment set to 0
// returns a new snowflake ID with worker ID, process ID, and sequence set to 0
// this can be used for various pagination requests to the discord api
id = NewSnowflake(time.Now())
id := New(time.Now())

// returns the fmt.Stringer as a Snowflake
id = ParseString(...)
// returns a snowflake ID from an environment variable
id := GetEnv("guild_id")

// returns the int64 as a Snowflake
id = ParseInt64(123456789012345678)
// returns a snowflake ID from an environment variable and a bool indicating if the key was found
id, found := LookupEnv("guild_id")

// returns the uint64 as a Snowflake
id = ParseUInt64(123456789012345678)
// returns the string as a snowflake ID or an error
id, err := Parse("123456789012345678")

// returns a snowflake from an environment variable
id = GetSnowflakeEnv("guild_id")
// returns the string as a snowflake ID or panics if an error occurs
id := MustParse("123456789012345678")
```

## License
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/disgoorg/snowflake
module github.com/disgoorg/snowflake/v2

go 1.18
139 changes: 91 additions & 48 deletions snowflake.go
Original file line number Diff line number Diff line change
@@ -1,81 +1,124 @@
package snowflake

import (
"fmt"
"bytes"
"os"
"strconv"
"time"
)

var Epoch int64 = 1420070400000
// Epoch is the discord epoch in milliseconds.
const Epoch = 1420070400000

// NewSnowflake returns a new Snowflake based on the given time.Time
//goland:noinspection GoUnusedExportedFunction
func NewSnowflake(timestamp time.Time) Snowflake {
return Snowflake(strconv.FormatInt(((timestamp.UnixNano()/1_000_000)-Epoch)<<22, 10))
// Parse parses a string into a snowflake ID.
// returns ID(0) if the string is "null"
func Parse(str string) (ID, error) {
if str == "null" {
return 0, nil
}
id, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return 0, err
}
return ID(id), nil
}

// ParseString parses a fmt.Stringer into a Snowflake
//goland:noinspection GoUnusedExportedFunction
func ParseString(str fmt.Stringer) Snowflake {
return Snowflake(str.String())
// MustParse parses a string into a snowflake ID and panics on error.
// returns ID(0) if the string is "null"
func MustParse(str string) ID {
id, err := Parse(str)
if err != nil {
panic(err)
}
return id
}

// ParseInt64 parses an int64 into a Snowflake
//goland:noinspection GoUnusedExportedFunction
func ParseInt64(i int64) Snowflake {
return Snowflake(strconv.FormatInt(i, 10))
// GetEnv returns the value of the environment variable named by the key and parses it as a snowflake.
// returns ID(0) if the environment variable is not set.
func GetEnv(key string) ID {
snowflake, _ := LookupEnv(key)
return snowflake
}

// ParseUInt64 parses an uint64 into a Snowflake
//goland:noinspection GoUnusedExportedFunction
func ParseUInt64(i uint64) Snowflake {
return Snowflake(strconv.FormatUint(i, 10))
// LookupEnv returns the value of the environment variable named by the key and parses it as a snowflake.
// returns false if the environment variable is not set.
func LookupEnv(key string) (ID, bool) {
env, found := os.LookupEnv(key)
if !found {
return 0, false
}
snowflake, _ := Parse(env)
return snowflake, true
}

// GetSnowflakeEnv returns a new Snowflake from an environment variable
//goland:noinspection GoUnusedExportedFunction
func GetSnowflakeEnv(key string) Snowflake {
return Snowflake(os.Getenv(key))
// New creates a new snowflake ID from the provided timestamp with worker id and sequence 0.
func New(timestamp time.Time) ID {
return ID((timestamp.UnixMilli() - Epoch) << 22)
}

// Snowflake is a general utility type around discord's IDs
type Snowflake string
// ID represents a unique snowflake ID.
type ID uint64

// String returns the string representation of the Snowflake
func (s Snowflake) String() string {
return string(s)
// MarshalJSON marshals the snowflake ID into a JSON string.
func (id ID) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(strconv.FormatUint(uint64(id), 10))), nil
}

// Int64 returns the int64 representation of the Snowflake
func (s Snowflake) Int64() int64 {
snowflake, err := strconv.ParseInt(s.String(), 10, 64)
// UnmarshalJSON unmarshals the snowflake ID from a JSON string.
func (id *ID) UnmarshalJSON(data []byte) error {
if bytes.Equal(data, []byte("null")) {
return nil
}
snowflake, err := strconv.Unquote(string(data))
if err != nil {
panic(err.Error())
return err
}
return snowflake
i, err := strconv.ParseUint(snowflake, 10, 64)
if err != nil {
return err
}
*id = ID(i)
return nil
}

// Deconstruct returns DeconstructedSnowflake (https://discord.com/developers/docs/reference#snowflakes-snowflake-id-format-structure-left-to-right)
func (s Snowflake) Deconstruct() DeconstructedSnowflake {
snowflake := s.Int64()
return DeconstructedSnowflake{
Time: time.Unix(0, ((snowflake>>22)+Epoch)*1_000_000),
WorkerID: (snowflake & 0x3E0000) >> 17,
ProcessID: (snowflake & 0x1F000) >> 12,
Increment: snowflake & 0xFFF,
}
// String returns a string representation of the snowflake ID.
func (id ID) String() string {
return strconv.FormatUint(uint64(id), 10)
}

// Time returns the time.Time the snowflake was created.
func (id ID) Time() time.Time {
return time.UnixMilli(int64(id>>22 + Epoch))
}

// Time returns the time.Time when the snowflake was created
func (s Snowflake) Time() time.Time {
return s.Deconstruct().Time
// WorkerID returns the id of the worker the snowflake was created on.
func (id ID) WorkerID() uint8 {
return uint8(id & 0x3E0000 >> 17)
}

func (id ID) ProcessID() uint8 {
return uint8(id & 0x3E0000 >> 12)
}

// Sequence returns the sequence of the snowflake.
func (id ID) Sequence() uint16 {
return uint16(id & 0xFFF)
}

// Deconstruct returns DeconstructedID (https://discord.com/developers/docs/reference#snowflakes-snowflake-id-format-structure-left-to-right).
func (id ID) Deconstruct() DeconstructedSnowflake {
return DeconstructedSnowflake{
Time: id.Time(),
WorkerID: id.WorkerID(),
ProcessID: id.ProcessID(),
Sequence: id.Sequence(),
}
}

// DeconstructedSnowflake contains the properties used by Discord for each CommandID
// DeconstructedSnowflake contains the properties used by Discord for each snowflake ID.
type DeconstructedSnowflake struct {
Time time.Time
WorkerID int64
ProcessID int64
Increment int64
WorkerID uint8
ProcessID uint8
Sequence uint16
}

0 comments on commit 06f0dcc

Please sign in to comment.