wobin@0: -- A Twitter client of sorts for World of Warcraft wobin@0: -- Author: Wobin wobin@0: -- Email: wobster@gmail.com wobin@0: -- wobin@1: Squawk = LibStub("AceAddon-3.0"):NewAddon("Squawk") wobin@0: wobin@0: Squawk.Model = {} wobin@0: Squawk.View = {} wobin@0: Squawk.Controller = {} wobin@0: wobin@0: local Model = Squawk.Model wobin@0: local View = Squawk.View wobin@0: local Controller = Squawk.Controller wobin@0: wobin@0: Model.UserSettings = {} wobin@0: local Settings = Model.UserSettings wobin@0: wobin@0: local defaults = { wobin@0: profile = { wobin@0: Squawks = {}, wobin@0: Follower = {}, wobin@0: Following = {}, wobin@7: Pending = {}, wobin@7: Requested = {}, wobin@0: Blocked = {}, wobin@0: } wobin@0: } wobin@0: wobin@0: function Squawk:OnInitialize() wobin@0: Model.db = LibStub("AceDB-3.0"):New("SquawkDB", defaults) wobin@4: Model.Squawks = Model.db.profile.Squawks wobin@0: Settings.Follower = Model.db.profile.Follower wobin@0: Settings.Following = Model.db.profile.Following wobin@7: Settings.Pending = Model.db.profile.Pending wobin@7: Settings.Requested = Model.db.profile.Requested wobin@0: Settings.Blocked = Model.db.profile.Blocked wobin@0: Settings.Private = Model.db.profile.Private wobin@0: wobin@0: LibStub("AceComm-3.0"):Embed(Controller) wobin@3: Controller:RegisterComm("Squawk", Controller.ReceiveMessage) wobin@3: wobin@3: LibStub("AceConsole-3.0"):Embed(View) wobin@0: end wobin@0: wobin@0: -- Model -- wobin@0: --[[ wobin@0: --Each Squawk will have the following information: wobin@0: -- * Owner (Name) wobin@0: -- * Time (Epoch) wobin@0: -- * Message (140 characters) wobin@0: -- * ReplyTo (Name) wobin@0: -- * Related (Names) wobin@0: -- wobin@0: -- Each User will have the following lists: wobin@0: -- * Follower wobin@0: -- * Following wobin@0: -- * Blocked wobin@7: -- * Pending (Requests to follow that you haven't acted on) wobin@7: -- * Requested (Requests to follow that you have made) wobin@0: -- * Privacy State wobin@7: -- wobin@7: -- A user can only request to follow an online person. Requests can be approved wobin@7: -- on or offline, but the initial request must be made online. wobin@7: -- wobin@7: -- When a user makes a request to follow a private user, the subsequent paths occur: wobin@7: -- - Followee is added to Settings.Requested wobin@7: -- - Followee receives 'follow request' -> (their) Settings.Pending wobin@7: -- - Followee acts on request -> (their) Settings.Pending cleared wobin@7: -- 1) Follwer is online wobin@7: -- - Follower receives 'request accepted' -> Added to Settings.Following and wobin@7: -- cleared from Settings.Requested wobin@7: -- 2) Follower is offline wobin@7: -- - The next time Follower is online and recieves a Squawk we check if there wobin@7: -- is a Settings.Requested for that name, and if so assume they have approved wobin@7: -- and clear/add records appropriately. wobin@13: -- wobin@13: -- For updating, there can be a few methods. wobin@13: -- wobin@13: -- Guild open: you're not private, you broadcast to the guild your last X wobin@13: -- squawks on login wobin@13: -- wobin@13: -- followers: wobin@0: --]] wobin@0: Model.Squawk = {} wobin@0: local Squawk = Model.Squawk wobin@13: local function wrap(str, limit) wobin@13: limit = limit or 72 wobin@13: local here = 1 wobin@13: return str:gsub("(%s+)()(%S+)()", wobin@13: function(sp, st, word, fi) wobin@13: if fi-here > limit then wobin@13: here = st wobin@13: return "\n"..word wobin@13: end wobin@13: end) wobin@13: end wobin@0: function Squawk:new(Message, Owner) wobin@0: local o = {} wobin@0: o.Owner = Owner or UnitName("player") wobin@13: o.Message = wrap(Message) wobin@10: o.Time = time() wobin@0: local reply, to = strsplit("@", ((strsplit(" ", Message)))) wobin@0: if reply == "" then wobin@0: o.ReplyTo = to wobin@0: end wobin@0: wobin@0: o.Related = {} wobin@0: wobin@0: for word in string.gmatch(Message, "@(%a+)") do wobin@0: if word ~= o.ReplyTo or "" then wobin@0: table.insert(o.Related, word) wobin@0: end wobin@0: end wobin@0: wobin@0: return o wobin@0: end wobin@0: wobin@0: function Squawk:Sort(Squawks) wobin@0: return table.sort(Squawks, function(a,b) return a.Time < b.Time end) wobin@0: end wobin@0: wobin@2: function Squawk:GetOwn(Squawks) wobin@2: local mine = {} wobin@2: for _, squawk in ipairs(Squawks) do wobin@2: if squawk.Owner == UnitName("player") then wobin@2: table.insert(mine, squawk) wobin@2: end wobin@2: end wobin@2: return self:Sort(mine) wobin@2: end wobin@2: wobin@0: function Settings:IsPrivate() wobin@0: return Settings.Private wobin@0: end wobin@0: wobin@10: function Settings:TogglePrivate() wobin@10: Settings.Private = not Settings.Private wobin@10: end wobin@10: wobin@0: function Settings:AddFollower(Name) wobin@2: Settings.Follower[Name] = 1 wobin@7: self:RemovePending(Name) wobin@0: end wobin@0: wobin@0: function Settings:AddFollowing(Name) wobin@2: Settings.Following[Name] = 1 wobin@7: self:RemoveRequested(Name) wobin@0: end wobin@0: wobin@0: function Settings:AddBlock(Name) wobin@2: Settings.Blocked[Name] = 1 wobin@7: self:RemoveFollower(Name) wobin@7: self:RemoveFollowing(Name) wobin@7: end wobin@7: wobin@7: function Settings:AddPending(Name) wobin@7: Settings.Pending[Name] = 1 wobin@7: end wobin@7: wobin@7: function Settings:AddRequested(Name) wobin@7: Settings.Requested[Name] = 1 wobin@0: end wobin@0: wobin@0: function Settings:RemoveFollower(Name) wobin@2: if Settings.Follower[Name] then wobin@2: Settings.Follower[Name] = nil wobin@0: end wobin@0: end wobin@0: wobin@0: function Settings:RemoveFollowing(Name) wobin@2: if Settings.Following[Name] then wobin@2: Settings.Following[Name] = nil wobin@0: end wobin@0: end wobin@0: wobin@0: function Settings:RemoveBlock(Name) wobin@2: if Settings.Blocked[Name] then wobin@2: Settings.Blocked[Name] = nil wobin@0: end wobin@0: end wobin@0: wobin@7: function Settings:RemovePending(Name) wobin@7: if Settings.Pending[Name] then wobin@7: Settings.Pending[Name] = nil wobin@7: end wobin@7: end wobin@7: wobin@7: function Settings:RemoveRequested(Name) wobin@7: if Settings.Requested[Name] then wobin@7: Settings.Requested[Name] = nil wobin@7: end wobin@7: end wobin@7: wobin@0: --Controller-- wobin@0: wobin@0: function Controller:TheyWantToFollowMe(Name) wobin@0: if Settings:IsPrivate() then wobin@7: Settings:AddPending(Name) wobin@0: self:PutForwardFollowRequest(Name) wobin@0: self:SendMessageToTarget(Name, "#Pending|"..UnitName("player")) wobin@0: else wobin@0: Settings:AddFollower(Name) wobin@0: View:NotifyOfNewFollower(Name) wobin@0: self:SendMessageToTarget(Name, "#Follow|"..UnitName("player")) wobin@0: end wobin@0: end wobin@0: wobin@7: function Controller:TheyWantToUnfollowMe(Name) wobin@7: Settings:RemoveFollower(Name) wobin@7: end wobin@7: wobin@0: function Controller:IWantToFollowThem(Name) wobin@2: self:SendMessageToTarget(Name, "#Request|"..UnitName("player")) wobin@7: Settings:AddRequested(Name) wobin@7: end wobin@7: wobin@7: function Controller:IWantToUnfollowThem(Name) wobin@7: Settings:RemoveFollowing(Name) wobin@7: self:SendMessageToTarget(Name, "#Unfollow|"..UnitName("player")) wobin@7: View:NotifyOfUnfollowing(Name) wobin@0: end wobin@0: wobin@0: function Controller:IAmNowFollowingThem(Name) wobin@0: Settings:AddFollowing(Name) wobin@0: View:NotifyOfNewFollowing(Name) wobin@0: end wobin@0: wobin@7: function Controller:AddANewSquawk(Name, Message, Source) wobin@10: if not Settings.Blocked[Name] then wobin@7: wobin@7: if Source == "WHISPER" then wobin@10: if Settings.Requested[Name] then -- We've been approved offline! wobin@10: Settings:AddFollowing(Name) wobin@7: end wobin@7: wobin@10: if not Settings.Following[Name] then -- If we're no longer following this person wobin@7: self:SendMessageToTarget(Name, "#Unfollow|"..UnitName("player")) wobin@9: return wobin@9: end wobin@10: end wobin@10: wobin@10: if Source == "GUILD" and Name == UnitName("player") then wobin@10: return wobin@7: end wobin@10: wobin@10: table.insert(Model.Squawks, Squawk:new(Message, Name)) wobin@10: View:UpdateSquawkList() wobin@7: end wobin@0: end wobin@0: wobin@2: function Controller:SendNewSquawk(Message) wobin@10: if not Settings:IsPrivate() then wobin@2: self:SendMessageToGuild("#Squawk|"..UnitName("player").."|"..Message) wobin@2: end wobin@2: wobin@2: self:AddANewSquawk(UnitName("player"), Message) wobin@2: wobin@10: for name, _ in pairs(Settings.Following) do wobin@2: self:SendMessageToTarget(name, "#Squawk|"..UnitName("player").."|"..Message) wobin@2: end wobin@2: end wobin@2: wobin@1: function Controller:ImPending(Name) wobin@1: View:NotifyOfPending(Name) wobin@1: end wobin@1: wobin@7: function Controller:PutForwardFollowRequest(Name) wobin@7: View:NotifyOfPendingRequest(Name) wobin@7: end wobin@7: wobin@10: function Controller:ApprovePendingRequest(Name) wobin@10: Settings:AddFollower(Name) wobin@10: View:NotifyOfNewFollower(Name) wobin@10: self:SendMessageToTarget(Name, "#Follow|"..UnitName("player")) wobin@10: end wobin@10: wobin@0: function Controller:SendMessageToTarget(Name, Message) wobin@10: self:SendCommMessage("Squawk", Message, "WHISPER", Name) wobin@0: end wobin@0: wobin@0: function Controller:SendMessageToGuild(Message) wobin@0: self:SendCommMessage("Squawk", Message, "GUILD") wobin@0: end wobin@0: wobin@0: local Parse = { wobin@1: ["#Pending"] = Controller.ImPending, wobin@1: ["#Follow"] = Controller.IAmNowFollowingThem, wobin@7: ["#Unfollow"] = Controller.TheyWantToUnfollowMe, wobin@2: ["#Squawk"] = Controller.AddANewSquawk, wobin@2: ["#Request"] = Controller.TheyWantToFollowMe, wobin@0: } wobin@0: wobin@10: function Controller:ReceiveMessage(Message, Distribution, Sender) wobin@0: local command, name, info = strsplit("|",Message) wobin@10: View:Print(Distribution..":"..Message) wobin@10: Parse[command](Controller, name, info, Distribution) wobin@0: end wobin@0: -- View -- wobin@0: wobin@0: function View:UpdateSquawkList() wobin@3: self:Print("Updated Squawk List") wobin@4: self:ShowMeMySquawks() wobin@0: end wobin@0: wobin@3: function View:NotifyOfPending(Name) wobin@3: self:Print(Name.." will have to approve your request") wobin@0: end wobin@0: wobin@7: function View:NotifyOfPendingRequest(Name) wobin@7: self:Print(Name.." wants to follow you.") wobin@7: end wobin@7: wobin@0: function View:NotifyOfNewFollowing(Name) wobin@3: self:Print("You are now following "..Name) wobin@0: end wobin@0: wobin@7: function View:NotifyOfUnfollowing(Name) wobin@7: self:Print("You are no longer following "..Name) wobin@7: end wobin@7: wobin@0: function View:NotifyOfNewFollower(Name) wobin@3: self:Print(Name.." is now following you") wobin@0: end wobin@4: wobin@4: function View:ShowMeMySquawks() wobin@5: for _,squawk in ipairs(Model.Squawks) do wobin@4: self:Print(squawk.Message) wobin@4: end wobin@4: end wobin@4: wobin@4: function View:ShowMeMyFollowers() wobin@4: self:Print("My followers are:") wobin@4: for name,_ in pairs(Settings.Follower) do wobin@4: self:Print(name) wobin@4: end wobin@4: end wobin@4: wobin@4: function View:ShowMeWhoImFollowing() wobin@4: self:Print("I am following:") wobin@4: for name,_ in pairs(Settings.Following) do wobin@4: self:Print(name) wobin@4: end wobin@4: end wobin@4: wobin@4: function View:ShowMeWhoIveBlocked() wobin@4: self:Print("I've blocked:") wobin@6: for name,_ in pairs(Settings.Blocked) do wobin@4: self:Print(name) wobin@4: end wobin@4: end wobin@10: wobin@13: local TimeSpan = { [1] = {"second", 60, 1}, wobin@13: [2] = {"minute", 3600, 60}, wobin@13: [3] = {"hour", 86400, 3600} } wobin@13: wobin@13: function View:GetTime(stime) wobin@13: local lapsed = difftime(time(), stime) wobin@13: if lapsed < 86400 then -- if we're still in the same day... wobin@13: for _,span in ipairs(TimeSpan) do wobin@13: if lapsed < span[2] then wobin@13: local timespan = math.floor(lapsed/span[3]) wobin@13: if timespan == 1 then wobin@13: timespan = timespan .." ".. span[1] wobin@13: else wobin@13: timespan = timespan .. " ".. span[1].."s" wobin@13: end wobin@13: return timespan.. " ago" wobin@13: end wobin@13: end wobin@13: end wobin@13: return date("%I:%M %p %b %d", stime) wobin@13: end wobin@13: wobin@10: local LDBFeed = LibStub("LibDataBroker-1.1"):NewDataObject("Squawk", {type = "data source", text = "Awk!"}) wobin@10: local QTip = LibStub("LibQTip-1.0") wobin@10: wobin@13: local function AddLine(tooltip, Line, Number, Owner, TimeStamp) wobin@13: if #Line < 79 then wobin@13: tooltip:AddLine(Number, Owner, Line, TimeStamp) wobin@13: else wobin@13: tooltip:AddLine(Number, Owner, Line:sub(1, 80).."-", TimeStamp) wobin@13: AddLine(tooltip, Line:sub(81)) wobin@13: end wobin@13: end wobin@13: wobin@10: function LDBFeed:OnEnter() wobin@13: local tooltip = QTip:Acquire("Squawk",4, "LEFT", "LEFT", "LEFT", "RIGHT") wobin@10: self.tooltip = tooltip wobin@13: tooltip:AddHeader('Squawk') wobin@13: for i,squawk in ipairs(Model.Squawks) do wobin@13: if #squawk.Message > 79 then wobin@13: local head = true wobin@13: local message = {strsplit("\n",squawk.Message)} wobin@13: for _,line in ipairs(message) do wobin@13: if head then wobin@13: AddLine(tooltip, line, i..".", squawk.Owner, View:GetTime(squawk.Time)) wobin@13: head = false wobin@13: else wobin@13: AddLine(tooltip, line) wobin@13: end wobin@13: end wobin@13: else wobin@13: tooltip:AddLine(i..".", squawk.Owner, squawk.Message, View:GetTime(squawk.Time)) wobin@13: end wobin@13: end wobin@10: tooltip:SmartAnchorTo(self) wobin@10: tooltip:Show() wobin@10: end wobin@10: wobin@10: function LDBFeed:OnTooltipShow() wobin@10: end wobin@10: wobin@10: function LDBFeed:OnLeave() wobin@10: QTip:Release(self.tooltip) wobin@10: end