{"id":20829784,"url":"https://github.com/pdf/golifx","last_synced_at":"2025-05-07T22:10:35.366Z","repository":{"id":35409030,"uuid":"39673636","full_name":"pdf/golifx","owner":"pdf","description":"golifx provides a simple Go interface to the LIFX LAN protocol","archived":false,"fork":false,"pushed_at":"2021-10-10T19:03:19.000Z","size":176,"stargazers_count":25,"open_issues_count":2,"forks_count":12,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-31T14:40:12.645Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pdf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-07-25T05:25:59.000Z","updated_at":"2024-05-01T06:04:25.000Z","dependencies_parsed_at":"2022-09-17T04:10:21.964Z","dependency_job_id":null,"html_url":"https://github.com/pdf/golifx","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fgolifx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fgolifx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fgolifx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fgolifx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pdf","download_url":"https://codeload.github.com/pdf/golifx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252961841,"owners_count":21832197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T23:22:08.205Z","updated_at":"2025-05-07T22:10:35.337Z","avatar_url":"https://github.com/pdf.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://drone.io/github.com/pdf/golifx/status.png)](https://drone.io/github.com/pdf/golifx/latest) [![GoDoc](https://godoc.org/github.com/pdf/golifx?status.svg)](http://godoc.org/github.com/pdf/golifx) ![License-MIT](http://img.shields.io/badge/license-MIT-red.svg)\n\n__Note:__ This library is at a moderately early stage - functionality is quite\nsolid, but the V2 protocol implementation needs documentation and tests.\n\n*v1.0.0 breaks the API for subscriptions - all subscription targets now just embed common.SubscriptionProvider, see the documentation for those methods below.*\n\nYou may find binaries for a trivial CLI application that allows querying and\ncontrolling your LIFX devices under [releases](https://github.com/pdf/golifx/releases/latest).\n\nAlternatively, if you have Go installed, you may install the `lifx` command from\nsource like so:\n\n```shell\ngo get -u github.com/pdf/golifx/cmd/lifx\n```\n\nThe `lifx` command will be available at `${GOPATH}/bin/lifx`\n\n# golifx\n--\n    import \"github.com/pdf/golifx\"\n\nPackage golifx provides a simple Go interface to the LIFX LAN protocol.\n\nBased on the protocol documentation available at: http://lan.developer.lifx.com/\n\nAlso included in cmd/lifx is a small CLI utility that allows interacting with\nyour LIFX devices on the LAN.\n\nIn various parts of this package you may find references to a Device or a Light.\nThe LIFX protocol makes room for future non-light devices by making a light a\nsuperset of a device, so a Light is a Device, but a Device is not necessarily a\nLight. At this stage, LIFX only produces lights though, so they are the only\ntype of device you will interact with.\n\n## Usage\n\n```go\nconst (\n\t// VERSION of this library\n\tVERSION = \"1.0.4\"\n)\n```\n\n#### func  SetLogger\n\n```go\nfunc SetLogger(logger common.Logger)\n```\nSetLogger allows assigning a custom levelled logger that conforms to the\ncommon.Logger interface. To capture logs generated during client creation, this\nshould be called before creating a Client. Defaults to common.StubLogger, which\ndoes no logging at all.\n\n#### type Client\n\n```go\ntype Client struct {\n\tcommon.SubscriptionProvider\n\tsync.RWMutex\n}\n```\n\nClient provides a simple interface for interacting with LIFX devices. Client can\nnot be instantiated manually or it will not function - always use NewClient() to\nobtain a Client instance.\n\n#### func  NewClient\n\n```go\nfunc NewClient(p common.Protocol) (*Client, error)\n```\nNewClient returns a pointer to a new Client and any error that occurred\ninitializing the client, using the protocol p. It also kicks off a discovery\nrun.\n\n#### func (*Client) Close\n\n```go\nfunc (c *Client) Close() error\n```\nClose signals the termination of this client, and cleans up resources\n\n#### func (*Client) GetDeviceByID\n\n```go\nfunc (c *Client) GetDeviceByID(id uint64) (common.Device, error)\n```\nGetDeviceByID looks up a device by its `id` and returns a common.Device. May\nreturn a common.ErrNotFound error if the lookup times out without finding the\ndevice.\n\n#### func (*Client) GetDeviceByLabel\n\n```go\nfunc (c *Client) GetDeviceByLabel(label string) (common.Device, error)\n```\nGetDeviceByLabel looks up a device by its `label` and returns a common.Device.\nMay return a common.ErrNotFound error if the lookup times out without finding\nthe device.\n\n#### func (*Client) GetDevices\n\n```go\nfunc (c *Client) GetDevices() (devices []common.Device, err error)\n```\nGetDevices returns a slice of all devices known to the client, or\ncommon.ErrNotFound if no devices are currently known.\n\n#### func (*Client) GetGroupByID\n\n```go\nfunc (c *Client) GetGroupByID(id string) (common.Group, error)\n```\nGetGroupByID looks up a group by its `id` and returns a common.Group. May return\na common.ErrNotFound error if the lookup times out without finding the group.\n\n#### func (*Client) GetGroupByLabel\n\n```go\nfunc (c *Client) GetGroupByLabel(label string) (common.Group, error)\n```\nGetGroupByLabel looks up a group by its `label` and returns a common.Group. May\nreturn a common.ErrNotFound error if the lookup times out without finding the\ngroup.\n\n#### func (*Client) GetGroups\n\n```go\nfunc (c *Client) GetGroups() (groups []common.Group, err error)\n```\nGetGroups returns a slice of all groups known to the client, or\ncommon.ErrNotFound if no groups are currently known.\n\n#### func (*Client) GetLightByID\n\n```go\nfunc (c *Client) GetLightByID(id uint64) (light common.Light, err error)\n```\nGetLightByID looks up a light by its `id` and returns a common.Light. May return\na common.ErrNotFound error if the lookup times out without finding the light, or\ncommon.ErrDeviceInvalidType if the device exists but is not a light.\n\n#### func (*Client) GetLightByLabel\n\n```go\nfunc (c *Client) GetLightByLabel(label string) (common.Light, error)\n```\nGetLightByLabel looks up a light by its `label` and returns a common.Light. May\nreturn a common.ErrNotFound error if the lookup times out without finding the\nlight, or common.ErrDeviceInvalidType if the device exists but is not a light.\n\n#### func (*Client) GetLights\n\n```go\nfunc (c *Client) GetLights() (lights []common.Light, err error)\n```\nGetLights returns a slice of all lights known to the client, or\ncommon.ErrNotFound if no lights are currently known.\n\n#### func (*Client) GetLocationByID\n\n```go\nfunc (c *Client) GetLocationByID(id string) (common.Location, error)\n```\nGetLocationByID looks up a location by its `id` and returns a common.Location.\nMay return a common.ErrNotFound error if the lookup times out without finding\nthe location.\n\n#### func (*Client) GetLocationByLabel\n\n```go\nfunc (c *Client) GetLocationByLabel(label string) (common.Location, error)\n```\nGetLocationByLabel looks up a location by its `label` and returns a\ncommon.Location. May return a common.ErrNotFound error if the lookup times out\nwithout finding the location.\n\n#### func (*Client) GetLocations\n\n```go\nfunc (c *Client) GetLocations() (locations []common.Location, err error)\n```\nGetLocations returns a slice of all locations known to the client, or\ncommon.ErrNotFound if no locations are currently known.\n\n#### func (*Client) GetRetryInterval\n\n```go\nfunc (c *Client) GetRetryInterval() *time.Duration\n```\nGetRetryInterval returns the currently configured retry interval for operations\non this client\n\n#### func (*Client) GetTimeout\n\n```go\nfunc (c *Client) GetTimeout() *time.Duration\n```\nGetTimeout returns the currently configured timeout period for operations on\nthis client\n\n#### func (*Client) SetColor\n\n```go\nfunc (c *Client) SetColor(color common.Color, duration time.Duration) error\n```\nSetColor broadcasts a request to change the color of all devices on the network.\n\n#### func (*Client) SetDiscoveryInterval\n\n```go\nfunc (c *Client) SetDiscoveryInterval(interval time.Duration) error\n```\nSetDiscoveryInterval causes the client to discover devices and state every\ninterval. You should set this to a non-zero value for any long-running process,\notherwise devices will only be discovered once.\n\n#### func (*Client) SetPower\n\n```go\nfunc (c *Client) SetPower(state bool) error\n```\nSetPower broadcasts a request to change the power state of all devices on the\nnetwork. A state of true requests power on, and a state of false requests power\noff.\n\n#### func (*Client) SetPowerDuration\n\n```go\nfunc (c *Client) SetPowerDuration(state bool, duration time.Duration) error\n```\nSetPowerDuration broadcasts a request to change the power state of all devices\non the network, transitioning over the specified duration. A state of true\nrequests power on, and a state of false requests power off. Not all device types\nsupport transitioning, so if you wish to change the state of all device types,\nyou should use SetPower instead.\n\n#### func (*Client) SetRetryInterval\n\n```go\nfunc (c *Client) SetRetryInterval(retryInterval time.Duration)\n```\nSetRetryInterval sets the retry interval for operations on this client. If a\ntimeout has been set, and the retry interval exceeds the timeout, the retry\ninterval will be set to half the timeout\n\n#### func (*Client) SetTimeout\n\n```go\nfunc (c *Client) SetTimeout(timeout time.Duration)\n```\nSetTimeout sets the time that client operations wait for results before\nreturning an error. The special value of 0 may be set to disable timeouts, and\nall operations will wait indefinitely, but this is not recommended.\n# common\n--\n    import \"github.com/pdf/golifx/common\"\n\nPackage common contains common elements for the golifx client and protocols\n\n## Usage\n\n```go\nconst (\n\t// DefaultTimeout is the default duration after which operations time out\n\tDefaultTimeout = 2 * time.Second\n\t// DefaultRetryInterval is the default interval at which operations are\n\t// retried\n\tDefaultRetryInterval = 100 * time.Millisecond\n)\n```\n\n```go\nvar (\n\t// ErrNotFound not found\n\tErrNotFound = errors.New(`Not found`)\n\t// ErrProtocol protocol error\n\tErrProtocol = errors.New(`Protocol error`)\n\t// ErrDuplicate already exists\n\tErrDuplicate = errors.New(`Already exists`)\n\t// ErrInvalidArgument invalid argument\n\tErrInvalidArgument = errors.New(`Invalid argument`)\n\t// ErrClosed connection closed\n\tErrClosed = errors.New(`Connection closed`)\n\t// ErrTimeout timed out\n\tErrTimeout = errors.New(`Timed out`)\n\t// ErrDeviceInvalidType invalid device type\n\tErrDeviceInvalidType = errors.New(`Invalid device type`)\n\t// ErrUnsupported operation is not supported\n\tErrUnsupported = errors.New(`Operation not supported`)\n)\n```\n\n#### func  ColorEqual\n\n```go\nfunc ColorEqual(a, b Color) bool\n```\nColorEqual tests whether two Colors are equal\n\n#### func  SetLogger\n\n```go\nfunc SetLogger(logger Logger)\n```\nSetLogger wraps the supplied logger with a logPrefixer to denote golifx logs\n\n#### type Client\n\n```go\ntype Client interface {\n\tGetTimeout() *time.Duration\n\tGetRetryInterval() *time.Duration\n}\n```\n\nClient defines the interface required by protocols\n\n#### type Color\n\n```go\ntype Color struct {\n\tHue        uint16 `json:\"hue\"`        // range 0 to 65535\n\tSaturation uint16 `json:\"saturation\"` // range 0 to 65535\n\tBrightness uint16 `json:\"brightness\"` // range 0 to 65535\n\tKelvin     uint16 `json:\"kelvin\"`     // range 2500° (warm) to 9000° (cool)\n}\n```\n\nColor is used to represent the color and color temperature of a light. The color\nis represented as a 48-bit HSB (Hue, Saturation, Brightness) value. The color\ntemperature is represented in K (Kelvin) and is used to adjust the warmness /\ncoolness of a white light, which is most obvious when saturation is close zero.\n\n#### func  AverageColor\n\n```go\nfunc AverageColor(colors ...Color) (color Color)\n```\nAverageColor returns the average of the provided colors\n\n#### type Device\n\n```go\ntype Device interface {\n\t// Returns the ID for the device\n\tID() uint64\n\n\t// GetLabel gets the label for the device\n\tGetLabel() (string, error)\n\t// SetLabel sets the label for the device\n\tSetLabel(label string) error\n\t// GetPower requests the current power state of the device, true for on,\n\t// false for off\n\tGetPower() (bool, error)\n\t// CachedPower returns the last known power state of the device, true for\n\t// on, false for off\n\tCachedPower() bool\n\t// SetPower sets the power state of the device, true for on, false for off\n\tSetPower(state bool) error\n\t// GetFirmwareVersion returns the firmware version of the device\n\tGetFirmwareVersion() (string, error)\n\t// CachedFirmwareVersion returns the last known firmware version of the\n\t// device\n\tCachedFirmwareVersion() string\n\t// GetProductName returns the product name of the device\n\tGetProductName() (string, error)\n\n\t// Device is a SubscriptionTarget\n\tSubscriptionTarget\n}\n```\n\nDevice represents a generic LIFX device\n\n#### type ErrNotImplemented\n\n```go\ntype ErrNotImplemented struct {\n\tMethod string\n}\n```\n\nErrNotImplemented not implemented\n\n#### func (*ErrNotImplemented) Error\n\n```go\nfunc (e *ErrNotImplemented) Error() string\n```\nError satisfies the error interface\n\n#### type EventExpiredDevice\n\n```go\ntype EventExpiredDevice struct {\n\tDevice Device\n}\n```\n\nEventExpiredDevice is emitted by a Client or Group when a Device is no longer\nknown\n\n#### type EventExpiredGroup\n\n```go\ntype EventExpiredGroup struct {\n\tGroup Group\n}\n```\n\nEventExpiredGroup is emitted by a Client or Group when a Group is no longer\nknown\n\n#### type EventExpiredLocation\n\n```go\ntype EventExpiredLocation struct {\n\tLocation Location\n}\n```\n\nEventExpiredLocation is emitted by a Client or Group when a Location is no\nlonger known\n\n#### type EventNewDevice\n\n```go\ntype EventNewDevice struct {\n\tDevice Device\n}\n```\n\nEventNewDevice is emitted by a Client or Group when it discovers a new Device\n\n#### type EventNewGroup\n\n```go\ntype EventNewGroup struct {\n\tGroup Group\n}\n```\n\nEventNewGroup is emitted by a Client when it discovers a new Group\n\n#### type EventNewLocation\n\n```go\ntype EventNewLocation struct {\n\tLocation Location\n}\n```\n\nEventNewLocation is emitted by a Client when it discovers a new Location\n\n#### type EventUpdateColor\n\n```go\ntype EventUpdateColor struct {\n\tColor Color\n}\n```\n\nEventUpdateColor is emitted by a Light or Group when its Color is updated\n\n#### type EventUpdateLabel\n\n```go\ntype EventUpdateLabel struct {\n\tLabel string\n}\n```\n\nEventUpdateLabel is emitted by a Device or Group when its label is updated\n\n#### type EventUpdatePower\n\n```go\ntype EventUpdatePower struct {\n\tPower bool\n}\n```\n\nEventUpdatePower is emitted by a Device or Group when its power state is updated\n\n#### type Group\n\n```go\ntype Group interface {\n\t// ID returns a base64 encoding of the device ID\n\tID() string\n\n\t// Label returns the label for the group\n\tGetLabel() string\n\n\t// Devices returns the devices in the group\n\tDevices() []Device\n\n\t// Lights returns the lights in the group\n\tLights() []Light\n\n\t// Returns the power state of the group, true if any members are on, false\n\t// if all members off. Returns error on communication errors.\n\tGetPower() (bool, error)\n\n\t// Returns the average color of lights in the group. Returns error on\n\t// communication error.\n\tGetColor() (Color, error)\n\n\t// SetColor requests a change of color for all devices in the group that\n\t// support color changes, transitioning over the specified duration\n\tSetColor(color Color, duration time.Duration) error\n\t// SetPower sets the power of devices in the group that support power\n\t// changes, state is true for on, false for off.\n\tSetPower(state bool) error\n\t// SetPowerDuration sets the power of devices in the group that support\n\t// power changes, transitioning over the speficied duration, state is true\n\t// for on, false for off.\n\tSetPowerDuration(state bool, duration time.Duration) error\n\n\t// Device is a SubscriptionTarget\n\tSubscriptionTarget\n}\n```\n\nGroup represents a group of LIFX devices\n\n#### type Light\n\n```go\ntype Light interface {\n\t// SetColor changes the color of the light, transitioning over the specified\n\t// duration\n\tSetColor(color Color, duration time.Duration) error\n\t// GetColor requests the current color of the light\n\tGetColor() (Color, error)\n\t// CachedColor returns the last known color of the light\n\tCachedColor() Color\n\t// SetPowerDuration sets the power of the light, transitioning over the\n\t// speficied duration, state is true for on, false for off.\n\tSetPowerDuration(state bool, duration time.Duration) error\n\n\t// Light is a superset of the Device interface\n\tDevice\n}\n```\n\nLight represents a LIFX light device\n\n#### type Location\n\n```go\ntype Location interface {\n\t// Location is a group\n\tGroup\n}\n```\n\nLocation represents a locality-based group of LIFX devices\n\n#### type Logger\n\n```go\ntype Logger interface {\n\t// Debugf handles debug level messages\n\tDebugf(format string, args ...interface{})\n\t// Infof handles info level messages\n\tInfof(format string, args ...interface{})\n\t// Warnf handles warn level messages\n\tWarnf(format string, args ...interface{})\n\t// Errorf handles error level messages\n\tErrorf(format string, args ...interface{})\n\t// Fatalf handles fatal level messages, and must exit the application\n\tFatalf(format string, args ...interface{})\n\t// Panicf handles debug level messages, and must panic the application\n\tPanicf(format string, args ...interface{})\n}\n```\n\nLogger represents a minimal levelled logger\n\n```go\nvar (\n\t// Log holds the global logger used by golifx, can be set via SetLogger() in\n\t// the golifx package\n\tLog Logger\n)\n```\n\n#### type Protocol\n\n```go\ntype Protocol interface {\n\tSubscriptionTarget\n\n\t// GetLocations returns a slice of all locations known to the protocol, or\n\t// ErrNotFound if no locations are currently known.\n\tGetLocations() (locations []Location, err error)\n\t// GetLocation looks up a location by its `id`\n\tGetLocation(id string) (Location, error)\n\t// GetGroups returns a slice of all groups known to the protocol, or\n\t// ErrNotFound if no locations are currently known.\n\tGetGroups() (locations []Group, err error)\n\t// GetGroup looks up a group by its `id`\n\tGetGroup(id string) (Group, error)\n\t// GetDevices returns a slice of all devices known to the protocol, or\n\t// ErrNotFound if no devices are currently known.\n\tGetDevices() (devices []Device, err error)\n\t// GetDevice looks up a device by its `id`\n\tGetDevice(id uint64) (Device, error)\n\t// Discover initiates device discovery, this may be a noop in some future\n\t// protocol versions.  This is called immediately when the client connects\n\t// to the protocol\n\tDiscover() error\n\t// SetTimeout attaches the client timeout to the protocol\n\tSetTimeout(timeout *time.Duration)\n\t// SetRetryInterval attaches the client retry interval to the protocol\n\tSetRetryInterval(retryInterval *time.Duration)\n\t// Close closes the protocol driver, no further communication with the\n\t// protocol is possible\n\tClose() error\n\n\t// SetPower sets the power state globally, on all devices\n\tSetPower(state bool) error\n\t// SetPowerDuration sets the power state globally, on all lights, over the\n\t// specified duration\n\tSetPowerDuration(state bool, duration time.Duration) error\n\t// SetColor changes the color globally, on all lights, over the specified\n\t// duration\n\tSetColor(color Color, duration time.Duration) error\n}\n```\n\nProtocol defines the interface between the Client and a protocol implementation\n\n#### type StubLogger\n\n```go\ntype StubLogger struct{}\n```\n\nStubLogger satisfies the Logger interface, and simply does nothing with received\nmessages\n\n#### func (*StubLogger) Debugf\n\n```go\nfunc (l *StubLogger) Debugf(format string, args ...interface{})\n```\nDebugf handles debug level messages\n\n#### func (*StubLogger) Errorf\n\n```go\nfunc (l *StubLogger) Errorf(format string, args ...interface{})\n```\nErrorf handles error level messages\n\n#### func (*StubLogger) Fatalf\n\n```go\nfunc (l *StubLogger) Fatalf(format string, args ...interface{})\n```\nFatalf handles fatal level messages, exits the application\n\n#### func (*StubLogger) Infof\n\n```go\nfunc (l *StubLogger) Infof(format string, args ...interface{})\n```\nInfof handles info level messages\n\n#### func (*StubLogger) Panicf\n\n```go\nfunc (l *StubLogger) Panicf(format string, args ...interface{})\n```\nPanicf handles debug level messages, and panics the application\n\n#### func (*StubLogger) Warnf\n\n```go\nfunc (l *StubLogger) Warnf(format string, args ...interface{})\n```\nWarnf handles warn level messages\n\n#### type Subscription\n\n```go\ntype Subscription struct {\n\tsync.Mutex\n}\n```\n\nSubscription exposes an event channel for consumers, and attaches to a\nSubscriptionTarget, that will feed it with events\n\n#### func (*Subscription) Close\n\n```go\nfunc (s *Subscription) Close() error\n```\nClose cleans up resources and notifies the provider that the subscription should\nno longer be used. It is important to close subscriptions when you are done with\nthem to avoid blocking operations.\n\n#### func (*Subscription) Events\n\n```go\nfunc (s *Subscription) Events() \u003c-chan interface{}\n```\nEvents returns a chan reader for reading events published to this subscription\n\n#### type SubscriptionProvider\n\n```go\ntype SubscriptionProvider struct {\n\tsync.RWMutex\n}\n```\n\nSubscriptionProvider provides an embedable subscription factory\n\n#### func (*SubscriptionProvider) Close\n\n```go\nfunc (s *SubscriptionProvider) Close() (err error)\n```\nClose all subscriptions\n\n#### func (*SubscriptionProvider) Notify\n\n```go\nfunc (s *SubscriptionProvider) Notify(event interface{})\n```\nNotify sends the provided event to all subscribers\n\n#### func (*SubscriptionProvider) Subscribe\n\n```go\nfunc (s *SubscriptionProvider) Subscribe() *Subscription\n```\nSubscribe returns a new Subscription for this provider\n\n#### type SubscriptionTarget\n\n```go\ntype SubscriptionTarget interface {\n\tSubscribe() *Subscription\n\tNotify(event interface{})\n}\n```\n\nSubscriptionTarget generally embeds a SubscriptionProvider\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdf%2Fgolifx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpdf%2Fgolifx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdf%2Fgolifx/lists"}