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
 
 
2
 
 
3
 
 
4
5
 
 

Restrictions on movement and total siege making aid operations almost impossible

6
 
 

For his Abu Dhabi-set science fiction graphic novel Solarblader, Emirati writer Mo Abedin focuses his speculative fiction on renewable energy, inspired by the initiatives happening in the current-day UAE. Dubbing the new subgenre "solarpunk", due to the reliance on solar energy in his version of the future, he hopes to inspire a new trend.

Sandstorm debuted the first volume of Abedin’s graphic novel titled Solarblade at this year's Middle East Film and Comic Con, which ends today. Set in an alternate-universe Abu Dhabi in 2525, it imagines the UAE capital fully reliant on solar energy, bolstered by alien technology that helps harness the full power of the sun.

7
8
1
Urban Jungle on Steam (store.steampowered.com)
submitted 2 minutes ago by falseprophet@fedia.io to c/Bside@fedia.io
 
 

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

9
 
 

I present to you all !absoluteunit@sh.itjust.works, a community themed around very big things that are bigger than one might expect.

Basically, an absolute unit is either an animal, a person, an object or anything else that is bigger than it's usually expected, like a very big vegetable, a very big dog, a very big building and so on, basically anything that can be very big. Here is a more detailed explanation.

So yeah, if you ever encountered something bigger than normal you can post it there!

Have fun! Hope you like it!

10
 
 

By Stuart A. Thompson April 23, 2025

"The New York Times found three users on X who feuded with Mr. Musk in December only to see their reach on the social platform practically vanish overnight. The accounts are the starkest signs yet that Mr. Musk or others at the company have the power to punish critics and that they may be willing to use it, startling free speech advocates who hoped that the billionaire would be their champion."

https://archive.ph/Wm2Cc

11
12
 
 

“Please come back, my son, I will share everything I have with you.”

13
14
15
 
 

transcription: what if we escaped the psyche ward together...

16
17
18
 
 

Several countries in the region have already issued drought warnings, with upcoming harvests and access to water at risk for populations already facing longer, hotter, and more frequent heatwaves.

It urged countries that rely on the 12 major river basins in the region to develop "improved water management, stronger drought preparedness, better early warning systems, and greater regional cooperation."

"Carbon emissions have already locked in an irreversible course of recurrent snow anomalies in the HKH (Hindu Kush-Himalayas)," Gyamtsho said.

19
 
 
20
21
 
 

Console commands allow you to play Oblivion Remastered pretty much however you want.

You can fly around the skies, max out all of your skills and attributes, get the best and strongest items, or become a literal god. Most everything is possible through the use of console commands, as long as you know what they are — and how to use them.

Below, we’ll show you how to use console commands Oblivion Remastered, before getting into various lists of all console commands in Oblivion Remastered, which was compiled thanks to help from the Unofficial Elder Scrolls pages and The Elder Scrolls Wiki.

How to use console commands in Oblivion Remastered

Prior to inputting console commands in Oblivion Remastered, there are a few things you should know:

Console commands are only available on Windows PC.Using any console commands will disable achievements across all of your save files.

There are a ways to re-enable achievements, such as reverting to a save before you used any console commands, uninstalling and reinstalling the game, or, probably the best method, installing a mod to re-enable achievements.

A menu shows how to input console commands in Oblivion Remastered

To use console commands in Oblivion Remastered, press the “~” button on your keyboard to open the console command panel, and type or paste commands into the text bar. If the tilde button does not open the console command panel, you may need to set your keyboard language to “English/US Keyboard.”

As you’re inputting commands, keep the following in mind:

