diff --git a/config/config.sample.yaml b/config/config.sample.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8fb83dcb1bc2d4fae32d7f47b17691b3fa783c5c
--- /dev/null
+++ b/config/config.sample.yaml
@@ -0,0 +1,7 @@
+bridge:
+  domain: localhost
+  homeserverUrl: http://localhost:8008
+auth:
+  clientID: 12345 # Get from discord
+  secret: blah
+  botToken: foobar
diff --git a/config/config.schema.yaml b/config/config.schema.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..798771b14f4ebc5eb57f82021970728d0055f2eb
--- /dev/null
+++ b/config/config.schema.yaml
@@ -0,0 +1,22 @@
+"$schema": "http://json-schema.org/draft-04/schema#"
+type: "object"
+required: ["bridge", "auth"]
+properties:
+    bridge:
+        type: "object"
+        required: ["domain", "homeserverUrl"]
+        properties:
+          domain:
+            type: "string"
+          homeserverUrl:
+            type: "string"
+    auth:
+        type: "object"
+        required: ["botToken"]
+        properties:
+          clientID:
+            type: "string"
+          secret:
+            type: "string"
+          botToken:
+            type: "string"
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c9af63d94d57dc5cccc655bd6a3471f5968131e8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "matrix-appservice-discord",
+  "version": "0.0.1",
+  "description": "A bridge between Matrix and Discord",
+  "main": "discordas.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "lint": "tslint --project ./tsconfig.json",
+    "coverage": "istanbul --include-all-sources cover -x /src/discordas.js _mocha -- test/ -R spec",
+    "build": "tsc",
+    "start": "tsc && node ./build/discordas.js -p 9005 -c config.yaml",
+    "getbotlink": "node ./tools/addbot.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/Half-Shot/matrix-appservice-discord.git"
+  },
+  "keywords": [
+    "matrix",
+    "discord",
+    "bridge",
+    "application-service",
+    "as"
+  ],
+  "author": "Half-Shot",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/Half-Shot/matrix-appservice-discord/issues"
+  },
+  "homepage": "https://github.com/Half-Shot/matrix-appservice-discord#readme",
+  "dependencies": {
+    "@types/node": "^7.0.5",
+    "bluebird": "^3.4.7",
+    "discord.js": "^11.0.0",
+    "js-yaml": "^3.8.1",
+    "npmlog": "^4.0.2",
+    "tslint": "^4.4.2",
+    "typescript": "^2.1.6"
+  },
+  "devDependencies": {
+    "chai": "^3.5.0",
+    "chai-as-promised": "^6.0.0",
+    "eslint": "^3.8.1",
+    "istanbul": "^0.4.5",
+    "mocha": "^3.1.2"
+  }
+}
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d2c7779424f11a5d3296eedbc0b8858fc3b3387c
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,23 @@
+/** Type annotations for config/config.schema.yaml */
+
+export class DiscordBridgeConfig {
+  public bridge: DiscordBridgeConfigBridge;
+  public auth: DiscordBridgeConfigAuth;
+  public guilds: DiscordBridgeConfigGuilds[];
+}
+
+class DiscordBridgeConfigBridge {
+  public domain: string;
+  public homeserverUrl: string;
+}
+
+class DiscordBridgeConfigAuth {
+  public clientID: string;
+  public secret: string;
+  public botToken: string;
+}
+
+class DiscordBridgeConfigGuilds {
+  public id: string;
+  public aliasName: string;
+}
diff --git a/src/discordas.ts b/src/discordas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1bfbf460324196d5fe548ec656b6cc4b84170be1
--- /dev/null
+++ b/src/discordas.ts
@@ -0,0 +1,79 @@
+import { Cli, Bridge, AppServiceRegistration, ClientFactory } from "matrix-appservice-bridge";
+import * as log from "npmlog";
+import * as yaml from "js-yaml";
+import * as fs from "fs";
+import { DiscordBridgeConfig } from "./config";
+import { DiscordBot } from "./discordbot";
+import { MatrixRoomHandler } from "./matrixroomhandler";
+
+const cli = new Cli({
+  bridgeConfig: {
+    affectsRegistration: true,
+    schema: "./config/config.schema.yaml",
+  },
+  registrationPath: "discord-registration.yaml",
+  generateRegistration,
+  run,
+});
+
+try {
+  cli.run();
+} catch (err) {
+  console.error("Init", "Failed to start bridge."); // eslint-disable-line no-console
+  console.error("Init", err); // eslint-disable-line no-console
+}
+
+function generateRegistration(reg, callback)  {
+  reg.setId(AppServiceRegistration.generateToken());
+  reg.setHomeserverToken(AppServiceRegistration.generateToken());
+  reg.setAppServiceToken(AppServiceRegistration.generateToken());
+  reg.setSenderLocalpart("_discord_bot");
+  reg.addRegexPattern("users", "@_discord_.*", true);
+  reg.addRegexPattern("aliases", "#_discord_.*", true);
+  callback(reg);
+}
+
+function run (port: number, config: DiscordBridgeConfig) {
+  log.info("discordas", "Starting Discord AS");
+  const yamlConfig = yaml.safeLoad(fs.readFileSync("discord-registration.yaml", "utf8"));
+  const registration = AppServiceRegistration.fromObject(yamlConfig);
+  if (registration === null) {
+    throw new Error("Failed to parse registration file");
+  }
+
+  const clientFactory = new ClientFactory({
+    appServiceUserId: "@" + registration.sender_localpart + ":" + config.bridge.domain,
+    token: registration.as_token,
+    url: config.bridge.homeserverUrl,
+  });
+
+  const bridge = new Bridge({
+    clientFactory,
+    controller: {
+      // onUserQuery: userQuery,
+      onAliasQuery: (alias, aliasLocalpart) => {
+        return roomhandler.OnAliasQuery(alias, aliasLocalpart);
+      },
+      onEvent: (request, context) => { roomhandler.OnEvent(request, context); },
+      onAliasQueried: (alias, roomId) => { return roomhandler.OnAliasQueried(alias, roomId); },
+      // onLog: function (line, isError) {
+      //   if(isError) {
+      //     if(line.indexOf("M_USER_IN_USE") === -1) {//QUIET!
+      //       log.warn("matrix-appservice-bridge", line);
+      //     }
+      //   }
+      // }
+    },
+    domain: config.bridge.domain,
+    homeserverUrl: config.bridge.homeserverUrl,
+    registration,
+  });
+
+  const discordbot = new DiscordBot(config, bridge);
+  const roomhandler = new MatrixRoomHandler(bridge, discordbot, config);
+
+  log.info("AppServ", "Started listening on port %s at %s", port, new Date().toUTCString() );
+  bridge.run(port, config);
+  discordbot.run();
+
+}
diff --git a/src/discordbot.ts b/src/discordbot.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd41e2213c3e19f7fb6369f0e187839393a77512
--- /dev/null
+++ b/src/discordbot.ts
@@ -0,0 +1,111 @@
+import { DiscordBridgeConfig } from "./config";
+import * as Discord from "discord.js";
+import * as log from "npmlog";
+
+export class DiscordBot {
+  private config: DiscordBridgeConfig;
+  private bot: Discord.Client;
+  private bridge;
+  constructor(config: DiscordBridgeConfig, bridge) {
+    this.config = config;
+    this.bridge = bridge;
+  }
+
+  public run () {
+    this.bot = new Discord.Client();
+
+    this.bot.on("ready", () => {
+      log.info("DiscordBot", "I am ready!");
+    });
+
+    this.bot.on("typingStart", (c, u) => { this.OnTyping(c, u, true); });
+    this.bot.on("typingStop", (c, u) => { this.OnTyping(c, u, false); });
+    // create an event listener for messages
+    this.bot.on("message", (msg) => {
+      this.GetRoomIdFromChannel(msg.channel).then((room) => {
+        const intent = this.bridge.getIntentFromLocalpart(`_discord_${msg.author.id}`);
+        intent.sendText(room, msg.content);
+      });
+    });
+
+    this.bot.login(this.config.auth.botToken);
+  }
+
+  private GetRoomIdFromChannel(channel: Discord.Channel): Promise<string> {
+    return this.bridge.getRoomStore().getEntriesByRemoteRoomData({
+      discord_channel: channel.id,
+    }).then((rooms) => {
+      if (rooms.length === 0) {
+        log.warn("DiscordBot", `Got message but couldn't find room chan id:${channel.id} for it.`);
+        return Promise.reject("Room not found.");
+      }
+      return rooms[0].matrix.getId();
+    });
+  }
+
+  private OnTyping(channel: Discord.Channel, user: Discord.User, isTyping: boolean) {
+    this.GetRoomIdFromChannel(channel).then((room) => {
+      const intent = this.bridge.getIntentFromLocalpart(`_discord_${user.id}`);
+      intent.sendTyping(room, isTyping);
+    });
+
+  }
+
+  public GetBot (): Discord.Client {
+    return this.bot;
+  }
+
+  public LookupRoom (server: string, room: string): Promise<Discord.TextChannel> {
+    const guild = this.bot.guilds.find((g) => {
+      return (g.id === server || g.name.replace(" ", "-") === server);
+    });
+    if (guild === null) {
+      return Promise.reject("Guild not found");
+    }
+
+    const channel = guild.channels.find((c) => {
+      return ((c.id === room  || c.name.replace(" ", "-") === room ) && c.type === "text");
+    });
+    if (channel === null) {
+      return Promise.reject("Channel not found");
+    }
+    return Promise.resolve(channel);
+
+  }
+
+  public ProcessMatrixMsgEvent(event, guild_id: string, channel_id: string): Promise<any> {
+    if (event.content.msgtype !== "m.text") {
+      return Promise.reject("The AS doesn't support non m.text messages");
+    }
+    let chan;
+    const mxClient = this.bridge.getClientFactory().getClientAs();
+    this.LookupRoom(guild_id, channel_id).then((channel) => {
+      chan = channel;
+      return mxClient.getProfileInfo(event.sender);
+    }).then((profile) => {
+      if (!profile.displayname) {
+        profile.displayname = event.sender;
+      }
+      if (profile.avatar_url) {
+        profile.avatar_url = mxClient.mxcUrlToHttp(profile.avatar_url);
+      }
+      const embed = new Discord.RichEmbed({
+        author: {
+          name: profile.displayname,
+          icon_url: profile.avatar_url,
+          url: `https://matrix.to/#/${event.sender}`,
+          // TODO: Avatar
+        },
+        description: event.content.body,
+      });
+      log.info("DiscordBot", "Outgoing Message ", embed)
+      chan.sendEmbed(embed);
+    }).catch((err) => {
+      log.error("DiscordBot", "Couldn't send message. ", err);
+    });
+  }
+
+  public OnUserQuery (userId: string): any {
+    return false;
+  }
+}
diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e4d3f4f5ec1453d0ece87c6fd8741fde062e5b84
--- /dev/null
+++ b/src/matrixroomhandler.ts
@@ -0,0 +1,115 @@
+import { DiscordBot } from "./discordbot";
+import { Bridge, RemoteRoom } from "matrix-appservice-bridge";
+import { DiscordBridgeConfig } from "./config";
+
+import * as Discord from "discord.js";
+import * as log from "npmlog";
+
+export class MatrixRoomHandler {
+  private config: DiscordBridgeConfig;
+  private bridge: Bridge;
+  private discord: DiscordBot;
+  private alias_list: any;
+  constructor (bridge: Bridge, discord: DiscordBot, config: DiscordBridgeConfig) {
+    this.bridge = bridge;
+    this.discord = discord;
+    this.config = config;
+    this.alias_list = {};
+  }
+
+  public OnAliasQueried (alias: string, roomId: string) {
+    const aliasLocalpart = alias.substr(1, alias.length - `:${this.config.bridge.domain}`.length - 1);
+    log.info("MatrixRoomHandler", `Room created ${aliasLocalpart} => ${roomId}`);
+    if (this.alias_list[aliasLocalpart] == null) {
+      log.warn("MatrixRoomHandler", "Room was created but we couldn't assign additonal aliases");
+      return;
+    }
+    const mxClient = this.bridge.getClientFactory().getClientAs();
+    this.alias_list[aliasLocalpart].forEach((item) => {
+      if (item === "#" + aliasLocalpart) {
+        return;
+      }
+      mxClient.createAlias(item, roomId).catch( (err) => {
+        log.warn("MatrixRoomHandler", `Failed to create alias '${aliasLocalpart} for ${roomId}'`, err);
+      });
+    });
+    delete this.alias_list[aliasLocalpart];
+  }
+
+  public OnEvent (request, context) {
+    console.log(context);
+    const event = request.getData();
+    if (event.type === "m.room.message" && context.rooms.remote) {
+      let srvChanPair = context.rooms.remote.roomId.substr("_discord".length).split("_", 2);
+      this.discord.ProcessMatrixMsgEvent(event, srvChanPair[0], srvChanPair[1]);
+    }
+  }
+
+  public OnAliasQuery (alias: string, aliasLocalpart: string): Promise<any> {
+    let srvChanPair = aliasLocalpart.substr("_discord_".length).split("_", 2);
+    if (srvChanPair.length < 2 || srvChanPair[0] === "" || srvChanPair[1] === "") {
+      log.warn("MatrixRoomHandler", `Alias '${aliasLocalpart}' was missing a server and/or a channel`);
+      return;
+    }
+    return this.discord.LookupRoom(srvChanPair[0], srvChanPair[1]).then((channel) => {
+      return this.createMatrixRoom(channel, aliasLocalpart);
+    }).catch((err) => {
+      log.error("MatrixRoomHandler", `Couldn't find discord room '${aliasLocalpart}'.`, err);
+    });
+  }
+
+  private createMatrixRoom (channel: Discord.TextChannel, alias: string) {
+    const botID = this.bridge.getBot().getUserId();
+    // const roomOwner = "@_discord_" + user.id_str + ":" + this._bridge.opts.domain;
+    const users = {};
+    users[botID] = 100;
+    // users[roomOwner] = 75;
+    // var powers = util.roomPowers(users);
+    const remote = new RemoteRoom(`discord_${channel.guild.id}_${channel.id}`);
+    remote.set("discord_type", "text");
+    remote.set("discord_guild", channel.guild.id);
+    remote.set("discord_channel", channel.id);
+
+    const gname = channel.guild.name.replace(" ", "-");
+    const cname = channel.name.replace(" ", "-");
+
+    this.alias_list[alias] = [
+      `#_discord_${channel.guild.id}_${channel.id}:${this.config.bridge.domain}`,
+      `#_discord_${channel.guild.id}_${cname}:${this.config.bridge.domain}`,
+      `#_discord_${gname}_${channel.id}:${this.config.bridge.domain}`,
+      `#_discord_${gname}_${cname}:${this.config.bridge.domain}`,
+    ];
+
+    const creationOpts = {
+      visibility: "public",
+      room_alias_name: alias,
+      name: `[Discord] ${channel.guild.name}#${channel.name}`,
+      topic: channel.topic ? channel.topic : "",
+      // invite: [roomOwner],
+      initial_state: [
+        // powers,
+        {
+          type: "m.room.join_rules",
+          content: {
+            join_rule: "public",
+          },
+          state_key: "",
+        }
+        // }, {
+        //   type: "org.matrix.twitter.data",
+        //   content: user,
+        //   state_key: ""
+        // }, {
+        //   type: "m.room.avatar",
+        //   state_key: "",
+        //   content: {
+        //     url: avatar
+        //   }
+      ],
+    };
+    return {
+      creationOpts,
+      remote,
+    };
+  }
+}
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000000000000000000000000000000000000..a93481d22320c95def1406620b32c228b27f0268
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,3 @@
+--reporter list
+--ui bdd
+--recursive
diff --git a/tools/addbot.js b/tools/addbot.js
new file mode 100644
index 0000000000000000000000000000000000000000..86d55daca8199114718094ba074c18c8d06e1463
--- /dev/null
+++ b/tools/addbot.js
@@ -0,0 +1,18 @@
+const yaml = require("js-yaml");
+const fs = require("fs");
+const flags = require("../node_modules/discord.js/src/util/Constants.js").PermissionFlags;
+const yamlConfig = yaml.safeLoad(fs.readFileSync("config.yaml", "utf8"));
+if (yamlConfig === null) {
+  console.error("You have an error in your discord config.");
+}
+const client_id = yamlConfig.auth.clientID;
+const perms = flags.READ_MESSAGES |
+  flags.SEND_MESSAGES |
+  flags.CHANGE_NICKNAME |
+  flags.CONNECT |
+  flags.SPEAK |
+  flags.EMBED_LINKS |
+  flags.ATTACH_FILES |
+  flags.READ_MESSAGE_HISTORY;
+
+console.log(`Go to https://discordapp.com/api/oauth2/authorize?client_id=${client_id}&scope=bot&permissions=${perms} to invite the bot into a guild.`);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..b51be7ecfed92da985567e893e71390904ef1347
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "moduleResolution": "Node",
+        "target": "ES6",
+        "noImplicitAny": false,
+        "sourceMap": false,
+        "outDir": "./build"
+    },
+    "compileOnSave": true,
+    "include": [
+        "src/**/*"
+    ]
+}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000000000000000000000000000000000000..c49a430912b2ce1a84844f77da03771404e0ad6c
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,9 @@
+{
+  "extends": "tslint:recommended",
+  "rules": {
+    "ordered-imports": "off",
+    "no-trailing-whitespace": "off",
+    "max-classes-per-file": "off",
+    "object-literal-sort-keys": "off"
+  }
+}