SDF Chatter

4,739 readers
174 users here now
founded 2 years ago
ADMINS
SDF

Support for this instance is greatly appreciated at https://sdf.org/support

1
 
 

In my effort to move away from streaming services, I'm purchasing songs from my favourite artists (mostly through Bandcamp, which Muse unfortunately doesn't have)

On Muse's website, it states that the only two options to download their music is through Google Play and Amazon Music. However, I don't see the dolby atmos version of the albums on Google Play and Amazon Music doesn't work, I tried installing but I just get an error message.

Does someone know how I could download these albums?

2
3
2
submitted 32 minutes ago* (last edited 32 minutes ago) by dominik@chrastecky.dev to c/programming@chrastecky.dev
 
 

I've created Matrix bots before, and sending simple unencrypted messages is so easy it doesn't even require a library. Typically, you'd get your room ID, username, and password, then perform two HTTP requests: one for login, and one for sending the message.

But recently, I wanted to do things the proper way. We're migrating from Slack to Matrix for a project I'm working on with some friends, and we've decided that all rooms, including our server notifications channel, should be encrypted. This meant I had to find a suitable library with end-to-end encryption support in a language I'm comfortable with. Eventually, I settled on mautrix-go.

Setting Up Your Matrix Bot

We'll create a straightforward proof-of-concept bot that logs in, sends a single message, and exits. Later, we'll enhance it by adding encryption support.

Installation

First, install the mautrix-go library:

go get maunium.net/go/mautrix

Defining Constants

We'll use some constants for simplicity in this example. Remember: never store sensitive credentials like this in production code.

const homeserver = "https://matrix.exapmle.com/" // replace with your server
const username = "test_bot"
const password = "super-secret-cool-password"
const roomID = "!okfsAqlvVqyZZRgPWy:example.com"

const userId = ""
const accessToken = ""
const deviceId = ""

Initially, the user ID, access token, and device ID are empty because the bot needs to log in and retrieve these values. Usually, you'd store them securely in a database or similar storage.

Initializing the Client

Now, let's create the Matrix client:

func main() {
	client, err := mautrix.NewClient(homeserver, userId, accessToken)
	if err != nil {
		panic(err)
	}
}

Logging In

If your credentials aren't set, log in to obtain them:

    if deviceId == "" || userId == "" || accessToken == "" {
		resp, err := client.Login(context.Background(), &mautrix.ReqLogin{
			Type: mautrix.AuthTypePassword,
			Identifier: mautrix.UserIdentifier{
				User: username,
				Type: mautrix.IdentifierTypeUser,
			},
			Password:         password,
			StoreCredentials: true,
		})
		if err != nil {
			panic(err)
		}

		log.Println(resp.DeviceID)
		log.Println(resp.AccessToken)
		log.Println(resp.UserID)

		return
	}

The printed values will look something like this:

2025/04/19 15:57:50 AQWFKLSBNJ
2025/04/19 15:57:50 syt_dgVzdF7ibFQ_GurkyhAWzEpTGgSBemjL_2JdxlO
2025/04/19 15:57:50 @test_bot:example.com

Copy these values back into your constants.

Sending an Unencrypted Message

Now we can send a basic message:

	client.DeviceID = deviceId
	content := event.MessageEventContent{
		MsgType: event.MsgText,
		Body:    "Hello world from Go!",
	}

	_, err = client.SendMessageEvent(context.Background(), roomID, event.EventMessage, content)
	if err != nil {
		panic(err)
	}

At this stage, your message will arrive in the Matrix room—but it's not encrypted yet:

 Screenshot of a Matrix room showing a message from “Test bot” saying “Hello world from Go!”, marked as not encrypted.

Here's the full code so far:

import (
	"context"
	"log"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"
)

const homeserver = "https://matrix.exapmle.com/" // replace with your server
const username = "test_bot"
const password = "super-secret-cool-password"
const roomID = "!okfsAqlvVqyZZRgPWy:example.com"

const userId = "@test_bot:example.com"
const accessToken = "syt_dgVzdF7ibFQ_GurkyhAWzEpTGgSBemjL_2JdxlO"
const deviceId = "AQWFKLSBNJ"