For some of the commands, you’ll need to manually input a parameter such as an item ID or location ID, which we explain more for each respective command.Some codes will be listed with brackets, but the brackets should not be included when typing in the command. For example, setlevel [#] should be written as setlevel # without any brackets.Commands are not case sensitive.

Oblivion Remastered common console commands list

Below, we’ve listed out some of the most commonly used Oblivion Remastered console commands for your convenience.

CommandDescriptionhelpShow all console commands.tgmToggles God mode.ObvGodModeToggles God mode and maxes out all of your skills and attributes, unlocks all spells, provides one million gold, and more.advlevelLevel up once.player.advskill [Skill] [#]Adds # levels to a skill.player.setav [Attribute] [#]Sets an attribute to a specific # value.player.additem [ItemID] [#]Adds # items to the player's inventory.coc [LocationID]Teleports the player to a location.killKills a selected target.unlockUnlocks the selected target.ShowRaceMenuAllows you to change your appearance, but this will reset all of your skills to their default values.ShowClassMenuAllows you to change your class, but this will reset all of your skills to their default values.ShowBirthsignMenuAllows you to change your birthsign.ShowSpellMakingAllows you to create spells.PSBLearns all magic spells.tmm 1Reveals all map markers.tfowRemoves the fog of war from the map.movetoqtTeleports the player to the active quest objective.player.payfinethiefPays off the player's bounty without removing any stolen items.FlyGives you the ability to fly.

Oblivion Remastered toggle commands list

Toggle commands in Oblivion Remastered activate or deactivate various aspects of the game.

CommandDescriptionshowsubtitleToggles subtitles.taiToggles AI. If this command is used on a target, the target's AI will be toggled. If this command is not used on a target, AI will toggled for all characters.tcaiToggles combat AI. If this command is used on a target, the target's combat AI will be toggled. If this command is not used on a target, combat AI will be toggled for all characters.tclToggles collision.tdetectToggle AI detection.tdtToggle debug text.tfowToggle fog of war.tfhToggle full help.tlbToggle Lite Brite.tllToggle land level of detail.tlvToggle leaves.tmToggles menus.tmm [#]Toggles map markers with 0 being "show no markers" and 1 being "show all markers."tsToggles the sky.ttToggles trees.twfToggle wireframe mode.twsToggle water rendering.

Oblivion Remastered targeted commands list

Targeted commands in Oblivion Remastered apply an affect to a specific target. Bear in mind that while we’ve tested most of these, we have not been able to test all of them yet, as some are contingent on parts of the game we haven’t unlocked yet. We’ll update this guide when all targeted console commands have been tested.

CommandDescriptionactivateActivates a target.additem [ItemID] [#]Adds # items to the player's inventory.disableDisables a target.enableEnables a target.removeitem [ItemID] [#]Removes # items from the player's inventory.equipitem [ItemID] [#]Have an NPC equip an item or prevent an NPC from equipping an item by setting the # value to 0 or 1 respectively.removeallitemsRemove all items from inventory.addspell [SpellID]Adds a spell to your magic tab.removespell [SpellID]Removes a spell from your magic tab.dispel [SpellID]Dispels a magic effect from a target.dispelallspellsDispels all magic effects from a target.createfullactorcopyClones a target.deletefullactorcopyDeletes a clone of the target.DuplicateAllItems [ContainerID]Duplicates all items from a target container to a referenced container.getav [Attribute]Gets value of attribute.setav [Attribute] [#]Sets an attribute to a value.setscale [#]Change target height.kill [target]Kills target.lock [#]Locks a target. The value will change how difficult a target is to open with 1 being the easiest and 99 being the hardest. If the value is set to 100, the container will need a key to open it.unlockUnlocks a target.moddisposition [ActorID] [#]Modifies a targets disposition level.moveto [LocationID]Teleports the target to a destination.payfinePays off bounties and confiscated any stolen items.payfinethiefPays off bounties without confiscating any stolen items.placeatme [target]Spawns a copy of a targeted item by the player.resurrectResurrects the target.SetActorFullName [name]Changes the name of the target.getBarterGoldGet the target's shop gold.setBarterGold [#]Set the target's shop gold to the # value.getCrimeGoldShows the target's current bounty.setCrimeGold [#]Sets the target's bounty to the # value.setessential [ID] [#]Sets the target's essential flag with 1 being essential and 0 being non-essential. If a target is essential, they will be knocked down rather than killed.setlevel [#]Sets the target's level to the # value.SetOpenState [#]Sets the lock/unlocked state of a door with 1 being unlocked and 0 being closed.SetOwnership [Owner|Faction]Changes ownership of an item to a character or faction. If no parameter is set, the player will become the owner.StopCombatStops the target from engaging in combat.StartCombat [target]Forces a target to engage in combat.StartConversation [target] [topic]Starts a conversation with an NPC.

Oblivion Remastered other commands list

This list covers console commands that don’t neatly fit into the other categories above.

CommandDescriptionadvlevelLevels up the player once.advskill [Skill] [#]Levels up a skill # number of times.completequestCompletes current active quest.caqsCompletes all quests in the game.coc [LocationID]Teleports the player to a location.coe [X], [Y]Teleports the player to a location using two coordinates.enableplayercontrolsAllows the player to move during cinematics.essentialdeathreloadForces you to reload after displaying a message.fov [#]Changes the field of view to # value.fw [WeatherID]Changes the weather using the weather ID.getstage [QuestID]Shows the current stage of the specified quest.setstage [QuestID] [Stage]Sets the stage of a quest to the specified stage. Use command SQT to see a quest ID and command getstage to see the quest stage.killallKills all creatures in a large radius.modpca [Attribute] [#]Adds # to an attribute.modpcs [Attribute] [#]Adds # to a skill.movetoqtTeleports the player to the current quest objective.prid [ID]Select a target using their refID value.psbAdds all spells to the magic tab.qqqExits the game.save [name]Saves the game.saveiniSaves ini settings.set [globalvar] to [var]Sets global variable to # value.setdebugtextSelects what command "tdt" displays.setpcfame [#]Sets the players fame to #.setpcinfamy [#]Sets the players infamy to #.setquestobject [ID] [#]Changes the quest item status to either on (1) or off (0).sexchangeChanges the gender of the player or target character.settimescaleto [#]Changes the speed at which time progresses in-game.show [var]Shows the value of global variable.showbirthsignmenuAllows you to change your birthsign.showclassmenuAllows you to change your class, but this will reset all of your skills to their default values.showenchantmentOpens the enchanting window.showracemenuAllows you to change your race, but this will reset all of your skills to their default values.showspellmakingAllows you to create spells.sqShow all quests in the game.sqtShows the current quest.sw [WeatherID]Sets the weather to a specific weather ID.swdpDisplays a list of all characters detecting the player.getgs [Game Settting]Shows the value of a game setting.setgs [Game Setting] [#]Sets the value of a game setting to #.


From Polygon via this RSS feed

22
1
submitted 5 minutes ago* (last edited 5 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!

23
24
 
 

Arts Atlanta:

Each week, ArtsATL curates a selection of the most exciting arts and culture events happening in Atlanta this weekend, highlighting nine must-see experiences.  :: Thursday Deep Fried Folk: A Celebration of Southern Folk Art Deep Fried Folk brings together some of the South’s most celebrated self-taught artists, including works by Howard Finster, R.A. Miller, Purvis Young, Mose...

25
 
 

"If you can't respect the basic fundamental underlying principles with which we order society — which is 'Do not steal' — then what are you left with?" asks investigative journalist Carole Cadwalladr. Following her TED2025 stage talk, Cadwalladr is in conversation with Chris Anderson, head of TED, to warn about surveillance fascism. What happens when big Silicon Valley companies take over communication platforms and weaponize intellectual property against you? She suggests that when you feel powerless, it's often actually because you are powerful — and explores why it's so important to fight information chaos by supporting independent media and journalists.

view more: next ›