func main() {
	client, err := mautrix.NewClient(homeserver, userId, accessToken)
	if err != nil {
		panic(err)
	}

	if deviceId == "" || userId == "" || accessToken == "" {
		resp, err := client.Login(context.Background(), &mautrix.ReqLogin{
			Type: mautrix.AuthTypePassword,
			Identifier: mautrix.UserIdentifier{
				User: username,
				Type: mautrix.IdentifierTypeUser,
			},
			Password:         password,
			StoreCredentials: true,
		})
		if err != nil {
			panic(err)
		}

		log.Println(resp.DeviceID)
		log.Println(resp.AccessToken)
		log.Println(resp.UserID)

		return
	}

	client.DeviceID = deviceId
	content := event.MessageEventContent{
		MsgType: event.MsgText,
		Body:    "Hello world from Go!",
	}

	_, err = client.SendMessageEvent(context.Background(), roomID, event.EventMessage, content)
	if err != nil {
		panic(err)
	}
}

Sending Encrypted Messages

Encrypting messages involves syncing with the server and setting up cryptography, but don't worry—it's still quite straightforward. Let's see how easily this can be done using mautrix-go.

Create a Cryptography Helper

We'll first create a secure key ("pickle key") and helper function. Make sure to keep this key completely secret and never share it publicly:

// note that the key doesn't have to be a string, you can directly generate random bytes and store them somewhere in a binary form
const pickleKeyString = "NnSHJguDSW7vtSshQJh2Yny4zQHc6Wyf"

func setupCryptoHelper(cli *mautrix.Client) (*cryptohelper.CryptoHelper, error) {
	// remember to use a secure key for the pickle key in production
	pickleKey := []byte(pickleKeyString)

	// this is a path to the SQLite database you will use to store various data about your bot
	dbPath := "crypto.db"

	helper, err := cryptohelper.NewCryptoHelper(cli, pickleKey, dbPath)
	if err != nil {
		return nil, err
	}

	// initialize the database and other stuff
	err = helper.Init(context.Background())
	if err != nil {
		return nil, err
	}

	return helper, nil
}

Syncing the Client

First, we create the syncer and assign it to the client:

	syncer := mautrix.NewDefaultSyncer()
	client.Syncer = syncer

Then we create and assign the crypto helper:

	cryptoHelper, err := setupCryptoHelper(client)
	if err != nil {
		panic(err)
	}
	client.Crypto = cryptoHelper

The syncer is needed to listen to events from synchronization, which is what we'll implement next:

	go func() {
		if err := client.Sync(); err != nil {
			panic(err)
		}
	}()

The Sync() method is a blocking call and runs until an error occurs, so we run it in a goroutine. Now we'll use a channel to wait for the first event from the syncer to make sure everything's initialized:

    readyChan := make(chan bool)
	var once sync.Once
	syncer.OnSync(func(ctx context.Context, resp *mautrix.RespSync, since string) bool {
		once.Do(func() {
			close(readyChan)
		})

		return true
	})

The sync.Once ensures the channel gets closed only once, even if multiple sync events come in in different threads. Finally, we wait for the first sync:

	log.Println("Waiting for sync to receive first event from the encrypted room...")
	<-readyChan
	log.Println("Sync received")

Now your client is ready to send encrypted messages! The full section we just created looks like this:

    readyChan := make(chan bool)
	var once sync.Once
	syncer.OnSync(func(ctx context.Context, resp *mautrix.RespSync, since string) bool {
		once.Do(func() {
			close(readyChan)
		})

		return true
	})

	go func() {
		if err := client.Sync(); err != nil {
			panic(err)
		}
	}()

	log.Println("Waiting for sync to receive first event from the encrypted room...")
	<-readyChan
	log.Println("Sync received")

And just to confirm everything worked, here's what the message looks like in the Matrix room:

 Screenshot of a Matrix room showing an encrypted message with a warning that the sending device hasn’t been verified.

As you can see, the message was encrypted successfully, but the session still isn't verified yet—hence the warning. We'll fix that next.

Here's the full source code so far:

import (
	"context"
	"log"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/crypto/cryptohelper"
	"maunium.net/go/mautrix/event"
	"sync"
)

const homeserver = "https://matrix.exapmle.com/" // replace with your server
const username = "test_bot"
const password = "super-secret-cool-password"
const roomID = "!okfsAqlvVqyZZRgPWy:example.com"

const userId = "@test_bot:example.com"
const accessToken = "syt_dgVzdF7ibFQ_GurkyhAWzEpTGgSBemjL_2JdxlO"
const deviceId = "AQWFKLSBNJ"
const pickleKeyString = "NnSHJguDSW7vtSshQJh2Yny4zQHc6Wyf"

func setupCryptoHelper(cli *mautrix.Client) (*cryptohelper.CryptoHelper, error) {
	// remember to use a secure key for the pickle key in production
	pickleKey := []byte(pickleKeyString)

	// this is a path to the SQLite database you will use to store various data about your bot
	dbPath := "crypto.db"

	helper, err := cryptohelper.NewCryptoHelper(cli, pickleKey, dbPath)
	if err != nil {
		return nil, err
	}

	// initialize the database and other stuff
	err = helper.Init(context.Background())
	if err != nil {
		return nil, err
	}

	return helper, nil
}

func main() {
	client, err := mautrix.NewClient(homeserver, userId, accessToken)
	if err != nil {
		panic(err)
	}

	if deviceId == "" || userId == "" || accessToken == "" {
		resp, err := client.Login(context.Background(), &mautrix.ReqLogin{
			Type: mautrix.AuthTypePassword,
			Identifier: mautrix.UserIdentifier{
				User: username,
				Type: mautrix.IdentifierTypeUser,
			},
			Password:         password,
			StoreCredentials: true,
		})
		if err != nil {
			panic(err)
		}

		log.Println(resp.DeviceID)
		log.Println(resp.AccessToken)
		log.Println(resp.UserID)

		return
	}
	client.DeviceID = deviceId

	syncer := mautrix.NewDefaultSyncer()
	client.Syncer = syncer

	cryptoHelper, err := setupCryptoHelper(client)
	if err != nil {
		panic(err)
	}
	client.Crypto = cryptoHelper

	readyChan := make(chan bool)
	var once sync.Once
	syncer.OnSync(func(ctx context.Context, resp *mautrix.RespSync, since string) bool {
		once.Do(func() {
			close(readyChan)
		})

		return true
	})

	go func() {
		if err := client.Sync(); err != nil {
			panic(err)
		}
	}()

	log.Println("Waiting for sync to receive first event from the encrypted room...")
	<-readyChan
	log.Println("Sync received")

	content := event.MessageEventContent{
		MsgType: event.MsgText,
		Body:    "Hello world from Go!",
	}

	_, err = client.SendMessageEvent(context.Background(), roomID, event.EventMessage, content)
	if err != nil {
		panic(err)
	}
}

Verifying the Session

For verified encryption, you'll need a recovery key (obtainable via Element). Store it securely. I have to admit, this part wasn't as intuitive for me—I had to look at some existing projects because it dives a bit deeper into Matrix internals than I usually go. Still, the method names are quite descriptive, so even without deep knowledge, it's not too hard to follow:

const recoveryKey = "EsUF NQce e4BW teUM Kf7W iZqD Nj3f 56qj GuN5 s3aw aut7 div2"

Just like the pickle key, the recovery key should be treated as highly sensitive—do not share or hardcode it in production environments.

Then, create this helper function:

func verifyWithRecoveryKey(machine *crypto.OlmMachine) (err error) {
	ctx := context.Background()

	keyId, keyData, err := machine.SSSS.GetDefaultKeyData(ctx)
	if err != nil {
		return
	}
	key, err := keyData.VerifyRecoveryKey(keyId, recoveryKey)
	if err != nil {
		return
	}
	err = machine.FetchCrossSigningKeysFromSSSS(ctx, key)
	if err != nil {
		return
	}
	err = machine.SignOwnDevice(ctx, machine.OwnIdentity())
	if err != nil {
		return
	}
	err = machine.SignOwnMasterKey(ctx)

	return
}

Call this function after synchronization—back in the main() function:

	err = verifyWithRecoveryKey(cryptoHelper.Machine())
	if err != nil {
		panic(err)
	}

Now, your messages will be encrypted, verified, and free of security warnings.

And just to confirm, here's what that looks like in the Matrix room—notice that the warning icon is gone:

 Screenshot of the same message in a Matrix room, now encrypted and verified with no warning displayed.

Here's the full source code:

import (
	"context"
	"log"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/crypto"
	"maunium.net/go/mautrix/crypto/cryptohelper"
	"maunium.net/go/mautrix/event"
	"sync"
)

const homeserver = "https://matrix.exapmle.com/" // replace with your server
const username = "test_bot"
const password = "super-secret-cool-password"
const roomID = "!okfsAqlvVqyZZRgPWy:example.com"

const userId = "@test_bot:example.com"
const accessToken = "syt_dgVzdF7ibFQ_GurkyhAWzEpTGgSBemjL_2JdxlO"
const deviceId = "AQWFKLSBNJ"
const pickleKeyString = "NnSHJguDSW7vtSshQJh2Yny4zQHc6Wyf"

func setupCryptoHelper(cli *mautrix.Client) (*cryptohelper.CryptoHelper, error) {
	// remember to use a secure key for the pickle key in production
	pickleKey := []byte(pickleKeyString)

	// this is a path to the SQLite database you will use to store various data about your bot
	dbPath := "crypto.db"

	helper, err := cryptohelper.NewCryptoHelper(cli, pickleKey, dbPath)
	if err != nil {
		return nil, err
	}

	// initialize the database and other stuff
	err = helper.Init(context.Background())
	if err != nil {
		return nil, err
	}

	return helper, nil
}

func verifyWithRecoveryKey(machine *crypto.OlmMachine) (err error) {
	ctx := context.Background()

	keyId, keyData, err := machine.SSSS.GetDefaultKeyData(ctx)
	if err != nil {
		return
	}
	key, err := keyData.VerifyRecoveryKey(keyId, recoveryKey)
	if err != nil {
		return
	}
	err = machine.FetchCrossSigningKeysFromSSSS(ctx, key)
	if err != nil {
		return
	}
	err = machine.SignOwnDevice(ctx, machine.OwnIdentity())
	if err != nil {
		return
	}
	err = machine.SignOwnMasterKey(ctx)

	return
}

func main() {
	client, err := mautrix.NewClient(homeserver, userId, accessToken)
	if err != nil {
		panic(err)
	}

	if deviceId == "" || userId == "" || accessToken == "" {
		resp, err := client.Login(context.Background(), &mautrix.ReqLogin{
			Type: mautrix.AuthTypePassword,
			Identifier: mautrix.UserIdentifier{
				User: username,
				Type: mautrix.IdentifierTypeUser,
			},
			Password:         password,
			StoreCredentials: true,
		})
		if err != nil {
			panic(err)
		}

		log.Println(resp.DeviceID)
		log.Println(resp.AccessToken)
		log.Println(resp.UserID)

		return
	}
	client.DeviceID = deviceId

	syncer := mautrix.NewDefaultSyncer()
	client.Syncer = syncer

	cryptoHelper, err := setupCryptoHelper(client)
	if err != nil {
		panic(err)
	}
	client.Crypto = cryptoHelper

	readyChan := make(chan bool)
	var once sync.Once
	syncer.OnSync(func(ctx context.Context, resp *mautrix.RespSync, since string) bool {
		once.Do(func() {
			close(readyChan)
		})

		return true
	})

	go func() {
		if err := client.Sync(); err != nil {
			panic(err)
		}
	}()

	log.Println("Waiting for sync to receive first event from the encrypted room...")
	<-readyChan
	log.Println("Sync received")

	err = verifyWithRecoveryKey(cryptoHelper.Machine())
	if err != nil {
		panic(err)
	}

	content := event.MessageEventContent{
		MsgType: event.MsgText,
		Body:    "Hello world from Go!",
	}

	_, err = client.SendMessageEvent(context.Background(), roomID, event.EventMessage, content)
	if err != nil {
		panic(err)
	}
}

Conclusion

With this approach, your Matrix bot securely communicates within encrypted rooms. Remember to securely store credentials, use secure keys, and manage device verification properly in production. Happy coding!

4
 
 

The Quebec Court of Appeal is ordering the airline to pay passengers more than $10 million in damages after they were charged higher amounts than the ticket price advertised.


From this RSS feed

5
 
 

Vancouver Fire and Rescue Services was called to the Hastings Sunrise neighbourhood around 4 a.m. after reports of a structure fire on East Hastings near Garden Drive.


From this RSS feed

6
 
 

Officials said they estimate the fire might not be completely extinguished until April 26, based on the fire's current containment level.


From this RSS feed

7
8
 
 

A magnitude 6.2 earthquake shook Istanbul and other areas, prompting widespread panic and dozens of injuries with officials saying many tried to jump from buildings.


From this RSS feed

9
 
 

cross-posted from: https://fedia.io/m/Bside/t/2091934

Urban Jungle on Steam

Use luscious plants to turn your ordinary home into a green haven in Urban Jungle. Find the best place for each plant in your small apartment and enjoy gardening without worries. And don't forget to pet your cat!

Homepage

10
 
 

From Decaturish:

AVONDALE ESTATES, Ga. — A long-standing Easter tradition rolled on in Avondale Estates on April 20 as an eclectic array of vintage vehicles cruised down neighborhood streets during the annual Antique Car Parade. Want Decaturish delivered to your inbox every day? Sign up for our free newsletter by clicking here. Support Decaturish by becoming a […]

11
 
 

Two accusers who testified in Harvey Weinstein's initial trial are expected to take the stand again. He also faces an additional allegation made by an unnamed woman.


From this RSS feed

12
13
 
 

The program creates opportunities for people accused of crimes and victims of crime to work together to come to resolutions, permitting suspects to avoid criminal records.


From this RSS feed

14
 
 

Winnipeg police say a 15-year-old boy is in custody after an incident at CF Polo Park mall Tuesday afternoon involving a firearm.


From this RSS feed

15
 
 

A new survey from TD Bank found Gen Z are the most likely Canadians to invest their tax returns once received, but also may be unclear on how some savings accounts work.


From this RSS feed

16
 
 

Someone must've had a few visits from the tooth fairy!


From this RSS feed

17
 
 

cross-posted from: https://lemmy.world/post/28615387

Hello fellow ents. I live in Holland, and still need to learn the local language. I've got no friends that can help with this venture, I'm all alone over here.

That said, I found a source of BioBizz Light Mix and bought a 50L bag of it for 1 plant. This will be an outdoor grow, and my first. Bought a 6.5gal/ 25L breathable fabric pot from RQS. Sunniest and longest period that hits my garden is West-facing. Strain is Diesel Autoflower.

I'm in Drenthe, The Netherlands, and should be inundated with how-to-grow-cannabis resources, but I'm at a loss. RQS (Royal Queen Seeds) has been a great resource, and I found Mr. Canuck on YouTube.

My seedling just cracked in the water today. So far, my research tells me Autoflowers don't like being transplanted, they take like half or an 1/8th of nutrients than photoperiodic traditional plants, so directly into soil she'll go.

My question is... nutes. Nutrients. First 2 weeks are just water, but this BioBizz Light Mix (https://www.biobizz.com/producto/lightmix/) is meant to just act as a well draining substrate, lacking nutrients. So I need help here.

NPK 3-1-2 or 4-4-4 or whatever, it's most likely gotta come out of a bottle. I would like to stick with organic over synthetic. Gaia Green is not found near me. Get me going in the vegetative state, I'll keep my ear to the ground come flowering state.

Halp ^_^ Thanks in advance. -LOLseas

18
 
 

From Decaturish:

TUCKER, Ga. — ARTucker hosted its annual free art festival at the Church Street Greenspace in Tucker on April 19. The festival featured artists’ booths, interactive chalk walls, live performances, food trucks and more. Founded in May 2015 by Patricia “Patty” Kurtz Young, the nonprofit organization showcases local artists and promotes the arts community in […]

19
 
 

Anthony Stolarz has taken on plenty of supporting roles.


From this RSS feed

20
 
 
21
22
 
 

archived (Wayback Machine)

23
 
 

Thousands gathered in Rome on Wednesday to pay their respects to Pope Francis, who will lie in state for three days at St. Peter's Basilica ahead of his funeral.


From this RSS feed

24
 
 

archived (Wayback Machine)

25
view more: next ›